日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

深度解讀:輸入 kubectl run 后,到底發(fā)生了什么?

 louy2 2019-10-10
【編者的話】為了確保整體的簡(jiǎn)單性和易上手,有時(shí) Kubernetes 會(huì)通過(guò)一些簡(jiǎn)單的抽象隱去操作背后的復(fù)雜邏輯,但作為一名有夢(mèng)想的工程師,掌握其背后的真正思路是十分有必要的。本文以 Kubectl 創(chuàng)建 Pod 為例,向你揭露從客戶端到 Kubelet 的請(qǐng)求的完整生命周期。

想象一下,當(dāng)你想在 Kubernetes 集群部署 Nginx 時(shí),你會(huì)執(zhí)行以下命令:
kubectl run nginx --image=nginx --replicas=3

幾秒后,你將看到三個(gè) Nginx Pod 分布在集群 Worker 節(jié)點(diǎn)上。這相當(dāng)神奇,但它背后究竟發(fā)生了什么?

Kubernetes 最為人稱道的地方是,它通過(guò)用戶友好的 API 處理跨基礎(chǔ)架構(gòu)的工作負(fù)載部署,通過(guò)簡(jiǎn)單的抽象隱藏其背后的復(fù)雜性。但是,為了充分理解它為我們提供的價(jià)值,我們需要理解它的原理。

本文將帶領(lǐng)你充分了解從客戶端到 Kubelet 請(qǐng)求的完整生命周期,并在必要時(shí)通過(guò)源代碼解釋它到底是什么。如果你想和更多 Kubernetes 技術(shù)專家交流,可以加我微信liyingjiese,備注『加群』。群里每周都有全球各大公司的最佳實(shí)踐以及行業(yè)最新動(dòng)態(tài)。

Kubectl

驗(yàn)證和生成器

首先,當(dāng)我們按下回車執(zhí)行命令后,Kubectl 會(huì)執(zhí)行客戶端驗(yàn)證,以確保非法請(qǐng)求(如創(chuàng)建不支持的資源或使用格式錯(cuò)誤的鏡像名稱)快速失敗,并不會(huì)發(fā)送給 kube-apiserver——即通過(guò)減少不必要的負(fù)載來(lái)提高系統(tǒng)性能。

驗(yàn)證通過(guò)后, Kubectl 開(kāi)始封裝它將發(fā)送給 kube-apiserver 的 HTTP 請(qǐng)求。在 Kubernetes 中,訪問(wèn)或更改狀態(tài)的所有嘗試都通過(guò) kube-apiserver 進(jìn)行,后者又與 etcd 進(jìn)行通信。Kubectl 客戶端也不例外。為了構(gòu)造 HTTP 請(qǐng)求, Kubectl 使用生成器(generators),這是一種負(fù)責(zé)序列化的抽象。

你可能沒(méi)有注意到,通過(guò)執(zhí)行 kubectl run,除了運(yùn)行 Deployment,我們還能利用指定參數(shù) --generator 來(lái)部署其他工作負(fù)載。

如果沒(méi)有指定 --generator 參數(shù)的值, Kubectl 會(huì)自動(dòng)推斷資源的類型,具體如下:
  • 具有 --restart-policy=Always 的資源被視為 Deployment;
  • 具有 --restart-policy=OnFailure 的資源被視為 Job;
  • 具有 --restart-policy=Never 的資源被視為 Pod。


Kubectl 還將確定是否需要觸發(fā)其他操作,例如記錄命令(用于部署或?qū)徲?jì)),或者此命令是否是 dry run。

當(dāng) Kubectl 判斷出要?jiǎng)?chuàng)建一個(gè) Deployment 后,它將使用 DeploymentV1Beta1 generator 配合我們提供的參數(shù),生成一個(gè)運(yùn)行時(shí)對(duì)象(Runtime Object)。

API Group 和版本協(xié)商

這里值得指出的是, Kubernetes 使用的是一個(gè)分類為 API Group 的版本化 API。它旨在對(duì)資源進(jìn)行分類,以便于推理。

同時(shí),它還為單個(gè) API 提供了更好的版本化方案。例如,Deployment 的 API Group 為 apps,其最新版本為 v1。這也是我們?yōu)槭裁葱枰?Deployment manifests 頂部指定 apiVersion: apps/v1 的原因。

回歸正文, Kubectl 生成運(yùn)行時(shí)對(duì)象之后,就開(kāi)始為它查找合適的 API Group 和版本,然后組裝一個(gè)知道該資源各種 REST 語(yǔ)義的版本化客戶端。

這個(gè)發(fā)現(xiàn)階段被稱為版本協(xié)商(version negotiation),這時(shí) Kubectl 會(huì)掃描 remote API 上的 /apis 路徑以檢索所有可能的 API Group。

由于 kube-apiserver 在 /apis 路徑中公開(kāi)其 OpenAPI 格式的 scheme 文檔,客戶端可以借此輕松找到匹配的 API。

為了提高性能, Kubectl 還將 OpenAPI scheme 緩存到 ~/.kube/cache/discovery 目錄。如果要了解 API 發(fā)現(xiàn)的完整過(guò)程,你可以試著刪除該目錄并在運(yùn)行 Kubectl 命令時(shí)將 -v 參數(shù)的值設(shè)為最大,然后你就可以在日志中看到所有試圖找到這些 API 版本的 HTTP 請(qǐng)求。
最后一步才是真正地發(fā)送 HTTP 請(qǐng)求。一旦請(qǐng)求獲得成功的響應(yīng), Kubectl 將會(huì)根據(jù)所需的輸出格式打印 success message。

客戶端驗(yàn)證

我們?cè)谏衔闹袥](méi)有提到的一件事是客戶端身份驗(yàn)證(這是在發(fā)送 HTTP 請(qǐng)求之前處理的),現(xiàn)在讓我們來(lái)看看。

為了成功發(fā)送請(qǐng)求, Kubectl 需要先進(jìn)行身份驗(yàn)證。用戶憑據(jù)一般存儲(chǔ)在 kubeconfig 文件中,但該文件可以存儲(chǔ)在其他不同位置。為了定位到它,我們可以執(zhí)行以下操作:
  • 如果指定參數(shù) --kubeconfig,那么采用該值;
  • 如果指定環(huán)境變量 $KUBECONFIG,那么采用該值;
  • 否則查看默認(rèn)的目錄,如 ~/.kube,并使用找到的第一個(gè)文件。


解析文件后,它會(huì)確定當(dāng)前要使用的上下文、當(dāng)前指向的集群以及當(dāng)前與用戶關(guān)聯(lián)的所有身份驗(yàn)證信息。如果用戶提供了額外的參數(shù)(例如 --username),則這些值優(yōu)先,并將覆蓋 kubeconfig 中指定的值。

一旦有了上述信息, Kubectl 就會(huì)填充客戶端的配置,以便它能夠適當(dāng)?shù)匦揎?HTTP 請(qǐng)求:
  • x509 證書使用 tls.TLSConfig 發(fā)送(包括 CA 證書);
  • bearer tokens 在 HTTP 請(qǐng)求頭 Authorization 中發(fā)送;
  • 用戶名和密碼通過(guò) HTTP 基礎(chǔ)認(rèn)證發(fā)送;
  • OpenID 認(rèn)證過(guò)程是由用戶事先手動(dòng)處理的,產(chǎn)生一個(gè)像 bearer token 一樣被發(fā)送的 token。


1.jpg

kube-apiserver

認(rèn)證

我們的請(qǐng)求已經(jīng)發(fā)送成功,接下來(lái)呢?kube-apiserver!

kube-apiserver 是客戶端和系統(tǒng)組件用來(lái)持久化和檢索集群狀態(tài)的主要接口。為了執(zhí)行其功能,它需要能夠驗(yàn)證請(qǐng)求是否合法,這個(gè)過(guò)程被稱為認(rèn)證 (Authentication)。

為了驗(yàn)證請(qǐng)求,當(dāng)服務(wù)器首次啟動(dòng)時(shí),kube-apiserver 會(huì)查看用戶提供的所有 CLI 參數(shù),并組裝合適的 authenticator 列表。

舉個(gè)例子:
  • 如果指定參數(shù) --client-ca-file,它會(huì)把 x509 authenticator 添加到列表中;
  • 如果指定參數(shù) --token-auth-file,它會(huì)把 token authenticator 添加到列表中。


每次收到請(qǐng)求時(shí),它都會(huì)遍歷 authenticator 列表進(jìn)行認(rèn)證,直到成功為止:
  • x509 handler 會(huì)驗(yàn)證 HTTP 請(qǐng)求是否是通過(guò) CA 根證書簽名的 TLS 密鑰編碼的;
  • bearer token handler 會(huì)驗(yàn)證 HTTP Authorization header 指定的 token 是否存在于 --token-auth-file 參數(shù)提供的 token 文件中;
  • basicauth handler 會(huì)簡(jiǎn)單驗(yàn)證 HTTP 請(qǐng)求的基本身份憑據(jù)。


如果認(rèn)證失敗,則請(qǐng)求失敗并返回匯總的錯(cuò)誤信息。

如果成功,則從請(qǐng)求中刪除 Authorization 標(biāo)頭,并將用戶信息添加到其上下文中,為之后的操作(例如授權(quán)和準(zhǔn)入控制器)提供訪問(wèn)先前建立的用戶身份的能力。

授權(quán)

請(qǐng)求已發(fā)送,kube-apiserver 也已成功驗(yàn)證我們是誰(shuí),所以我們終于解脫了?

想太多!

雖然我們證明了自己是誰(shuí),但還沒(méi)證明有權(quán)執(zhí)行此操作。畢竟身份(identity)和許可(permission)并不是一回事。因此,kube-apiserver 需要授權(quán)。

kube-apiserver 處理授權(quán)的方式與身份驗(yàn)證非常相似:基于 CLI 參數(shù)輸入,匯集一系列 authorizer,這些 authorizer 將針對(duì)每個(gè)傳入請(qǐng)求運(yùn)行。如果所有 authorizer 都拒絕該請(qǐng)求,則該請(qǐng)求將導(dǎo)致 Forbidden 響應(yīng)并不再繼續(xù)。如果單個(gè) authorizer 被批準(zhǔn),則請(qǐng)求繼續(xù)。

Kubernetes v1.14 的 authorizer 實(shí)例:
  • webhook:與集群外的 HTTP(S) 服務(wù)交互;
  • ABAC:執(zhí)行靜態(tài)文件中定義的策略;
  • RBAC:執(zhí)行由集群管理員添加為 K8s 資源的 RBAC 規(guī)則;
  • Node:確保 Kubelet 只能訪問(wèn)自己節(jié)點(diǎn)上的資源。


Admission Controller

好的,到目前為止,我們已經(jīng)過(guò)認(rèn)證并獲得了 kube-apiserver 的授權(quán)。那接下來(lái)呢?

從 kube-apiserver 的角度來(lái)看,它已經(jīng)驗(yàn)證了我們的身份并授權(quán)我們執(zhí)行后續(xù)操作,但對(duì)于 Kubernetes,系統(tǒng)的其他組件對(duì)此還有不少疑義,所以 Admission Controller 該閃亮登場(chǎng)了。

雖然認(rèn)證的重點(diǎn)在于證實(shí)用戶是否具有權(quán)限,但是 Admission Controllers 仍會(huì)攔截該請(qǐng)求,以確保它符合集群更廣泛的期望和規(guī)則。它們是對(duì)象持久化到 etcd 之前的最后一個(gè)堡壘,因此它們封裝了剩余的系統(tǒng)檢查以確保操作不會(huì)產(chǎn)生意外或負(fù)面結(jié)果。

Admission Controller 的工作方式類似于 Authentication 和 Authorization,但有一個(gè)區(qū)別:如果單個(gè) Admission Controller 失敗,則整個(gè)鏈斷開(kāi),請(qǐng)求將失敗。

Admission Controller 設(shè)計(jì)的真正優(yōu)勢(shì)在于它致力于提升可擴(kuò)展性。每個(gè)控制器都作為插件存儲(chǔ)在 plugin/pkg/admission 目錄中,最后編譯進(jìn) kube-apiserver 二進(jìn)制文件。

Kubernetes 目前提供十多種 Admission Controller,此處建議閱讀文檔:https://cs.kubernetes. ... lers/
2.jpg

etcd

到目前為止, Kubernetes 已經(jīng)完全審查了傳入的請(qǐng)求,并允許它繼續(xù)往下走。在下一步中,kube-apiserver 將反序列化 HTTP 請(qǐng)求,構(gòu)造運(yùn)行時(shí)對(duì)象(有點(diǎn)像 Kubectl generator 的逆過(guò)程),并將它們持久化到 etcd。這里我們稍微分析一下。

kube-apiserver 是怎么知道在接受我們的請(qǐng)求時(shí)該怎么做的呢?

在提供任何請(qǐng)求之前,kube-apiserver 會(huì)發(fā)生一系列非常復(fù)雜的步驟。讓我們從第一次運(yùn)行 kube-apiserver 二進(jìn)制文件開(kāi)始:
  • 當(dāng)運(yùn)行 kube-apiserver 二進(jìn)制文件時(shí),它會(huì)創(chuàng)建一個(gè)服務(wù)鏈,允許 apiserver 聚合。這是一種支持多 apiserver 的方式;
  • 之后,它會(huì)創(chuàng)建一個(gè)用作默認(rèn)實(shí)現(xiàn)的 generic apiserver;
  • 使用生成的 OpenAPI scheme 填充 apiserver 配置;
  • 然后,kube-apiserver 遍歷 scheme 中指定的所有 API Group, 并為其構(gòu)造 storage provider。當(dāng)你訪問(wèn)或變更資源狀態(tài)時(shí), kube-apiserver 就會(huì)調(diào)用這些 API Group;
  • 對(duì)于每個(gè) API Group, 它還會(huì)迭代每個(gè)組版本,并為每個(gè) HTTP 路由安裝 REST 映射。這允許 kube-apiserver 映射請(qǐng)求,并且一旦找到匹配就能夠委托給正確的代碼邏輯;
  • 對(duì)于本文的特定用例,將注冊(cè)一個(gè) POST handler,該處理程序?qū)⑽薪o create resource handler。


現(xiàn)在,kube-apiserver 已經(jīng)知道存在哪些路由及內(nèi)部映射,當(dāng)請(qǐng)求匹配時(shí),它可以調(diào)用相應(yīng)的處理程序和存儲(chǔ)程序。這是非常完美的設(shè)計(jì)模式。讓我們假設(shè) HTTP 請(qǐng)求已經(jīng)被 kube-apiserver 收到了:
  • 如果程序處理鏈可以將請(qǐng)求與注冊(cè)的路由匹配,它會(huì)將該請(qǐng)求交給注冊(cè)到該路由的 dedicated handler。否則它會(huì)回退到 path-based handler(這是調(diào)用 /apis 時(shí)會(huì)發(fā)生的情況)。如果沒(méi)有為該路由注冊(cè)處理程序,則會(huì)調(diào)用 not found handler,最終返回 404;
  • 幸運(yùn)的是,我們有一個(gè)處理器名為 createHandler!它有什么作用?它將首先解碼 HTTP 請(qǐng)求并執(zhí)行基礎(chǔ)驗(yàn)證,例如確保請(qǐng)求提供的 JSON 與我們的版本化 API 資源匹配;
  • 審計(jì)和準(zhǔn)入控制階段;
  • 之后,資源會(huì)通過(guò) storage provider 存儲(chǔ)到 etcd 中。默認(rèn)情況下,保持到 etcd 的鍵的格式為 /,當(dāng)然,它也支持自定義;
  • 資源創(chuàng)建過(guò)程中出現(xiàn)的任何錯(cuò)誤都會(huì)被捕獲,最后 storage provider 會(huì)執(zhí)行 get 調(diào)用來(lái)確認(rèn)該資源是否被成功創(chuàng)建。如果需要額外的清理工作(finalization),就會(huì)調(diào)用后期創(chuàng)建的處理器和裝飾器;
  • 最后,構(gòu)造 HTTP 響應(yīng)并返回給客戶端。


這么多步驟,能夠堅(jiān)持走到這里是非常了不起的!同時(shí),apiserver 實(shí)際上也做了很多工作。總結(jié)一下:我們部署的 Deployment 現(xiàn)在存在于 etcd 中,但仍沒(méi)有看到它真正地 work……

注:在 Kubernetes v1.14 之前,這往后還有 Initializer 的步驟,該步驟在 v1.14 被 webhook admission 取代。

控制循環(huán)

Deployment Controller

截至目前,我們的 Deployment 已經(jīng)存儲(chǔ)于 etcd 中,并且所有的初始化邏輯都已完成。接下來(lái)的階段將涉及 Deployment 所依賴的資源拓?fù)浣Y(jié)構(gòu)。

在 Kubernetes, Deployment 實(shí)際上只是 ReplicaSet 的集合,而 ReplicaSet 是 Pod 的集合。那么 Kubernetes 如何從一個(gè) HTTP 請(qǐng)求創(chuàng)建這個(gè)層次結(jié)構(gòu)呢?這就不得不提 Kubernetes 的內(nèi)置控制器(Controller)。

Kubernetes 系統(tǒng)中使用了大量的 Controller, Controller 是一個(gè)用于將系統(tǒng)狀態(tài)從當(dāng)前狀態(tài)調(diào)諧到期望狀態(tài)的異步腳本。所有內(nèi)置的 Controller 都通過(guò)組件 kube-controller-manager 并行運(yùn)行,每種 Controller 都負(fù)責(zé)一種具體的控制流程。

首先,我們介紹一下 Deployment Controller:

將 Deployment 存儲(chǔ)到 etcd 后,我們可以通過(guò) kube-apiserver 使其可見(jiàn)。當(dāng)這個(gè)新資源可用時(shí), Deployment controller 會(huì)檢測(cè)到它,它的工作是監(jiān)聽(tīng) Deployment 的更改。在我們的例子中, Controller 通過(guò)注冊(cè)創(chuàng)建事件的回調(diào)函數(shù)(更多相關(guān)信息,參見(jiàn)下文)。

當(dāng)我們的 Deployment 首次可用時(shí),將執(zhí)行此回調(diào)函數(shù),并將該對(duì)象添加到內(nèi)部工作隊(duì)列(internal work queue)。

當(dāng)它處理我們的 Deployment 對(duì)象時(shí),控制器將檢查我們的 Deployment 并意識(shí)到?jīng)]有與之關(guān)聯(lián)的 ReplicaSet 或 Pod。

它通過(guò)使用標(biāo)簽選擇器(label selectors)查詢 kube-apiserver 來(lái)實(shí)現(xiàn)此功能。有趣的是,這個(gè)同步過(guò)程是狀態(tài)不可知的。另外,它以相同的方式調(diào)諧新對(duì)象和已存在的對(duì)象。

在意識(shí)到?jīng)]有與其關(guān)聯(lián)的 ReplicaSet 或 Pod 后,Deployment Controller 就會(huì)開(kāi)始執(zhí)行彈性伸縮流程(scaling process)。它通過(guò)推出(如創(chuàng)建)一個(gè) ReplicaSet, 為其分配 label selector 并將其版本號(hào)設(shè)置為 1。

ReplicaSet 的 PodSpec 字段是從 Deployment 的 manifest 以及其他相關(guān)元數(shù)據(jù)中復(fù)制而來(lái)。有時(shí) Deployment 在此之后也需要更新(例如,如果設(shè)置了 process deadline)。

當(dāng)完成以上步驟之后,該 Deployment 的 status 就會(huì)被更新,然后重新進(jìn)入與之前相同的循環(huán),等待 Deployment 與期望的狀態(tài)相匹配。由于 Deployment Controller 只關(guān)心 ReplicaSet, 因此需要 ReplicaSet Controller 繼續(xù)調(diào)諧過(guò)程。

ReplicaSet Controller

在上一步中,Deployment 控制器創(chuàng)建了屬于該 Deployment 的第一個(gè) ReplicaSet, 但仍然沒(méi)有創(chuàng)建 Pod。所以這里我們要引入一個(gè)新東西:ReplicaSet 控制器!

ReplicaSet 控制器的作用是監(jiān)視 ReplicaSet 及其相關(guān)資源 Pod 的生命周期。與大多數(shù)其它控制器一樣,它通過(guò)觸發(fā)某些事件的處理程序來(lái)實(shí)現(xiàn)目標(biāo)。

當(dāng)創(chuàng)建 ReplicaSet 時(shí)(由 Deployment 控制器創(chuàng)建),ReplicaSet 控制器會(huì)檢查新 ReplicaSet 的狀態(tài),并意識(shí)到現(xiàn)有狀態(tài)與期望狀態(tài)之間存在偏差。然后,它會(huì)嘗試通過(guò)調(diào)整 Pod 的副本數(shù)來(lái)調(diào)諧這種狀態(tài)。

Pod 的創(chuàng)建也是批量進(jìn)行的,從數(shù)量 SlowStartInitialBatchSize 開(kāi)始,然后在每次成功的迭代中以一種 slow start 操作加倍。這樣做的目的是在大量 Pod 啟動(dòng)失敗時(shí)(如由于資源配額),可以減輕 kube-apiserver 被大量不必要的 HTTP 請(qǐng)求吞沒(méi)的風(fēng)險(xiǎn)。

Kubernetes 通過(guò) Owner References (子資源的某個(gè)字段中引用其父資源的 ID) 來(lái)執(zhí)行嚴(yán)格的資源對(duì)象層級(jí)結(jié)構(gòu)。這確保了 Controller 管理的資源被刪除(級(jí)聯(lián)刪除)時(shí),子資源就會(huì)被垃圾收集器刪除。同時(shí),它還為父資源提供了一種有效的方式來(lái)避免競(jìng)爭(zhēng)同一個(gè)子資源(想象兩對(duì)父母認(rèn)為他們擁有同一個(gè)孩子的場(chǎng)景)。

Owner References 的另一個(gè)好處是,它是有狀態(tài)的。如果重啟任何的 Controller,那么由于資源對(duì)象的拓?fù)潢P(guān)系與 Controller 無(wú)關(guān),該重啟時(shí)間不會(huì)影響到系統(tǒng)的穩(wěn)定運(yùn)行。這種對(duì)資源隔離的重視也體現(xiàn)在 Controller 本身的設(shè)計(jì)中:Controller 不能對(duì)自己沒(méi)有明確擁有的資源進(jìn)行操作,它們之間互不干涉,互不共享。

有時(shí)系統(tǒng)中也會(huì)出現(xiàn)孤兒 (orphaned) 資源,通常由以下兩種途徑產(chǎn)生:
  • 父資源被刪除,但子資源沒(méi)有被刪除;
  • 垃圾收集策略禁止刪除子資源。


當(dāng)發(fā)生這種情況時(shí), Controller 將會(huì)確保孤兒資源擁有新的 Owner。多個(gè)父資源可以相互競(jìng)爭(zhēng)同一個(gè)孤兒資源,但只有一個(gè)會(huì)成功(其他父資源會(huì)收到一個(gè)驗(yàn)證錯(cuò)誤)。

Informers

你可能已經(jīng)注意到,有些 Controller(例如 RBAC 授權(quán)器或 Deployment Controller)需要檢索集群狀態(tài)然后才能正常運(yùn)行。

以 RBAC 授權(quán)器為例,當(dāng)請(qǐng)求進(jìn)入時(shí),授權(quán)器會(huì)將用戶的初始狀態(tài)緩存下來(lái)供以后使用,然后用它來(lái)檢索與 etcd 中的用戶關(guān)聯(lián)的所有角色(Role)和角色綁定(RoleBinding)。

那么 Controller 是如何訪問(wèn)和修改這些資源對(duì)象的呢?答案是引入 Informer。

Infomer 是一種模式,它允許 Controller 訂閱存儲(chǔ)事件并列出它們感興趣的資源。除了提供一個(gè)很好的工作抽象,它還需要處理很多細(xì)節(jié),如緩存。通過(guò)使用這種設(shè)計(jì),它還允許控制器以線程安全的方式進(jìn)行交互,而不必?fù)?dān)心線程沖突。

有關(guān) Informer 的更多信息,可深入閱讀:http://thub. ... tores

Scheduler

當(dāng)所有的 Controller 正常運(yùn)行后,etcd 中就會(huì)保存一個(gè) Deployment、一個(gè) ReplicaSet 和 三個(gè) Pod, 并且可以通過(guò) kube-apiserver 查看到。然而,這些 Pod 還處于 Pending 狀態(tài),因?yàn)樗鼈冞€沒(méi)有被調(diào)度到集群中合適的 Node 上。最終解決這個(gè)問(wèn)題的 Controller 是 Scheduler。

Scheduler 作為一個(gè)獨(dú)立的組件運(yùn)行在集群控制平面上,工作方式與其他 Controller 相同:監(jiān)聽(tīng)事件并調(diào)諧狀態(tài)。

具體來(lái)說(shuō), Scheduler 的作用是過(guò)濾 PodSpec 中 NodeName 字段為空的 Pod 并嘗試將其調(diào)度到合適的節(jié)點(diǎn)。

為了找到合適的節(jié)點(diǎn), Scheduler 會(huì)使用特定的算法,默認(rèn)調(diào)度算法工作流程如下:
  • 當(dāng) Scheduler 啟動(dòng)時(shí),會(huì)注冊(cè)一系列默認(rèn)的預(yù)選策略,這些預(yù)選策略會(huì)對(duì)候選節(jié)點(diǎn)進(jìn)行評(píng)估,判斷候選節(jié)點(diǎn)是否滿足候選 Pod 的需求。例如,如果 PodSpec 顯式地限制了 CPU 和內(nèi)存資源,并且節(jié)點(diǎn)的資源容量不滿足候選 Pod 的需求時(shí),Pod 就不會(huì)被調(diào)度到該節(jié)點(diǎn)上(資源容量 = 節(jié)點(diǎn)資源總量 - 節(jié)點(diǎn)中已運(yùn)行的容器需求資源總和);
  • 一旦選擇了適當(dāng)?shù)墓?jié)點(diǎn),就會(huì)對(duì)剩余的節(jié)點(diǎn)運(yùn)行一系列優(yōu)先級(jí)函數(shù),以對(duì)候選節(jié)點(diǎn)進(jìn)行打分。例如,為了在整個(gè)系統(tǒng)中分散工作負(fù)載,它將偏好于資源請(qǐng)求較少的節(jié)點(diǎn)(因?yàn)檫@表明運(yùn)行的工作負(fù)載較少)。當(dāng)它運(yùn)行這些函數(shù)時(shí),它為每個(gè)節(jié)點(diǎn)分配一個(gè)成績(jī),然后選擇分?jǐn)?shù)最高的節(jié)點(diǎn)進(jìn)行調(diào)度。


一旦算法找到了合適的節(jié)點(diǎn), Scheduler 就會(huì)創(chuàng)建一個(gè) Binding 對(duì)象,該對(duì)象的 Name 和 Uid 與 Pod 相匹配,并且其 ObjectReference 字段包含所選節(jié)點(diǎn)的名稱,然后通過(guò)發(fā)送 POST 請(qǐng)求給 apiserver。

當(dāng) kube-apiserver 接收到此 Binding 對(duì)象時(shí),注冊(cè)表會(huì)將該對(duì)象反序列化(registry deserializes)并更新 Pod 資源中的以下字段:
  • 將 NodeName 的值設(shè)置為 Binding 對(duì)象 ObjectReference 中的 NodeName;
  • 添加相關(guān)的注釋(annotations);
  • 將 PodScheduled 的 status 設(shè)置為 True。


一旦 Scheduler 將 Pod 調(diào)度到某個(gè)節(jié)點(diǎn)上,該節(jié)點(diǎn)的 Kubelet 就會(huì)接管該 Pod 并開(kāi)始部署。

附注:自定義調(diào)度器:有趣的是預(yù)測(cè)和優(yōu)先級(jí)函數(shù) (predicates and priority functions) 都是可擴(kuò)展的,可以使用 --policy-config-file 標(biāo)志來(lái)定義。這引入了一定程度的靈活性。管理員還可以在獨(dú)立部署中運(yùn)行自定義調(diào)度器(具有自定義處理邏輯的控制器)。如果 PodSpec 中包含 schedulerName,Kubernetes 會(huì)將該 pod 的調(diào)度移交給使用該名稱注冊(cè)的調(diào)度器。

Kubelet

Pod Sync

截至目前,所有的 Controller 都完成了工作,讓我們來(lái)總結(jié)一下:
  • HTTP 請(qǐng)求通過(guò)了認(rèn)證、授權(quán)和準(zhǔn)入控制階段;
  • 一個(gè) Deployment、ReplicaSet 和三個(gè) Pod 被持久化到 etcd;
  • 最后每個(gè) Pod 都被調(diào)度到合適的節(jié)點(diǎn)。


然而,到目前為止,所有的狀態(tài)變化僅僅只是針對(duì)保存在 etcd 中的資源對(duì)象,接下來(lái)的步驟涉及到在 Worker 節(jié)點(diǎn)之間運(yùn)行具體的容器,這是分布式系統(tǒng) Kubernetes 的關(guān)鍵因素。這些事情都是由 Kubelet 完成的。

在 Kubernetes 集群中,每個(gè) Node 節(jié)點(diǎn)上都會(huì)啟動(dòng)一個(gè) Kubelet 服務(wù)進(jìn)程,該進(jìn)程用于處理 Scheduler 下發(fā)到本節(jié)點(diǎn)的任務(wù),管理 Pod 的生命周期。這意味著它將處理 Pod 與 Container Runtime 之間所有的轉(zhuǎn)換邏輯,包括掛載卷、容器日志、垃圾回收以及其他重要事件。

一個(gè)有用的方法,你可以把 Kubelet 當(dāng)成一種特殊的 Controller,它每隔 20 秒(可以自定義)向 kube-apiserver 查詢 Pod,過(guò)濾 NodeName 與自身所在節(jié)點(diǎn)匹配的 Pod 列表。

一旦獲取到了這個(gè)列表,它就會(huì)通過(guò)與自己的內(nèi)部緩存進(jìn)行比較來(lái)檢測(cè)差異,如果有差異,就開(kāi)始同步 Pod 列表。我們來(lái)看看同步過(guò)程是什么樣的:
  • 如果 Pod 正在創(chuàng)建, Kubelet 就會(huì)暴露一些指標(biāo),可以用于在 Prometheus 中追蹤 Pod 啟動(dòng)延時(shí);

  • 然后,生成一個(gè) PodStatus 對(duì)象,表示 Pod 當(dāng)前階段的狀態(tài)。Pod 的 Phase 狀態(tài)是 Pod 在其生命周期中的高度概括,包括 Pending、Running、Succeeded、Failed 和 Unkown 這幾個(gè)值。狀態(tài)的產(chǎn)生過(guò)程非常復(fù)雜,因此很有必要深入深挖一下:
    • 首先,串行執(zhí)行一系列 PodSyncHandlers,每個(gè)處理器檢查 Pod 是否應(yīng)該運(yùn)行在該節(jié)點(diǎn)上。當(dāng)其中之一的處理器認(rèn)為該 Pod 不應(yīng)該運(yùn)行在該節(jié)點(diǎn)上,則 Pod 的 Phase 值就會(huì)變成 PodFailed 并將從該節(jié)點(diǎn)被驅(qū)逐。例如,以 Job 為例,當(dāng)一個(gè) Pod 失敗重試的時(shí)間超過(guò)了 activeDeadlineSeconds 設(shè)置的值,就會(huì)將該 Pod 從該節(jié)點(diǎn)驅(qū)逐出去
    • 接下來(lái),Pod 的 Phase 值由 init 容器和主容器狀態(tài)共同決定。由于主容器尚未啟動(dòng),容器被視為處于等待階段,如果 Pod 中至少有一個(gè)容器處于等待階段,則其 Phase 值為 Pending
    • 最后,Pod 的 Condition 字段由 Pod 內(nèi)所有容器狀態(tài)決定?,F(xiàn)在我們的容器還沒(méi)有被容器運(yùn)行時(shí) (Container Runtime) 創(chuàng)建,所以,Kubelet 將 PodReady 的狀態(tài)設(shè)置為 False

  • 生成 PodStatus 之后,Kubelet 就會(huì)將它發(fā)送到 Pod 的 status 管理器,該管理器的任務(wù)是通過(guò) apiserver 異步更新 etcd 中的記錄;
  • 接下來(lái)運(yùn)行一系列 admit handlers 以確保該 Pod 具有正確的權(quán)限(包括強(qiáng)制執(zhí)行 AppArmor profiles 和 NO_NEW_PRIVS),在該階段被拒絕的 Pod 將永久處于 Pending 狀態(tài);
  • 如果 Kubelet 啟動(dòng)時(shí)指定了 cgroups-per-qos 參數(shù),Kubelet 就會(huì)為該 Pod 創(chuàng)建 cgroup 并設(shè)置對(duì)應(yīng)的資源限制。這是為了更好的 Pod 服務(wù)質(zhì)量(QoS);

  • 為 Pod 創(chuàng)建相應(yīng)的數(shù)據(jù)目錄,包括:
    • Pod 目錄(通常是 /var/run/kubelet/pods/
    • Pod 的掛載卷目錄(/volumes)
    • Pod 的插件目錄(/plugins)

  • 卷管理器會(huì)掛載 Spec.Volumes 中定義的相關(guān)數(shù)據(jù)卷,然后等待掛載成功;
  • 從 apiserver 中檢索 Spec.ImagePullSecrets,然后將對(duì)應(yīng)的 Secret 注入到容器中;
  • 最后,通過(guò)容器運(yùn)行時(shí) (Container Runtime) 啟動(dòng)容器(下面會(huì)詳細(xì)描述)。


CRI 和 pause 容器

到了這個(gè)階段,大量的初始化工作都已經(jīng)完成,容器已經(jīng)準(zhǔn)備好開(kāi)始啟動(dòng)了,而容器是由容器運(yùn)行時(shí)(例如 Docker) 啟動(dòng)的。

為了更具可擴(kuò)展性, Kubelet 使用 CRI 來(lái)與具體的容器運(yùn)行時(shí)進(jìn)行交互。簡(jiǎn)而言之, CRI 提供了 Kubelet 和特定容器運(yùn)行時(shí)實(shí)現(xiàn)之間的抽象。通過(guò) protocol buffers(一種更快的 JSON) 和 gRPC API(一種非常適合執(zhí)行 Kubernetes 操作的API)進(jìn)行通信。

這是一個(gè)非??岬南敕?,因?yàn)橥ㄟ^(guò)在 Kubelet 和容器運(yùn)行時(shí)之間使用已定義的接口約定,容器編排的實(shí)際實(shí)現(xiàn)細(xì)節(jié)變得無(wú)關(guān)緊要。重要的是接口約定,這允許以最小的開(kāi)銷添加新的容器運(yùn)行時(shí),因?yàn)闆](méi)有核心 Kubernetes 代碼需要更改!

回到部署我們的容器,當(dāng)一個(gè) Pod 首次啟動(dòng)時(shí), Kubelet 調(diào)用 RunPodSandbox 遠(yuǎn)程過(guò)程命令。沙箱是描述一組容器的 CRI 術(shù)語(yǔ),在 Kubernetes 中對(duì)應(yīng)的是 Pod。這個(gè)術(shù)語(yǔ)是故意模糊的,因此其他不使用容器的運(yùn)行時(shí),不會(huì)失去其意義(想象一個(gè)基于 hypervisor 的運(yùn)行時(shí),沙箱可能指的是 VM)。

在我們的例子中,我們使用的是 Docker,所以在此容器運(yùn)行時(shí)中,創(chuàng)建沙箱涉及創(chuàng)建 pause 容器。

pause 容器像 Pod 中的所有其他容器的父級(jí)一樣,因?yàn)樗休d了工作負(fù)載容器最終將使用的許多 Pod 級(jí)資源。這些“資源”是 Linux Namespaces(IPC、Network、PID)。

pause 容器提供了一種托管所有這些 Namespaces 的方法,并允許子容器共享它們。成為同一 Network Namespace 一部分的好處是同一個(gè) Pod 中的容器可以使用 localhost 相互訪問(wèn)。

pause 容器的第二個(gè)好處與 PID Namespace 有關(guān)。在這些 Namespace 中,進(jìn)程形成一個(gè)分層樹,頂部的“init” 進(jìn)程負(fù)責(zé)“收獲”僵尸進(jìn)程。更多信息,請(qǐng)深入閱讀:https://www./en/al ... ainer

創(chuàng)建 pause 容器后,將開(kāi)始檢查磁盤狀態(tài)然后啟動(dòng)主容器。

CNI 和 Pod 網(wǎng)絡(luò)

現(xiàn)在,我們的 Pod 有了基本的骨架:一個(gè) pause 容器,它托管所有 Namespaces 以允許 Pod 間通信。但容器的網(wǎng)絡(luò)如何運(yùn)作以及建立的?

當(dāng) Kubelet 為 Pod 設(shè)置網(wǎng)絡(luò)時(shí),它會(huì)將任務(wù)委托給 CNI(Container Network Interface)插件,其運(yùn)行方式與 Container Runtime Interface 類似。簡(jiǎn)而言之,CNI 是一種抽象,允許不同的網(wǎng)絡(luò)提供商對(duì)容器使用不同的網(wǎng)絡(luò)實(shí)現(xiàn)。

Kubelet 通過(guò) stdin 將 JSON 數(shù)據(jù)(配置文件位于 /etc/cni/net.d 中)傳輸?shù)较嚓P(guān)的 CNI 二進(jìn)制文件(位于 /opt/cni/bin) 中與之交互。下面是 JSON 配置文件的一個(gè)簡(jiǎn)單示例 :
{
'cniVersion': '0.3.1',
'name': 'bridge',
'type': 'bridge',
'bridge': 'cnio0',
'isGateway': true,
'ipMasq': true,
'ipam': {
    'type': 'host-local',
    'ranges': [
      [{'subnet': '${POD_CIDR}'}]
    ],
    'routes': [{'dst': '0.0.0.0/0'}]
}


CNI 插件還可以通過(guò) CNI_ARGS 環(huán)境變量為 Pod 指定其他的元數(shù)據(jù),包括 Pod Name 和 Namespace。

接下來(lái)會(huì)發(fā)生什么取決于 CNI 插件,這里我們以 bridge CNI 插件為例:
  • 該插件首先會(huì)在 Root Network Namespace(也就是宿主機(jī)的 Network Namespace) 中設(shè)置本地 Linux 網(wǎng)橋,以便為該主機(jī)上的所有容器提供網(wǎng)絡(luò)服務(wù);
  • 然后將一個(gè)網(wǎng)絡(luò)接口 (veth 設(shè)備對(duì)的一端)插入到 pause 容器的 Network Namespace 中,并將另一端連接到網(wǎng)橋上。你可以這樣來(lái)理解 veth 設(shè)備對(duì):它就像一根很長(zhǎng)的管道,一端連接到容器,一端連接到 Root Network Namespace 中,允許數(shù)據(jù)包在中間傳輸;

  • 之后,為 pause 容器的網(wǎng)絡(luò)接口分配一個(gè) IP 并設(shè)置相應(yīng)的路由,于是 Pod 就有了自己的 IP。IP 的分配是由 JSON 配置文件中指定的 IPAM Plugin 實(shí)現(xiàn)的;
    • IPAM Plugin 的工作方式和 CNI 插件類似:通過(guò)二進(jìn)制文件調(diào)用并具有標(biāo)準(zhǔn)化的接口,每一個(gè) IPAM Plugin 都必須要確定容器網(wǎng)絡(luò)接口的 IP、子網(wǎng)以及網(wǎng)關(guān)和路由,并將信息返回給 CNI 插件。最常見(jiàn)的 IPAM Plugin 稱為 host-local,它從預(yù)定義的一組地址池為容器分配 IP 地址。它將相關(guān)信息保存在主機(jī)的文件系統(tǒng)中,從而確保了單個(gè)主機(jī)上每個(gè)容器 IP 地址的唯一性。

  • 對(duì)于 DNS, Kubelet 將為 CNI 插件指定 Kubernetes 集群內(nèi)部 DNS 服務(wù)器 IP 地址,確保正確設(shè)置容器的 resolv.conf 文件。


跨主機(jī)容器網(wǎng)絡(luò)

到目前為止,我們已經(jīng)描述了容器如何與宿主機(jī)進(jìn)行通信,但跨主機(jī)之間的容器如何通信呢?

通常情況下, Kubernetes 使用 Overlay 網(wǎng)絡(luò)來(lái)進(jìn)行跨主機(jī)容器通信,這是一種動(dòng)態(tài)同步多個(gè)主機(jī)間路由的方法。一個(gè)較常用的 Overlay 網(wǎng)絡(luò)插件是 flannel,它提供了跨節(jié)點(diǎn)的三層網(wǎng)絡(luò)。

Flannel 不會(huì)管容器與宿主機(jī)之間的通信(這是 CNI 插件的職責(zé)),但它對(duì)主機(jī)間的流量傳輸負(fù)責(zé)。為此,它為主機(jī)選擇一個(gè)子網(wǎng)并將其注冊(cè)到 etcd,然后保留集群路由的本地表示,并將傳出的數(shù)據(jù)包封裝在 UDP 數(shù)據(jù)報(bào)中,確保它到達(dá)正確的主機(jī)。

更多信息,請(qǐng)深入閱讀:https://github.com/coreos/flannel

啟動(dòng)容器

所有的網(wǎng)絡(luò)配置都已完成。還剩什么?真正啟動(dòng)工作負(fù)載容器!

一旦 sanbox 完成初始化并處于 active 狀態(tài), Kubelet 將開(kāi)始為其創(chuàng)建容器。首先啟動(dòng) PodSpec 中定義的 Init Container,然后再啟動(dòng)主容器,具體過(guò)程如下:
  • 拉取容器的鏡像。如果是私有倉(cāng)庫(kù)的鏡像,就會(huì)使用 PodSpec 中指定的 Secret 來(lái)拉取該鏡像;
  • 通過(guò) CRI 創(chuàng)建容器。Kubelet 使用 PodSpec 中的信息填充了 ContainerConfig 數(shù)據(jù)結(jié)構(gòu)(在其中定義了 command、image、labels、mounts、devices、environment variables 等),然后通過(guò) protobufs 發(fā)送給 CRI。對(duì)于 Docker 來(lái)說(shuō),它會(huì)將這些信息反序列化并填充到自己的配置信息中,然后再發(fā)送給 Dockerd 守護(hù)進(jìn)程。在這個(gè)過(guò)程中,它會(huì)將一些元數(shù)據(jù)(例如容器類型、日志路徑、sandbox ID 等)添加到容器中;
  • 然后 Kubelet 將容器注冊(cè)到 CPU 管理器,它通過(guò)使用 * UpdateContainerResources CRI 方法給容器分配給本地節(jié)點(diǎn)上的 CPU 資源;
  • 最后容器真正啟動(dòng);
  • 如果 Pod 中包含 Container Lifecycle Hooks,容器啟動(dòng)之后就會(huì)運(yùn)行這些 Hooks。Hook 的類型包括兩種:Exec(執(zhí)行一段命令) 和 HTTP(發(fā)送 HTTP 請(qǐng)求)。如果 PostStart Hook 啟動(dòng)的時(shí)間過(guò)長(zhǎng)、掛起或者失敗,容器將永遠(yuǎn)不會(huì)變成 Running 狀態(tài)。


Wrap-up

最后的最后,現(xiàn)在我們的集群上應(yīng)該會(huì)運(yùn)行三個(gè)容器,分布在一個(gè)或多個(gè)工作節(jié)點(diǎn)上。所有的網(wǎng)絡(luò)、數(shù)據(jù)卷和秘鑰都由 Kubelet 填充,并通過(guò) CRI 接口添加到容器中,配置成功!

原文地址:https://github.com/jamiehannaf ... n-k8s

源代碼版譯文地址(強(qiáng)烈建議閱讀):https://github.com/bbbmj/what-happens-when-k8s

原文鏈接:https://mp.weixin.qq.com/s/KqRt1eav5mNaut5PK45D-Q
【編者的話】為了確保整體的簡(jiǎn)單性和易上手,有時(shí) Kubernetes 會(huì)通過(guò)一些簡(jiǎn)單的抽象隱去操作背后的復(fù)雜邏輯,但作為一名有夢(mèng)想的工程師,掌握其背后的真正思路是十分有必要的。本文以 Kubectl 創(chuàng)建 Pod 為例,向你揭露從客戶端到 Kubelet 的請(qǐng)...

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多