创建 tls 客户端 凭据时发生严重错误。内部错误状态为 10013_kubectl 创建 Pod 背后到底发生了什么?...
kubectl 創(chuàng)建 Pod 背后到底發(fā)生了什么?
想象一下,如果我想將 nginx 部署到 Kubernetes 集群,我可能會在終端中輸入類似這樣的命令:
$ kubectl run --image=nginx --replicas=3
然后回車。幾秒鐘后,你就會看到三個 nginx pod 分布在所有的工作節(jié)點上。這一切就像變魔術一樣,但你并不知道這一切的背后究竟發(fā)生了什么事情。
Kubernetes 的神奇之處在于:它可以通過用戶友好的 API 來處理跨基礎架構的 deployments,而背后的復雜性被隱藏在簡單的抽象中。但為了充分理解它為我們提供的價值,我們需要理解它的內(nèi)部原理。
本指南將引導您理解從 client 到 Kubelet 的請求的完整生命周期,必要時會通過源代碼來說明背后發(fā)生了什么。
這是一份可以在線修改的文檔,如果你發(fā)現(xiàn)有什么可以改進或重寫的,歡迎提供幫助!
1. kubectl
驗證和生成器
當敲下回車鍵以后,kubectl 首先會執(zhí)行一些客戶端驗證操作,以確保不合法的請求(例如,創(chuàng)建不支持的資源或使用格式錯誤的鏡像名稱)將會快速失敗,也不會發(fā)送給 kube-apiserver。通過減少不必要的負載來提高系統(tǒng)性能。
驗證通過之后, kubectl 開始將發(fā)送給 kube-apiserver 的 HTTP 請求進行封裝。kube-apiserver 與 etcd 進行通信,所有嘗試訪問或更改 Kubernetes 系統(tǒng)狀態(tài)的請求都會通過 kube-apiserver 進行,kubectl 也不例外。kubectl 使用生成器(generators)來構造 HTTP 請求。生成器是一個用來處理序列化的抽象概念。
通過 kubectl run 不僅可以運行 deployment,還可以通過指定參數(shù) --generator 來部署其他多種資源類型。如果沒有指定 --generator 參數(shù)的值,kubectl 將會自動判斷資源的類型。
例如,帶有參數(shù) --restart-policy=Always 的資源將被部署為 Deployment,而帶有參數(shù) --restart-policy=Never 的資源將被部署為 Pod。同時 kubectl 也會檢查是否需要觸發(fā)其他操作,例如記錄命令(用來進行回滾或?qū)徲?#xff09;。
在 kubectl 判斷出要創(chuàng)建一個 Deployment 后,它將使用 DeploymentV1Beta1 生成器從我們提供的參數(shù)中生成一個運行時對象。
API 版本協(xié)商與 API 組
為了更容易地消除字段或者重新組織資源結構,Kubernetes 支持多個 API 版本,每個版本都在不同的 API 路徑下,例如 /api/v1 或者 /apis/extensions/v1beta1。不同的 API 版本表明不同的穩(wěn)定性和支持級別,更詳細的描述可以參考 Kubernetes API 概述。
API 組旨在對類似資源進行分類,以便使得 Kubernetes API 更容易擴展。API 的組名在 REST 路徑或者序列化對象的 apiVersion 字段中指定。例如,Deployment 的 API 組名是 apps,最新的 API 版本是 v1beta2,這就是為什么你要在 Deployment manifests 頂部輸入 apiVersion: apps/v1beta2。
kubectl 在生成運行時對象后,開始為它找到適當?shù)?API 組和 API 版本,然后組裝成一個版本化客戶端,該客戶端知道資源的各種 REST 語義。該階段被稱為版本協(xié)商,kubectl 會掃描 remote API 上的 /apis 路徑來檢索所有可能的 API 組。由于 kube-apiserver 在 /apis 路徑上公開了 OpenAPI 格式的規(guī)范文檔, 因此客戶端很容易找到合適的 API。
為了提高性能,kubectl 將 OpenAPI 規(guī)范緩存到了 ~/.kube/cache 目錄。如果你想了解 API 發(fā)現(xiàn)的過程,請嘗試刪除該目錄并在運行 kubectl 命令時將 -v 參數(shù)的值設為最大值,然后你將會看到所有試圖找到這些 API 版本的HTTP 請求。參考 kubectl 備忘單。
最后一步才是真正地發(fā)送 HTTP 請求。一旦請求發(fā)送之后獲得成功的響應,kubectl 將會根據(jù)所需的輸出格式打印 success message。
客戶端身份認證
在發(fā)送 HTTP 請求之前還要進行客戶端認證,這是之前沒有提到的,現(xiàn)在可以來看一下。
為了能夠成功發(fā)送請求,kubectl 需要先進行身份認證。用戶憑證保存在 kubeconfig 文件中,kubectl 通過以下順序來找到 kubeconfig 文件:
- 如果提供了 --kubeconfig 參數(shù), kubectl 就使用 –kubeconfig 參數(shù)提供的 kubeconfig 文件。
- 如果沒有提供 –kubeconfig 參數(shù),但設置了環(huán)境變量 $KUBECONFIG,則使用該環(huán)境變量提供的 kubeconfig 文件。
- 如果 –kubeconfig 參數(shù)和環(huán)境變量 $KUBECONFIG 都沒有提供,kubectl 就使用默認的 kubeconfig 文件 $HOME/.kube/config。
解析完 kubeconfig 文件后,kubectl 會確定當前要使用的上下文、當前指向的群集以及與當前用戶關聯(lián)的任何認證信息。如果用戶提供了額外的參數(shù)(例如 –username),則優(yōu)先使用這些參數(shù)覆蓋 kubeconfig 中指定的值。一旦拿到這些信息之后, kubectl 就會把這些信息填充到將要發(fā)送的 HTTP 請求頭中:
- x509 證書使用 tls.TLSConfig 發(fā)送(包括 CA 證書)。
- bearer tokens 在 HTTP 請求頭 Authorization 中發(fā)送。
- 用戶名和密碼通過 HTTP 基本認證發(fā)送。
- OpenID 認證過程是由用戶事先手動處理的,產(chǎn)生一個像 bearer token 一樣被發(fā)送的 token。
2. kube-apiserver
認證
現(xiàn)在我們的請求已經(jīng)發(fā)送成功了,接下來將會發(fā)生什么?這時候就該 kube-apiserver 閃亮登場了!kube-apiserver 是客戶端和系統(tǒng)組件用來保存和檢索集群狀態(tài)的主要接口。為了執(zhí)行相應的功能,kube-apiserver 需要能夠驗證請求者是合法的,這個過程被稱為認證。
那么 apiserver 如何對請求進行認證呢?當 kube-apiserver 第一次啟動時,它會查看用戶提供的所有 CLI 參數(shù),并組合成一個合適的令牌列表。
舉個例子:如果提供了 --client-ca-file 參數(shù),則會將 x509 客戶端證書認證添加到令牌列表中;如果提供了 --token-auth-file 參數(shù),則會將 breaer token 添加到令牌列表中。
每次收到請求時,apiserver 都會通過令牌鏈進行認證,直到某一個認證成功為止:
- x509 處理程序?qū)Ⅱ炞C HTTP 請求是否是由 CA 根證書簽名的 TLS 密鑰進行編碼的。
- bearer token 處理程序?qū)Ⅱ炞C --token-auth-file 參數(shù)提供的 token 文件是否存在。
- 基本認證處理程序確保 HTTP 請求的基本認證憑證與本地的狀態(tài)匹配。
如果認證失敗,則請求失敗并返回相應的錯誤信息;如果驗證成功,則將請求中的 Authorization 請求頭刪除,并將用戶信息添加到其上下文中。這給后續(xù)的授權和準入控制器提供了訪問之前建立的用戶身份的能力。
授權
OK,現(xiàn)在請求已經(jīng)發(fā)送,并且 kube-apiserver 已經(jīng)成功驗證我們是誰,終于解脫了!
然而事情并沒有結束,雖然我們已經(jīng)證明了我們是合法的,但我們有權執(zhí)行此操作嗎?畢竟身份和權限不是一回事。為了進行后續(xù)的操作,kube-apiserver 還要對用戶進行授權。
kube-apiserver 處理授權的方式與處理身份驗證的方式相似:通過 kube-apiserver 的啟動參數(shù) --authorization_mode 參數(shù)設置。它將組合一系列授權者,這些授權者將針對每個傳入的請求進行授權。如果所有授權者都拒絕該請求,則該請求會被禁止響應并且不會再繼續(xù)響應。如果某個授權者批準了該請求,則請求繼續(xù)。
kube-apiserver 目前支持以下幾種授權方法:
- webhook: 它與集群外的 HTTP(S) 服務交互。
- ABAC: 它執(zhí)行靜態(tài)文件中定義的策略。
- RBAC: 它使用 rbac.authorization.k8s.io API Group實現(xiàn)授權決策,允許管理員通過 Kubernetes API 動態(tài)配置策略。
- Node: 它確保 kubelet 只能訪問自己節(jié)點上的資源。
準入控制
突破了之前所說的認證和授權兩道關口之后,客戶端的調(diào)用請求就能夠得到 API Server 的真正響應了嗎?答案是:不能!
從 kube-apiserver 的角度來看,它已經(jīng)驗證了我們的身份并且賦予了相應的權限允許我們繼續(xù),但對于 Kubernetes 而言,其他組件對于應不應該允許發(fā)生的事情還是很有意見的。所以這個請求還需要通過 Admission Controller 所控制的一個 準入控制鏈 的層層考驗,官方標準的 “關卡” 有近十個之多,而且還能自定義擴展!
雖然授權的重點是回答用戶是否有權限,但準入控制器會攔截請求以確保它符合集群的更廣泛的期望和規(guī)則。它們是資源對象保存到 etcd 之前的最后一個堡壘,封裝了一系列額外的檢查以確保操作不會產(chǎn)生意外或負面結果。不同于授權和認證只關心請求的用戶和操作,準入控制還處理請求的內(nèi)容,并且僅對創(chuàng)建、更新、刪除或連接(如代理)等有效,而對讀操作無效。
注意
準入控制器的工作方式與授權者和驗證者的工作方式類似,但有一點區(qū)別:與驗證鏈和授權鏈不同,如果某個準入控制器檢查不通過,則整個鏈會中斷,整個請求將立即被拒絕并且返回一個錯誤給終端用戶。
準入控制器設計的重點在于提高可擴展性,某個控制器都作為一個插件存儲在 plugin/pkg/admission 目錄中,并且與某一個接口相匹配,最后被編譯到 kube-apiserver 二進制文件中。
大部分準入控制器都比較容易理解,接下來著重介紹 SecurityContextDeny、ResourceQuota 及 LimitRanger 這三個準入控制器。
- SecurityContextDeny 該插件將禁止創(chuàng)建設置了 Security Context 的 Pod。
- ResourceQuota 不僅能限制某個 Namespace 中創(chuàng)建資源的數(shù)量,而且能限制某個 Namespace 中被 Pod 所請求的資源總量。該準入控制器和資源對象 ResourceQuota 一起實現(xiàn)了資源配額管理。
- LimitRanger 作用類似于上面的 ResourceQuota 控制器,針對 Namespace 資源的每個個體(Pod 與 Container 等)的資源配額。該插件和資源對象 LimitRange 一起實現(xiàn)資源配額管理。
3. etcd
到現(xiàn)在為止,Kubernetes 已經(jīng)對該客戶端的調(diào)用請求進行了全面徹底地審查,并且已經(jīng)驗證通過,運行它進入下一個環(huán)節(jié)。下一步 kube-apiserver 將對 HTTP 請求進行反序列化,然后利用得到的結果構建運行時對象(有點像 kubectl 生成器的逆過程),并保存到 etcd 中。下面我們將這個過程分解一下。
當收到請求時,kube-apiserver 是如何知道它該怎么做的呢?事實上,在客戶端發(fā)送調(diào)用請求之前就已經(jīng)產(chǎn)生了一系列非常復雜的流程。我們就從 kube-apiserver 二進制文件首次運行開始分析吧:
現(xiàn)在 kube-apiserver 已經(jīng)知道了所有的路由及其對應的 REST 路徑,以便在請求匹配時知道調(diào)用哪些處理器和鍵值存儲。多么機智的設計!現(xiàn)在假設客戶端的 HTTP 請求已經(jīng)被 kube-apiserver 收到了:
原來 apiserver 做了這么多的工作,以前竟然沒有發(fā)現(xiàn)呢!到目前為止,我們創(chuàng)建的 Deployment 資源已經(jīng)保存到了 etcd 中,但 apiserver 仍然看不到它。
4. 初始化
在一個資源對象被持久化到數(shù)據(jù)存儲之后,apiserver 還無法完全看到或調(diào)度它,在此之前還要執(zhí)行一系列Initializers。Initializers是一種與資源類型相關聯(lián)的控制器,它會在資源對外可用之前執(zhí)行某些邏輯。如果某個資源類型沒有Initializers,就會跳過此初始化步驟立即使資源對外可見。
正如大佬的博客指出的那樣,Initializers是一個強大的功能,因為它允許我們執(zhí)行通用引導操作。例如:
- 將代理邊車容器注入到暴露 80 端口的 Pod 中,或者加上特定的 annotation。
- 將保存著測試證書的 volume 注入到特定命名空間的所有 Pod 中。
- 如果 Secret 中的密碼小于 20 個字符,就組織其創(chuàng)建。
initializerConfiguration 資源對象允許你聲明某些資源類型應該運行哪些Initializers。如果你想每創(chuàng)建一個 Pod 時就運行一個自定義Initializers,你可以這樣做:
通過該配置創(chuàng)建資源對象 InitializerConfiguration 之后,就會在每個 Pod 的 metadata.initializers.pending 字段中添加 custom-pod-initializer 字段。該初始化控制器會定期掃描新的 Pod,一旦在 Pod 的 pending 字段中檢測到自己的名稱,就會執(zhí)行其邏輯,執(zhí)行完邏輯之后就會將 pending 字段下的自己的名稱刪除。
只有在 pending 字段下的列表中的第一個Initializers可以對資源進行操作,當所有的Initializers執(zhí)行完成,并且 pending 字段為空時,該對象就會被認為初始化成功。
你可能會注意到一個問題:如果 kube-apiserver 不能顯示這些資源,那么用戶級控制器是如何處理資源的呢?
為了解決這個問題,kube-apiserver 暴露了一個 ?includeUninitialized 查詢參數(shù),它會返回所有的資源對象(包括未初始化的)。
5. 控制循環(huán)
Deployments controller
到了這個階段,我們的 Deployment 記錄已經(jīng)保存在 etcd 中,并且所有的初始化邏輯都執(zhí)行完成,接下來的階段將會涉及到該資源所依賴的拓撲結構。在 Kubernetes 中,Deployment 實際上只是一系列 Replicaset 的集合,而 Replicaset 是一系列 Pod 的集合。那么 Kubernetes 是如何從一個 HTTP 請求按照層級結構依次創(chuàng)建這些資源的呢?其實這些工作都是由 Kubernetes 內(nèi)置的 Controller(控制器) 來完成的。
Kubernetes 在整個系統(tǒng)中使用了大量的 Controller,Controller 是一個用于將系統(tǒng)狀態(tài)從“當前狀態(tài)”修正到“期望狀態(tài)”的異步腳本。所有 Controller 都通過 kube-controller-manager 組件并行運行,每種 Controller 都負責一種具體的控制流程。首先介紹一下 Deployment Controller:
將 Deployment 記錄存儲到 etcd 并初始化后,就可以通過 kube-apiserver 使其可見,然后 Deployment Controller 就會檢測到它(它的工作就是負責監(jiān)聽 Deployment 記錄的更改)。在我們的例子中,控制器通過一個 Informer 注冊一個創(chuàng)建事件的特定回調(diào)函數(shù)(更多信息參加下文)。
當 Deployment 第一次對外可見時,該 Controller 就會將該資源對象添加到內(nèi)部工作隊列,然后開始處理這個資源對象:
通過使用標簽選擇器查詢 kube-apiserver 來檢查該 Deployment 是否有與其關聯(lián)的 ReplicaSet 或 Pod 記錄。有趣的是,這個同步過程是狀態(tài)不可知的,它核對新記錄與核對已經(jīng)存在的記錄采用的是相同的方式。
在意識到?jīng)]有與其關聯(lián)的 ReplicaSet 或 Pod 記錄后,Deployment Controller 就會開始執(zhí)行彈性伸縮流程:
創(chuàng)建 ReplicaSet 資源,為其分配一個標簽選擇器并將其版本號設置為 1。ReplicaSet 的 PodSpec 字段從 Deployment 的 manifest 以及其他相關元數(shù)據(jù)中復制而來。有時 Deployment 記錄在此之后也需要更新(例如,如果設置了 process deadline)。
當完成以上步驟之后,該 Deployment 的 status 就會被更新,然后重新進入與之前相同的循環(huán),等待 Deployment 與期望的狀態(tài)相匹配。由于 Deployment Controller 只關心 ReplicaSet,因此需要通過 ReplicaSet Controller 來繼續(xù)協(xié)調(diào)。
ReplicaSets controller
在前面的步驟中,Deployment Controller 創(chuàng)建了第一個 ReplicaSet,但仍然還是沒有 Pod,這時候就該 ReplicaSet Controller 登場了!ReplicaSet Controller 的工作是監(jiān)視 ReplicaSets 及其相關資源(Pod)的生命周期。和大多數(shù)其他 Controller 一樣,它通過觸發(fā)某些事件的處理器來實現(xiàn)此目的。
當創(chuàng)建 ReplicaSet 時(由 Deployment Controller 創(chuàng)建),RS Controller 檢查新 ReplicaSet 的狀態(tài),并檢查當前狀態(tài)與期望狀態(tài)之間存在的偏差,然后通過調(diào)整 Pod 的副本數(shù)來達到期望的狀態(tài)。
Pod 的創(chuàng)建也是批量進行的,從 SlowStartInitialBatchSize 開始,然后在每次成功的迭代中以一種 slow start 操作加倍。這樣做的目的是在大量 Pod 啟動失敗時(例如,由于資源配額),可以減輕 kube-apiserver 被大量不必要的 HTTP 請求吞沒的風險。如果創(chuàng)建失敗,最好能夠優(yōu)雅地失敗,并且對其他的系統(tǒng)組件造成的影響最小!
Kubernetes 通過 Owner References(在子級資源的某個字段中引用其父級資源的 ID) 來構造嚴格的資源對象層級結構。這確保了一旦 Controller 管理的資源被刪除(級聯(lián)刪除),子資源就會被垃圾收集器刪除,同時還為父級資源提供了一種有效的方式來避免他們競爭同一個子級資源(想象兩對父母都認為他們擁有同一個孩子的場景)。
Owner References 的另一個好處是:它是有狀態(tài)的。如果有任何 Controller 重啟了,那么由于資源對象的拓撲關系與 Controller 無關,該操作不會影響到系統(tǒng)的穩(wěn)定運行。這種對資源隔離的重視也體現(xiàn)在 Controller 本身的設計中:Controller 不能對自己沒有明確擁有的資源進行操作,它們應該選擇對資源的所有權,互不干涉,互不共享。
有時系統(tǒng)中也會出現(xiàn)孤兒(orphaned)資源,通常由以下兩種途徑產(chǎn)生:
- 父級資源被刪除,但子級資源沒有被刪除
- 垃圾收集策略禁止刪除子級資源
當發(fā)生這種情況時,Controller 將會確保孤兒資源擁有新的 Owner。多個父級資源可以相互競爭同一個孤兒資源,但只有一個會成功(其他父級資源會收到驗證錯誤)。
Informers
你可能已經(jīng)注意到,某些 Controller(例如 RBAC 授權器或 Deployment Controller)需要先檢索集群狀態(tài)然后才能正常運行。拿 RBAC 授權器舉例,當請求進入時,授權器會將用戶的初始狀態(tài)緩存下來,然后用它來檢索與 etcd 中的用戶關聯(lián)的所有 角色(Role)和 角色綁定(RoleBinding)。那么問題來了,Controller 是如何訪問和修改這些資源對象的呢?事實上 Kubernetes 是通過 Informer 機制來解決這個問題的。
Infomer 是一種模式,它允許 Controller 查找緩存在本地內(nèi)存中的數(shù)據(jù)(這份數(shù)據(jù)由 Informer 自己維護)并列出它們感興趣的資源。
雖然 Informer 的設計很抽象,但它在內(nèi)部實現(xiàn)了大量的對細節(jié)的處理邏輯(例如緩存),緩存很重要,因為它不但可以減少對 Kubenetes API 的直接調(diào)用,同時也能減少 Server 和 Controller 的大量重復性工作。通過使用 Informer,不同的 Controller 之間以線程安全(Thread safety)的方式進行交互,而不必擔心多個線程訪問相同的資源時會產(chǎn)生沖突。
有關 Informer 的更多詳細解析,請參考這篇文章:Kubernetes: Controllers, Informers, Reflectors and Stores
Scheduler
當所有的 Controller 正常運行后,etcd 中就會保存一個 Deployment、一個 ReplicaSet 和 三個 Pod 資源記錄,并且可以通過 kube-apiserver 查看。然而,這些 Pod 資源現(xiàn)在還處于 Pending狀態(tài),因為它們還沒有被調(diào)度到集群中合適的 Node 上運行。這個問題最終要靠調(diào)度器(Scheduler)來解決。
Scheduler 作為一個獨立的組件運行在集群控制平面上,工作方式與其他 Controller 相同:監(jiān)聽實際并將系統(tǒng)狀態(tài)調(diào)整到期望的狀態(tài)。具體來說,Scheduler 的作用是將待調(diào)度的 Pod 按照特定的算法和調(diào)度策略綁定(Binding)到集群中某個合適的 Node 上,并將綁定信息寫入 etcd 中(它會過濾其 PodSpec 中 NodeName 字段為空的 Pod),默認的調(diào)度算法的工作方式如下:
一旦找到了合適的節(jié)點,Scheduler 就會創(chuàng)建一個 Binding 對象,該對象的 Name 和 Uid 與 Pod 相匹配,并且其 ObjectReference 字段包含所選節(jié)點的名稱,然后通過 POST 請求發(fā)送給 apiserver。
當 kube-apiserver 接收到此 Binding 對象時,注冊吧會將該對象反序列化并更新 Pod 資源中的以下字段:
- 將 NodeName 的值設置為 ObjectReference 中的 NodeName。
- 添加相關的注釋。
- 將 PodScheduled 的 status 值設置為 True。可以通過 kubectl 來查看:
$ kubectl get <PODNAME> -o go-template='{{range .status.conditions}}{{if eq .type "PodScheduled"}}{{.status}}{{end}}{{end}}'
復制
一旦 Scheduler 將 Pod 調(diào)度到某個節(jié)點上,該節(jié)點的 Kubelet 就會接管該 Pod 并開始部署。
注意
預選策略和優(yōu)選策略都可以通過 –policy-config-file 參數(shù)來擴展,如果默認的調(diào)度器不滿足要求,還可以部署自定義的調(diào)度器。如果 podSpec.schedulerName 的值設置為其他的調(diào)度器,則 Kubernetes 會將該 Pod 的調(diào)度轉交給那個調(diào)度器。
6. Kubelet
Pod 同步
現(xiàn)在,所有的 Controller 都完成了工作,我們來總結一下:
- HTTP 請求通過了認證、授權和準入控制階段。
- 一個 Deployment、ReplicaSet 和三個 Pod 資源被持久化到 etcd 存儲中。
- 然后運行了一系列Initializers。
- 最后每個 Pod 都被調(diào)度到合適的節(jié)點。
然而到目前為止,所有的狀態(tài)變化僅僅只是針對保存在 etcd 中的資源記錄,接下來的步驟涉及到運行在工作節(jié)點之間的 Pod 的分布狀況,這是分布式系統(tǒng)(比如 Kubernetes)的關鍵因素。這些任務都是由 Kubelet 組件完成的,讓我們開始吧!
在 Kubernetes 集群中,每個 Node 節(jié)點上都會啟動一個 Kubelet 服務進程,該進程用于處理 Scheduler 下發(fā)到本節(jié)點的任務,管理 Pod 的生命周期,包括掛載卷、容器日志記錄、垃圾回收以及其他與 Pod 相關的事件。
如果換一種思維模式,你可以把 Kubelet 當成一種特殊的 Controller,它每隔 20 秒(可以自定義)向 kube-apiserver 通過 NodeName 獲取自身 Node 上所要運行的 Pod 清單。一旦獲取到了這個清單,它就會通過與自己的內(nèi)部緩存進行比較來檢測新增加的 Pod,如果有差異,就開始同步 Pod 列表。我們來詳細分析一下同步過程:
- 首先串行執(zhí)行一系列 Pod 同步處理器(PodSyncHandlers),每個處理器檢查檢查 Pod 是否應該運行在該節(jié)點上。當所有的處理器都認為該 Pod 不應該運行在該節(jié)點上,則 Pod 的 Phase 值就會變成 PodFailed,并且將該 Pod 從該節(jié)點上驅(qū)逐出去。例如當你創(chuàng)建一個 Job 時,如果 Pod 失敗重試的時間超過了 spec.activeDeadlineSeconds 設置的值,就會將 Pod 從該節(jié)點驅(qū)逐出去。
- 接下來,Pod 的 Phase 值由 init 容器 和應用容器的狀態(tài)共同來決定。因為目前容器還沒有啟動,容器被視為處于等待階段,如果 Pod 中至少有一個容器處于等待階段,則其 Phase 值為 Pending。
- 最后,Pod 的 Condition 字段由 Pod 內(nèi)所有容器的狀態(tài)決定。現(xiàn)在我們的容器還沒有被容器運行時創(chuàng)建,所以 "https://github.com/kubernetes/kubernetes/blob/fc8bfe2d8929e11a898c4557f9323c482b5e8842/pkg/kubelet/status/generate.go#L70-L81">PodReady 的狀態(tài)被設置為 False。可以通過 kubectl 查看:
$ kubectl get <PODNAME> -o go-template='{{range .status.conditions}}{{if eq .type "Ready"}}{{.status}}{{end}}{{end}}'
復制
CRI 與 pause 容器
到了這個階段,大量的初始化工作都已經(jīng)完成,容器已經(jīng)準備好開始啟動了,而容器是由容器運行時(例如 Docker 和 Rkt)啟動的。
為了更容易擴展,Kubelet 從 1.5.0 開始通過容器運行時接口與容器運行時(Container Runtime)交互。簡而言之,CRI 提供了 Kubelet 和特定的運行時之間的抽象接口,它們之間通過協(xié)議緩沖區(qū)(它像一個更快的 JSON)和 gRPC API(一種非常適合執(zhí)行 Kubernetes 操作的 API)。這是一個非常酷的想法,通過使用 Kubelet 和運行時之間定義的契約關系,容器如何編排的具體實現(xiàn)細節(jié)已經(jīng)變得無關緊要。由于不需要修改 Kubernetes 的核心代碼,開發(fā)者可以以最小的開銷添加新的運行時。
不好意思有點跑題了,讓我們繼續(xù)回到容器啟動的階段。第一次啟動 Pod 時,Kubelet 會通過 Remote Procedure Command(RPC) 協(xié)議調(diào)用 RunPodSandbox。sandbox 用于描述一組容器,例如在 Kubernetes 中它表示的是 Pod。sandbox 是一個很寬泛的概念,所以對于其他沒有使用容器的運行時仍然是有意義的(比如在一個基于 hypervisor 的運行時中,sandbox 可能指的就是虛擬機)。
我們的例子中使用的容器運行時是 Docker,創(chuàng)建 sandbox 時首先創(chuàng)建的是 pause 容器。pause 容器作為同一個 Pod 中所有其他容器的基礎容器,它為 Pod 中的每個業(yè)務容器提供了大量的 Pod 級別資源,這些資源都是 Linux 命名空間(包括網(wǎng)絡命名空間,IPC 命名空間和 PID 命名空間)。
pause 容器提供了一種方法來管理所有這些命名空間并允許業(yè)務容器共享它們,在同一個網(wǎng)絡命名空間中的好處是:同一個 Pod 中的容器可以使用 localhost 來相互通信。pause 容器的第二個功能與 PID 命名空間的工作方式相關,在 PID 命名空間中,進程之間形成一個樹狀結構,一旦某個子進程由于父進程的錯誤而變成了“孤兒進程”,其便會被 init 進程進行收養(yǎng)并最終回收資源。關于 pause 工作方式的詳細信息可以參考:The Almighty Pause Container。
一旦創(chuàng)建好了 pause 容器,下面就會開始檢查磁盤狀態(tài)然后開始啟動業(yè)務容器。
CNI 和 Pod 網(wǎng)絡
現(xiàn)在我們的 Pod 已經(jīng)有了基本的骨架:一個共享所有命名空間以允許業(yè)務容器在同一個 Pod 里進行通信的 pause 容器。但現(xiàn)在還有一個問題,那就是容器的網(wǎng)絡是如何建立的?
當 Kubelet 為 Pod 創(chuàng)建網(wǎng)絡時,它會將創(chuàng)建網(wǎng)絡的任務交給 CNI 插件。CNI 表示容器網(wǎng)絡接口(Container Network Interface),和容器運行時的運行方式類似,它也是一種抽象,允許不同的網(wǎng)絡提供商為容器提供不同的網(wǎng)絡實現(xiàn)。通過將 json 配置文件(默認在 /etc/cni/net.d 路徑下)中的數(shù)據(jù)傳送到相關的 CNI 二進制文件(默認在 /opt/cni/bin 路徑下)中,cni 插件可以給 pause 容器配置相關的網(wǎng)絡,然后 Pod 中其他的容器都使用 pause 容器的網(wǎng)絡。下面是一個簡單的示例配置文件:
CNI 插件還會通過 CNI_ARGS 環(huán)境變量為 Pod 指定其他的元數(shù)據(jù),包括 Pod 名稱和命名空間。
下面的步驟因 CNI 插件而異,我們以 bridge 插件舉例:
- 該插件首先會在根網(wǎng)絡命名空間(也就是宿主機的網(wǎng)絡命名空間)中設置本地 Linux 網(wǎng)橋,以便為該主機上的所有容器提供網(wǎng)絡服務。
- 然后它會將一個網(wǎng)絡接口(veth 設備對的一端)插入到 pause 容器的網(wǎng)絡命名空間中,并將另一端連接到網(wǎng)橋上。你可以這樣來理解 veth 設備對:它就像一根很長的管道,一端連接到容器,一端連接到根網(wǎng)絡命名空間中,數(shù)據(jù)包就在管道中進行傳播。
- 接下來 json 文件中指定的 IPAM Plugin 會為 pause 容器的網(wǎng)絡接口分配一個 IP 并設置相應的路由,現(xiàn)在 Pod 就有了自己的 IP。
- IPAM Plugin 的工作方式和 CNI Plugin 類似:通過二進制文件調(diào)用并具有標準化的接口,每一個 IPAM Plugin 都必須要確定容器網(wǎng)絡接口的 IP、子網(wǎng)以及網(wǎng)關和路由,并將信息返回給 CNI 插件。最常見的 IPAM Plugin 是 host-local,它從預定義的一組地址池中為容器分配 IP 地址。它將地址池的信息以及分配信息保存在主機的文件系統(tǒng)中,從而確保了同一主機上每個容器的 IP 地址的唯一性。
- 最后 Kubelet 會將集群內(nèi)部的 DNS 服務器的 Cluster IP 地址傳給 CNI 插件,然后 CNI 插件將它們寫到容器的 /etc/resolv.conf 文件中。
一旦完成了上面的步驟,CNI 插件就會將操作的結果以 json 的格式返回給 Kubelet。
跨主機容器網(wǎng)絡
到目前為止,我們已經(jīng)描述了容器如何與宿主機進行通信,但跨主機之間的容器如何通信呢?
通常情況下使用 overlay 網(wǎng)絡來進行跨主機容器通信,這是一種動態(tài)同步多個主機間路由的方法。 其中最常用的 overlay 網(wǎng)絡插件是 flannel,flannel 具體的工作方式可以參考 CoreOS 的文檔。
容器啟動
所有網(wǎng)絡都配置完成后,接下來就開始真正啟動業(yè)務容器了!
一旦 sanbox 完成初始化并處于 active 狀態(tài),Kubelet 就可以開始為其創(chuàng)建容器了。首先啟動 PodSpec 中定義的 init 容器,然后再啟動業(yè)務容器。具體過程如下:
7. 總結
如果上面一切順利,現(xiàn)在你的集群上應該會運行三個容器,所有的網(wǎng)絡,數(shù)據(jù)卷和秘鑰都被通過 CRI 接口添加到容器中并配置成功。
上文所述的創(chuàng)建 Pod 整個過程的流程圖如下所示:
總結
以上是生活随笔為你收集整理的创建 tls 客户端 凭据时发生严重错误。内部错误状态为 10013_kubectl 创建 Pod 背后到底发生了什么?...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: string类型比较_redis存jso
- 下一篇: mysql timezone utc_在