作者 | 聲東 阿裡雲售後技術專家
導讀:阿裡雲售後技術團隊的同學,每天都在處理各式各樣千奇百怪的線上問題。常見的有網絡連接配接失敗、伺服器當機、性能不達标及請求響應慢等。但如果要評選的話,什麼問題看起來微不足道事實上卻讓人絞盡腦汁,我相信肯定是“删不掉”的問題,比如檔案删不掉、程序結束不掉、驅動解除安裝不了等。這樣的問題就像冰山,隐藏在它們背後的複雜邏輯,往往超過我們的預想。
背景
今天我們讨論的這個問題,跟 K8s 叢集的 Namespace 有關。Namespace 是 K8s 叢集資源的“收納”機制。我們可以把相關的資源“收納”到同一個 Namespace 裡,以避免不相關資源之間不必要的影響。
Namespace 本身也是一種資源。通過叢集 API Server 入口,我們可以建立 Namespace,而對于不再使用的 Namespace,我們需要清理掉。Namespace 的 Controller 會通過 API Server,監視叢集中 Namespace 的變化,然後根據變化來執行預先定義的動作。

有時候,我們會遇到下圖中的問題,即 Namespace 的狀态被标記成了 "Terminating",但卻沒有辦法被完全删除。
從叢集入口開始
因為删除操作是通過叢集 API Server 來執行的,是以我們要分析 API Server 的行為。跟大多數叢集元件類似,API Server 提供了不同級别的日志輸出。為了了解 API Server 的行為,我們将日志級别調整到最進階。然後,通過建立删除 tobedeletedb 這個 Namespace 來重制問題。
但可惜的是,API Server 并沒有輸出太多和這個問題有關的日志。
相關的日志,可以分為兩部分:
- 一部分是 Namespace 被删除的記錄,記錄顯示用戶端工具是 kubectl,以及發起操作的源 IP 位址是 192.168.0.41,這符合預期;
- 另外一部分是 Kube Controller Manager 在重複地擷取這個 Namespace 的資訊。
Kube Controller Manager 實作了叢集中大多數的 Controller,它在重複擷取 tobedeletedb 的資訊,基本上可以判斷,是 Namespace 的 Controller 在擷取這個 Namespace 的資訊。
Controller 在做什麼?
和上一節類似,我們通過開啟 Kube Controller Manager 最進階别日志,來研究這個元件的行為。在 Kube Controller Manager 的日志裡,可以看到 Namespace 的 Controller 在不斷地嘗試一個失敗了的操作,就是清理 tobedeletedb 這個 Namespace 裡“收納”的資源。
怎麼樣删除“收納盒”裡的資源?
這裡我們需要了解一點,就是 Namespace 作為資源的“收納盒”,其實是邏輯意義上的概念。它并不像現實中的收納工具,可以把小的物件收納其中。Namespace 的“收納”實際上是一種映射關系。
這一點之是以重要,是因為它直接決定了,删除 Namespace 内部資源的方法。如果是實體意義上的“收納”,那我們隻需要删除“收納盒”,裡邊的資源就一并被删除了。而對于邏輯意義上的關系,我們則需要羅列所有資源,并删除那些指向需要删除的 Namespace 的資源。
API、Group、Version
怎麼樣羅列叢集中的所有資源呢?這個問題需要從叢集 API 的組織方式說起。K8s 叢集的 API 不是鐵闆一塊的,它是用分組和版本來組織的。這樣做的好處顯而易見,就是不同分組的 API 可以獨立疊代,互不影響。常見的分組如 apps,它有 v1、v1beta1 和 v1beta2 三個版本。完整的分組/版本清單,可以使用 kubectl api-versions 指令看到。
我們建立的每一個資源,都必然屬于某一個 API 分組/版本。以下邊 Ingress 為例,我們指定 Ingress 資源的分組/版本為 networking.k8s.io/v1beta1。
kind: Ingress
metadata:
name: test-ingress
spec:
rules:
- http:
paths:
- path: /testpath
backend:
serviceName: test
servicePort: 80
用一個簡單的示意圖來總結 API 分組和版本。
實際上,叢集有很多 API 分組/版本,每個 API 分組/版本支援特定的資源類型。我們通過 yaml 編排資源時,需要指定資源類型 kind,以及 API 分組/版本 apiVersion。而要列出資源,我們需要擷取 API 分組/版本的清單。
Controller 為什麼不能删除 Namespace 裡的資源
了解了 API 分組/版本的概念之後,再回頭看 Kube Controller Manager 的日志,就會豁然開朗。顯然 Namespace 的 Controller 在嘗試擷取 API 分組/版本清單,當遇到 metrics.k8s.io/v1beta1 的時候,查詢失敗了。并且查詢失敗的原因是 "the server is currently unable to handle the request"。
再次回到叢集入口
在上一節中,我們發現 Kube Controller Manager 在擷取 metrics.k8s.io/v1beta1 這個 API 分組/版本的時候失敗了。而這個查詢請求,顯然是發給 API Server 的。是以我們回到 API Server 日志,分析 metrics.k8s.io/v1beta1 相關的記錄。在相同的時間點,我們看到 API Server 也報了同樣的錯誤 "the server is currently unable to handle the request"。
顯然這裡有一個沖突,就是 API Server 明顯在正常工作,為什麼在擷取 metrics.k8s.io/v1beta1 這個 API 分組版本的時候,會傳回 Server 不可用呢?為了回答這個問題,我們需要了解一下 API Server 的“外挂”機制。
叢集 API Server 有擴充自己的機制,開發者可以利用這個機制,來實作 API Server 的“外挂”。這個“外挂”的主要功能,就是實作新的 API 分組/版本。API Server 作為代理,會把相應的 API 調用,轉發給自己的“外挂”。
以 Metrics Server 為例,它實作了 metrics.k8s.io/v1beta1 這個 API 分組/版本。所有針對這個分組/版本的調用,都會被轉發到 Metrics Server。如下圖,Metrics Server 的實作,主要用到一個服務和一個 pod。
而上圖中最後的 apiservice,則是把“外挂”和 API Server 聯系起來的機制。下圖可以看到這個 apiservice 詳細定義。它包括 API 分組/版本,以及實作了 Metrics Server 的服務名。有了這些資訊,API Server 就能把針對 metrics.k8s.io/v1beta1 的調用,轉發給 Metrics Server。
節點與Pod之間的通信
經過簡單的測試,我們發現,這個問題實際上是 API server 和 metrics server pod 之間的通信問題。在阿裡雲 K8s 叢集環境裡,API Server 使用的是主機網絡,即 ECS 的網絡,而 Metrics Server 使用的是 Pod 網絡。這兩者之間的通信,依賴于 VPC 路由表的轉發。
以上圖為例,如果 API Server 運作在 Node A 上,那它的 IP 位址就是 192.168.0.193。假設 Metrics Server 的 IP 是 172.16.1.12,那麼從 API Server 到 Metrics Server 的網絡連接配接,必須要通過 VPC 路由表第二條路由規則的轉發。
檢查叢集 VPC 路由表,發現指向 Metrics Server 所在節點的路由表項缺失,是以 API server 和 Metrics Server 之間的通信出了問題。
Route Controller 為什麼不工作?
為了維持叢集 VPC 路由表項的正确性,阿裡雲在 Cloud Controller Manager 内部實作了 Route Controller。這個 Controller 在時刻監聽着叢集節點狀态,以及 VPC 路由表狀态。當發現路由表項缺失的時候,它會自動把缺失的路由表項填寫回去。
現在的情況,顯然和預期不一緻,Route Controller 顯然沒有正常工作。這個可以通過檢視 Cloud Controller Manager 日志來确認。在日志中,我們發現,Route Controller 在使用叢集 VPC id 去查找 VPC 執行個體的時候,沒有辦法擷取到這個執行個體的資訊。
但是叢集還在,ECS 還在,是以 VPC 不可能不在了。這一點我們可以通過 VPC id 在 VPC 控制台确認。那下邊的問題,就是為什麼 Cloud Controller Manager 沒有辦法擷取到這個 VPC 的資訊呢?
叢集節點通路雲資源
Cloud Controller Manager 擷取 VPC 資訊,是通過阿裡雲開放 API 來實作的。這基本上等于從雲上一台 ECS 内部,去擷取一個 VPC 執行個體的資訊,而這需要 ECS 有足夠的權限。目前的正常做法是,給 ECS 伺服器授予 RAM 角色,同時給對應的 RAM 角色綁定相應的角色授權。
如果叢集元件,以其所在節點的身份,不能擷取雲資源的資訊,那基本上有兩種可能性。一是 ECS 沒有綁定正确的 RAM 角色;二是 RAM 角色綁定的 RAM 角色授權沒有定義正确的授權規則。檢查節點的 RAM 角色,以及 RAM 角色所管理的授權,我們發現,針對 vpc 的授權政策被改掉了。
當我們把 Effect 修改成 Allow 之後,沒多久,所有的 Terminating 狀态的 namespace 全部都消失了。
問題大圖
總體來說,這個問題與 K8s 叢集的 6 個元件有關系,分别是 API Server 及其擴充 Metrics Server,Namespace Controller 和 Route Controller,以及 VPC 路由表和 RAM 角色授權。
通過分析前三個元件的行為,我們定位到,叢集網絡問題導緻了 API Server 無法連接配接到 Metrics Server;通過排查後三個元件,我們發現導緻問題的根本原因是 VPC 路由表被删除且 RAM 角色授權政策被改動。
後記
K8s 叢集 Namespace 删除不掉的問題,是線上比較常見的一個問題。這個問題看起來無關痛癢,但實際上不僅複雜,而且意味着叢集重要功能的缺失。這篇文章全面分析了一個這樣的問題,其中的排查方法和原理,希望對大家排查類似問題有一定的幫助。
“ 阿裡巴巴雲原生 關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術圈。”