Kubernetes API 伺服器作為叢集的核心,處理所有來自客戶端的請求,包含標準 RESTful 請求和特殊請求模式,例如 /proxy、/exec、/attach 和 /logs。理解 API 請求的生命週期對於 Kubernetes 的使用至關重要。每個請求都需經過身份驗證、RBAC 授權、准入控制和驗證等階段。API 伺服器支援 JSON、YAML 和 Protocol Buffers 等多種編碼格式,並採用樂觀平行控制機制來處理平行更新,利用資源版本號來避免資料衝突。此外,API 伺服器還支援監控 API 和 WebSocket 協定,以實作更豐富的功能。
Kubernetes API 請求生命週期詳解
當需要刪除一個請求時,會對資源路徑傳送 HTTP DELETE 請求(例如:/api/v1/namespaces/default/pods/foo)。需要注意的是,這種變更是永久性的——一旦發出 HTTP 請求,資源就會被刪除。
大多數請求的內容型別通常是根據文字的 JSON(application/json),但近期版本的 Kubernetes 也支援 Protocol Buffers 二進位編碼。一般來說,JSON 更適合人類可讀和除錯客戶端與伺服器之間的網路流量,但它的冗餘度較高,解析成本也較高。Protocol Buffers 則更難用常見工具(如 curl)進行內省,但能夠實作更高的效能和吞吐量。
除了這些標準請求外,許多請求使用 WebSocket 協定來實作客戶端和伺服器之間的串流會話。例如 exec 和 attach 命令等。這類別請求將在後續章節中詳細描述。
請求生命週期解析
為了更好地理解 API 伺服器如何處理不同請求,我們將逐步解析單個請求到 API 伺服器的處理過程。
身份驗證
請求處理的第一階段是身份驗證,用於確定與請求相關聯的身份。API 伺服器支援多種不同的身份驗證模式,包括客戶端憑證、Bearer Token 和 HTTP 基本身份驗證。一般來說,建議使用客戶端憑證或 Bearer Token 進行身份驗證;不建議使用 HTTP 基本身份驗證。
除了這些本地身份驗證方法外,身份驗證機制也是可插拔的,有多種使用遠端身份提供者的外掛實作可供選擇。這些外掛包括對 OpenID Connect(OIDC)協定的支援,以及 Azure Active Directory 的支援。這些身份驗證外掛被編譯到 API 伺服器和客戶端程式函式庫中。這意味著您可能需要確保命令列工具和 API 伺服器的版本大致相同,或支援相同的身份驗證方法。
API 伺服器還支援根據遠端 Webhook 的身份驗證組態,將身份驗證決策委託給外部伺服器,並透過 Bearer Token 轉發。外部伺服器驗證來自終端使用者的 Bearer Token,並將身份驗證資訊傳回給 API 伺服器。
鑒於這在確保伺服器安全方面的重要性,相關內容將在後續章節中探討。
RBAC/授權
在 API 伺服器確定了請求的身份後,接下來會進行授權。每個對 Kubernetes 的請求都遵循傳統的 RBAC 模型。要存取請求,身份必須具有與請求相關聯的適當角色。
Kubernetes RBAC 是一個豐富而複雜的主題,我們將在專門的章節中詳細介紹其運作原理。在本 API 伺服器摘要中,當處理請求時,API 伺服器會判斷與請求相關聯的身份是否可以存取請求中的動詞和 HTTP 路徑的組合。如果請求的身份具有適當的角色,則允許繼續。否則,將傳回 HTTP 403 回應。
准入控制
在請求經過身份驗證和授權後,將進入准入控制階段。身份驗證和 RBAC 決定了請求是否被允許,這是根據請求的 HTTP 特性(標頭、方法和路徑)。准入控制則決定了請求是否格式正確,並可能在處理之前對請求進行修改。准入控制定義了一個可插拔介面:apply(request): (transformedRequest, error)。
如果任何准入控制器發現錯誤,請求將被拒絕。如果請求被接受,則使用轉換後的請求代替初始請求。准入控制器按順序呼叫,每個控制器接收前一個控制器的輸出。
由於准入控制是一種通用、可插拔的機制,因此它被用於 API 伺服器中的多種不同功能。例如,它用於為物件新增預設值,也可以用於強制執行政策(例如,要求所有物件具有特定的標籤)。此外,它還可以用於向每個 Pod 中注入額外的容器。服務網格 Istio 就採用這種方法透明地注入其 Sidecar 容器。
准入控制器非常通用,可以透過根據 Webhook 的准入控制動態新增到 API 伺服器。
驗證
請求驗證發生在准入控制之後,不過也可以作為准入控制的一部分實作,尤其是對於外部根據 Webhook 的驗證。此外,驗證僅針對單個物件進行。如果需要更廣泛的叢集狀態知識,則必須將其實作為准入控制器。
此圖示展示了 Kubernetes API 請求的生命週期,從請求進入到最終處理的全過程。
內容解密:
此圖表呈現了 Kubernetes API 請求處理的完整流程。首先,請求進入系統並經過身份驗證階段,以確認使用者的身份。接著,系統進行 RBAC 授權檢查,以確保使用者具有執行該操作的許可權。透過授權後,系統會進行准入控制,以檢查和修改請求內容。最後,系統對請求進行驗證,以確保其符合預期格式和邏輯。透過所有這些檢查後,系統才會正式處理該請求。這樣的多層檢查機制保證了 Kubernetes 系統的安全性和穩定性。
Kubernetes API 伺服器的請求管理
Kubernetes API 伺服器是整個叢集的核心,負責處理所有來自客戶端的請求。這些請求包括標準的 RESTful 請求和一些特殊的請求模式。
請求驗證
請求驗證確保包含在請求中的特定資源是有效的。例如,它確保 Service 物件的名稱符合 DNS 名稱的規則,因為最終 Service 的名稱將被程式設計到 Kubernetes Service 發現 DNS 伺服器中。通常,驗證是根據資源型別定義的自訂程式碼。
特殊請求
除了標準的 RESTful 請求之外,API 伺服器還有一些特殊的請求模式,為客戶端提供了擴充套件的功能:
/proxy:用於在客戶端和叢集內執行的容器和服務之間進行網路流量轉發。/exec:用於在容器內執行命令。/attach:用於附加到容器內的程式。/logs:用於取得容器的日誌。
日誌操作
日誌操作是最容易理解的串流請求。客戶端透過在特定的 Pod 路徑後附加 /logs 來請求 Pod 的日誌,並指定容器名稱作為 HTTP 查詢引數。如果客戶端請求跟隨日誌(透過指定 follow 查詢引數),API 伺服器會保持 HTTP 回應開啟,並將新的日誌寫入 HTTP 回應中。
WebSocket 協定
Kubernetes API 伺服器支援 WebSocket 協定,用於雙向串流資料。它還引入了一個額外的多路復用串流協定,用於在單一 WebSocket 會話中服務多個獨立的位元組流。
多路復用串流協定
每條串流都被分配一個從 0 到 255 的編號,用於輸入和輸出。每個透過 WebSocket 協定傳送的幀,第一個位元組是串流編號,其餘的是在該串流上傳輸的資料。
代理端點
/proxy 端點用於在客戶端和叢集內執行的容器和服務之間進行網路流量轉發,而無需將這些端點外部暴露。為了串流這些 TCP 會話,協定稍微複雜一些,除了多路復用各種串流之外,串流的前兩個位元組(在串流編號之後)是被轉發的埠號。
監控操作
API 伺服器支援監控 API,用於監控路徑的變化。透過建立一個監控連線,客戶端可以獲得低延遲的更新,而無需輪詢。
樂觀平行更新
API 伺服器支援樂觀平行更新,用於檢測並發寫入並拒絕後面的寫入操作。這種機制避免了使用鎖(悲觀平行控制),而是檢測並發寫入的衝突。
讀取/更新/寫入競爭條件
許多 API 伺服器客戶端的操作涉及三個步驟:
- 從 API 伺服器讀取資料。
- 在記憶體中更新資料。
- 將資料寫回 API 伺服器。
此圖示說明瞭客戶端與 API 伺服器之間的互動過程。
內容解密:
此圖表展示了客戶端如何與 Kubernetes API 伺服器互動,包括讀取、更新和寫回資料的過程。這個過程涉及客戶端發起請求、API 伺服器處理請求並傳回結果。客戶端根據傳回的資料進行更新,然後將更新後的資料寫回 API 伺服器。API 伺服器驗證更新的資料,確保資料的一致性和正確性。
程式碼例項
import requests
# 取得 Pod 的日誌
def get_pod_logs(namespace, pod_name, container_name):
url = f"/api/v1/namespaces/{namespace}/pods/{pod_name}/log"
params = {"container": container_name, "follow": True}
response = requests.get(url, params=params, stream=True)
for line in response.iter_lines():
print(line.decode("utf-8"))
# 使用範例
get_pod_logs("default", "some-pod", "some-container")
內容解密:
此段程式碼展示瞭如何使用 Python 的 requests 函式庫來取得 Kubernetes Pod 的日誌。其中,get_pod_logs 函式接收名稱空間、Pod 名稱和容器名稱作為引數。它構建了一個 URL 用於請求 Pod 的日誌,並設定查詢引數以跟隨日誌。該函式使用 stream=True 引數來保持連線開啟,並迭代列印日誌行。這個例子演示瞭如何使用 Kubernetes API 取得容器日誌,並對日誌進行即時處理。
Kubernetes API 伺服器的平行控制與內部實作
在分散式系統中,多個伺服器同時對同一個物件進行讀取、更新和寫入操作可能會導致資料不一致的問題。以下是一個典型的平行存取衝突場景:
- 伺服器 A 讀取物件 O。
- 伺服器 B 讀取物件 O。
- 伺服器 A 在客戶端記憶體中更新物件 O。
- 伺服器 B 在客戶端記憶體中更新物件 O。
- 伺服器 A 寫入物件 O。
- 伺服器 B 寫入物件 O。
在這個過程中,伺服器 A 的更新被伺服器 B 的更新覆寫,從而導致資料遺失。
為瞭解決這個問題,Kubernetes API 伺服器採用了樂觀平行控制(Optimistic Concurrency)機制。這個機製假設多數情況下並不會發生衝突,只有在真正發生衝突時才會進行處理。每個物件在被讀取時都會傳回其資料和資源版本(Resource Version)。當客戶端嘗試寫入物件時,如果指定的資源版本與目前的版本不符,寫入操作將會失敗,並傳回 HTTP 錯誤碼 409(衝突)。
樂觀平行控制的運作流程
- 伺服器 A 讀取物件 O,版本為 v1。
- 伺服器 B 讀取物件 O,版本為 v1。
- 伺服器 A 在客戶端記憶體中更新物件 O,版本仍為 v1。
- 伺服器 B 在客戶端記憶體中更新物件 O,版本仍為 v1。
- 伺服器 A 寫入物件 O,版本為 v1,寫入成功。
- 伺服器 B 寫入物件 O,版本為 v1,但目前版本已變為 v2,因此傳回 HTTP 錯誤碼 409。
內容解密:
- 樂觀平行控制 的核心思想是假設衝突很少發生,從而避免了悲觀鎖帶來的效能問題。
- 資源版本 是實作樂觀平行控制的關鍵,每次物件更新都會導致版本號的變更。
- 當發生 HTTP 錯誤碼 409 時,客戶端需要重新讀取最新的物件版本並重試寫入操作。
API 請求的多種編碼格式
Kubernetes API 伺服器支援多種請求編碼格式,包括 JSON、YAML 和 Protocol Buffers。其中,JSON 是預設的編碼格式,而 YAML 和 Protocol Buffers 可以透過 HTTP 請求頭中的 Content-Type 欄位指定。
JSON 編碼
JSON 是最常用的資料交換格式,易於閱讀和解析。
YAML 編碼
YAML 是另一種常見的資料序列化格式,比 JSON 更具可讀性。雖然在與 API 伺服器通訊時使用 YAML 的優勢不大,但在某些手動操作(如使用 curl 工具傳送檔案)時,YAML 可以提供更友善的體驗。
Protocol Buffers 編碼
Protocol Buffers 是 Google 開發的一種高效的二進位資料序列化格式。使用 Protocol Buffers 可以提高 API 請求的效率和吞吐量。不過,由於其二進位特性,Protocol Buffers 在偵錯和分析時可能較為困難。
常見的 HTTP 回應碼
Kubernetes API 伺服器的回應遵循 HTTP 標準回應碼。以下是一些常見的回應碼及其意義:
- 202 Accepted:非同步請求已接受,將非同步處理請求。
- 400 Bad Request:請求格式錯誤或無法被伺服器理解。
- 401 Unauthorized:請求未經授權。
- 403 Forbidden:請求被理解,但拒絕執行。
- 409 Conflict:請求與目前資源狀態衝突,通常由於平行更新導致。
- 422 Unprocessable Entity:請求格式正確,但因語意錯誤而無法處理。
內容解密:
- HTTP 回應碼 提供了一個標準化的方式來表示請求的處理結果。
- 正確理解這些回應碼有助於開發者更好地處理 API 請求結果。
API 伺服器的內部實作
Kubernetes API 伺服器內部包含了一些控制迴圈,用於實作特定的功能,例如自定義資源定義(CRD)的管理。
CRD 控制迴圈
CRD 控制迴圈負責註冊和移除自定義資源定義所對應的 HTTP 路徑。當一個 CRD 被建立時,控制迴圈會為其註冊相應的 HTTP 路徑;當 CRD 被刪除時,控制迴圈會先標記路徑為無效,接著刪除相關資料,最後移除 HTTP 路徑。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Kubernetes API 請求生命週期與偵錯
package "Kubernetes Cluster" {
package "Control Plane" {
component [API Server] as api
component [Controller Manager] as cm
component [Scheduler] as sched
database [etcd] as etcd
}
package "Worker Nodes" {
component [Kubelet] as kubelet
component [Kube-proxy] as proxy
package "Pods" {
component [Container 1] as c1
component [Container 2] as c2
}
}
}
api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2
note right of api
核心 API 入口
所有操作經由此處
end note
@enduml此圖示展示了 CRD 控制迴圈的基本流程。
內容解密:
- CRD 控制迴圈確保了自定義資源定義與其對應的 HTTP 路徑能夠動態地被管理。
- 當 CRD 被刪除時,相關資料的清理是為了避免舊資料在未來 CRD 被重新建立時造成混淆。
除錯 API 伺服器
除錯 Kubernetes API 伺服器的一個主要方法是檢視其輸出的日誌。透過分析日誌,可以瞭解 API 請求的處理過程以及可能出現的問題。
日誌分析
日誌記錄了 API 伺服器的執行細節,包括請求處理、日誌級別等資訊。透過檢視這些日誌,可以診斷問題所在,並進行相應的調整。
程式碼層面的除錯
對於更複雜的問題,可能需要深入到程式碼層面進行除錯。這通常涉及到使用除錯工具來逐步執行程式碼、檢查變數狀態等,以找出問題根源。
Kubernetes API 伺服器偵錯
Kubernetes API 伺服器是整個叢集的核心服務,負責處理所有與叢集相關的請求。瞭解如何偵錯 API 伺服器對於維護叢集的穩定性和可靠性至關重要。本章節將探討 API 伺服器的偵錯方法,包括日誌分析、額外日誌的啟用以及使用 kubectl 偵錯請求。
基本日誌
預設情況下,API 伺服器會記錄所有傳送到伺服器的請求。這些日誌包括客戶端的 IP 位址、請求路徑以及伺服器傳回的程式碼。如果發生意外的錯誤導致伺服器當機,伺服器也會捕捉到這個錯誤,傳回 500 錯誤,並記錄該錯誤。
I0803 19:59:19.929302 1 trace.go:76] Trace[1449222206]: "Create /api/v1/namespaces/default/events" (started: 2018-08-03 19:59:19.001777279 +0000 UTC m=+25.386403121) (total time: 927.484579ms): Trace[1449222206]: [927.401927ms] [927.279642ms] Object stored in database
I0803 19:59:20.402215 1 controller.go:537] quota admission added evaluator for: { namespaces}
內容解密:
- 日誌格式:日誌以時間戳記開頭,後面跟著行號和日誌訊息本身。
- Trace 資訊:顯示了請求的建立時間、總耗時以及物件儲存在資料函式庫中的時間。
- Quota Admission:顯示了 quota admission 新增了評估器,用於管理名稱空間的資源配額。
稽核日誌
稽核日誌旨在使伺服器管理員能夠法醫學式地還原伺服器的狀態以及導致當前資料狀態的客戶端互動歷史。例如,它可以幫助使用者回答諸如“為什麼那個 ReplicaSet 被縮放到 100?”或“誰刪除了那個 Pod?”等問題。
稽核日誌具有可插拔的後端,可以寫入檔案或 webhook。無論哪種情況,記錄的資料都是一個結構化的 JSON 物件,屬於 audit.k8s.io API 群組中的事件型別。
稽核日誌組態
稽核日誌可以透過 audit.k8s.io API 群組中的策略物件進行組態。此策略允許您指定稽核事件被記錄到稽核日誌中的規則。
啟用額外日誌
Kubernetes 使用 github.com/golang/glog 分級日誌套件進行日誌記錄。使用 --v 旗標可以調整 API 伺服器的日誌詳細程度。一般來說,Kubernetes 專案將日誌詳細程度級別 2 (--v=2) 設定為合理的預設值,用於記錄相關但不過於冗餘的訊息。
# 調整日誌詳細程度
--v=2
內容解密:
--v旗標:用於調整日誌詳細程度。- 日誌級別:級別 2 是預設值,用於記錄相關訊息。
- 效能影響:過高的日誌詳細程度可能會影響效能,因此不建議在生產環境中執行過於冗餘的日誌級別。
使用 kubectl 偵錯請求
除了透過日誌偵錯 API 伺服器之外,還可以使用 kubectl 命令列工具偵錯與 API 伺服器的互動。與 API 伺服器一樣,kubectl 也使用 github.com/golang/glog 套件進行日誌記錄,並支援 --v 詳細程度旗標。
# 設定詳細程度為 10 以開啟最大冗餘日誌
kubectl --v=10 get pods
內容解密:
--v=10:開啟最大冗餘日誌,記錄所有請求以及嘗試列印可重複請求的curl命令。kubectl proxy:建立一個代理伺服器在本地主機上,自動提供身份驗證和授權憑證。- 直接操作 API 伺服器:透過
kubectl proxy可以使用curl命令直接對 API 伺服器傳送請求。
Kubernetes 排程器概述
當一個 Pod 被首次建立時,它通常沒有 nodeName 欄位。Kubernetes 排程器不斷掃描 API 伺服器(透過監控請求)以查詢沒有 nodeName 的 Pod,這些 Pod 是符合排程條件的。然後,排程器為 Pod 選擇一個合適的節點,並更新 Pod 定義中的 nodeName。一旦 nodeName 被設定,該節點上的 kubelet 就會被通知到 Pod 的存在(再次透過監控請求),並開始在該節點上執行該 Pod。
// 示例程式碼:排程器的基本邏輯
func schedulePod(pod *v1.Pod) {
// 選擇合適的節點
node := selectNode(pod)
// 更新 Pod 的 nodeName
pod.Spec.NodeName = node.Name
// 更新 Pod 定義
updatePod(pod)
}
內容解密:
schedulePod函式:代表排程器的基本邏輯,用於為 Pod 選擇合適的節點。selectNode函式:根據特定策略選擇合適的節點。updatePod函式:更新 Pod 定義中的nodeName,以便 kubelet 可以開始執行該 Pod。