Operator
? Operator 的工作原理俘枫,實際上是利用了 Kubernetes 的自定義 API 資源(CRD),來描述我們想要部署的“有狀態(tài)應(yīng)用”嘶炭;然后在自定義控制器里,根據(jù)自定義 API 對象的變化腥放,來完成具體的部署和運(yùn)維工作蜀铲。
? Etcd Operator 部署 Etcd 集群边琉,采用的是靜態(tài)集群(Static)的方式。
靜態(tài)集群的好處是记劝,它不必依賴于一個額外的服務(wù)發(fā)現(xiàn)機(jī)制來組建集群变姨,非常適合本地容器化部署。而它的難點厌丑,則在于你必須在部署的時候定欧,就規(guī)劃好這個集群的拓?fù)浣Y(jié)構(gòu),并且能夠知道這些節(jié)點固定的 IP 地址怒竿。
? 首先砍鸠,Etcd Operator 會創(chuàng)建一個“種子節(jié)點”;然后耕驰,Etcd Operator 會不斷創(chuàng)建新的 Etcd 節(jié)點爷辱,然后將它們逐一加入到這個集群當(dāng)中,直到集群的節(jié)點數(shù)等于 size耍属。這就意味著托嚣,在生成不同角色的 Etcd Pod 時,Operator 需要能夠區(qū)分種子節(jié)點與普通節(jié)點厚骗。–initial-cluster-state值設(shè)為 new 時示启,就代表了該節(jié)點是種子節(jié)點;值設(shè)為 existing领舰,那就是說明這個節(jié)點是一個普通節(jié)點
? Etcd Operator 啟動要做的第一件事( c.initResource)夫嗓,是創(chuàng)建 EtcdCluster 對象所需要的 CRD,即:前面提到的etcdclusters.etcd.database.coreos.com
冲秽。這樣 Kubernetes 就能夠“認(rèn)識”EtcdCluster 這個自定義 API 資源了舍咖。
? 而接下來,Etcd Operator 會定義一個 EtcdCluster 對象的 Informer锉桑。
? 當(dāng)etcd的yaml文件提交到k8s之后排霉,Etcd Operator 的 Informer,就會立刻“感知”到一個新的 EtcdCluster 對象被創(chuàng)建了出來民轴。所以攻柠,EventHandler 里的“添加”事件會被觸發(fā)——在 Etcd Operator 內(nèi)部創(chuàng)建一個對應(yīng)的 Cluster 對象
? cluster兩個工作:
1. **Bootstrap,即:創(chuàng)建一個單節(jié)點的種子集群后裸。**
2. **啟動該集群所對應(yīng)的控制循環(huán)瑰钮。**
以 addOneMember 方法為例,它執(zhí)行的流程如下所示:
- 生成一個新節(jié)點的 Pod 的名字微驶,比如:example-etcd-cluster-v6v6s6stxd浪谴;
- 調(diào)用 Etcd Client,執(zhí)行前面提到過的 etcdctl member add example-etcd-cluster-v6v6s6stxd 命令;
- 使用這個 Pod 名字苟耻,和已經(jīng)存在的所有節(jié)點列表篇恒,組合成一個新的 initial-cluster 字段的值;
- 使用這個 initial-cluster 的值梁呈,生成這個 Pod 里 Etcd 容器的啟動命令婚度。
PV、PVC官卡、StorageClass
PV:持久化存儲數(shù)據(jù)卷
? 這個 API 對象主要定義的是一個持久化存儲在宿主機(jī)上的目錄蝗茁,比如一個 NFS 的掛載目錄。由運(yùn)維人員事先創(chuàng)建在 Kubernetes 集群里待用的
PVC:是 Pod 所希望使用的持久化存儲的屬性寻咒。
? 比如哮翘,Volume 存儲的大小、可讀寫權(quán)限等等毛秘。PVC 對象通常由開發(fā)人員創(chuàng)建饭寺;或者以 PVC 模板的方式成為 StatefulSet 的一部分,然后由 StatefulSet 控制器負(fù)責(zé)創(chuàng)建帶編號的 PVC叫挟。
? 而用戶創(chuàng)建的 PVC 要真正被容器使用起來艰匙,就必須先和某個符合條件的 PV 進(jìn)行綁定。這里要檢查的條件抹恳,包括兩部分:
- 第一個條件员凝,當(dāng)然是 PV 和 PVC 的 spec 字段。比如奋献,PV 的存儲(storage)大小健霹,就必須滿足 PVC 的要求。
- 而第二個條件瓶蚂,則是 PV 和 PVC 的 storageClassName 字段必須一樣糖埋。這個機(jī)制我會在本篇文章的最后一部分專門介紹。
? 在成功地將 PVC 和 PV 進(jìn)行綁定之后窃这,Pod 就能夠像使用 hostPath 等常規(guī)類型的 Volume 一樣瞳别,在自己的 YAML 文件里聲明使用這個 PVC 了
? PVC 可以理解為持久化存儲的“接口”,它提供了對某種持久化存儲的描述杭攻,但不提供具體的實現(xiàn)洒试;而這個持久化存儲的實現(xiàn)部分則由 PV 負(fù)責(zé)完成。
? Volume Controller 維護(hù)著多個控制循環(huán)朴上,其中有一個循環(huán),扮演的就是撮合 PV 和 PVC 的“紅娘”的角色卒煞。它的名字叫作 PersistentVolumeController痪宰。他會不斷地查看當(dāng)前每一個 PVC,是不是已經(jīng)處于 Bound(已綁定)狀態(tài)。如果不是衣撬,那它就會遍歷所有的乖订、可用的 PV,并嘗試將其與這個“單身”的 PVC 進(jìn)行綁定具练。
PV 對象乍构,是如何變成容器里的一個持久化存儲的
? 所謂容器的 Volume,其實就是將一個宿主機(jī)上的目錄扛点,跟一個容器里的目錄綁定掛載在了一起哥遮。而所謂的“持久化 Volume”,指的就是這個宿主機(jī)上的目錄陵究,具備“持久性”眠饮。
? Kubernetes 需要做的工作,就是使用這些存儲服務(wù)铜邮,來為容器準(zhǔn)備一個持久化的宿主機(jī)目錄仪召,以供將來進(jìn)行綁定掛載時使用。而所謂“持久化”松蒜,指的是容器在這個目錄里寫入的文件扔茅,都會保存在遠(yuǎn)程存儲中,從而使得這個目錄具備了“持久性”秸苗。
? 這個準(zhǔn)備“持久化”宿主機(jī)目錄的過程召娜,我們可以形象地稱為“兩階段處理”。
? 當(dāng)一個 Pod 調(diào)度到一個節(jié)點上之后难述,kubelet 就要負(fù)責(zé)為這個 Pod 創(chuàng)建它的 Volume 目錄萤晴。默認(rèn)情況下,kubelet 為 Volume 創(chuàng)建的目錄是如下所示的一個宿主機(jī)上的路徑:
/var/lib/kubelet/pods/<Pod 的 ID>/volumes/kubernetes.io~<Volume 類型 >/<Volume 名字 >
? 這一步為虛擬機(jī)掛載遠(yuǎn)程磁盤的操作胁后,對應(yīng)的正是“兩階段處理”的第一階段店读。在 Kubernetes 中,我們把這個階段稱為 Attach攀芯。
? 第二個操作屯断,即:格式化這個磁盤設(shè)備,然后將它掛載到宿主機(jī)指定的掛載點上侣诺。這個將磁盤設(shè)備格式化并掛載到 Volume 宿主機(jī)目錄的操作殖演,對應(yīng)的正是“兩階段處理”的第二個階段,我們一般稱為:Mount年鸳。
? 經(jīng)過了“兩階段處理”趴久,我們就得到了一個“持久化”的 Volume 宿主機(jī)目錄。所以搔确,接下來彼棍,kubelet 只要把這個 Volume 目錄通過 CRI 里的 Mounts 參數(shù)灭忠,傳遞給 Docker,然后就可以為 Pod 里的容器掛載這個“持久化”的 Volume 了座硕。
StorageClass
人工管理 PV 的方式: Static Provisioning弛作;
自動創(chuàng)建 PV 的機(jī)制:Dynamic Provisioning。
Dynamic Provisioning 機(jī)制工作的核心华匾,在于一個名叫 StorageClass 的 API 對象映琳。
而 StorageClass 對象的作用,其實就是創(chuàng)建 PV 的模板蜘拉。
具體地說萨西,StorageClass 對象會定義如下兩個部分內(nèi)容:
- 第一,PV 的屬性诸尽。比如原杂,存儲類型、Volume 的大小等等您机。
- 第二穿肄,創(chuàng)建這種 PV 需要用到的存儲插件。比如际看,Ceph 等等咸产。
有了這樣兩個信息之后,Kubernetes 就能夠根據(jù)用戶提交的 PVC仲闽,找到一個對應(yīng)的 StorageClass 了脑溢。然后,Kubernetes 就會調(diào)用該 StorageClass 聲明的存儲插件赖欣,創(chuàng)建出需要的 PV屑彻。
- PVC 描述的,是 Pod 想要使用的持久化存儲的屬性顶吮,比如存儲的大小社牲、讀寫權(quán)限等。
- PV 描述的悴了,則是一個具體的 Volume 的屬性搏恤,比如 Volume 的類型、掛載目錄湃交、遠(yuǎn)程存儲服務(wù)器地址等熟空。
- 而 StorageClass 的作用,則是充當(dāng) PV 的模板搞莺。并且息罗,只有同屬于一個 StorageClass 的 PV 和 PVC,才可以綁定在一起才沧。
總結(jié):
? 用戶提交請求創(chuàng)建pod阱当,Kubernetes發(fā)現(xiàn)這個pod聲明使用了PVC俏扩,那就靠PersistentVolumeController幫它找一個PV配對。沒有現(xiàn)成的PV弊添,就去找對應(yīng)的StorageClass,幫它新創(chuàng)建一個PV捌木,然后和PVC完成綁定油坝。新創(chuàng)建的PV,還只是一個API 對象刨裆,需要經(jīng)過“兩階段處理”變成宿主機(jī)上的“持久化 Volume”才真正有用:
? 第一階段由運(yùn)行在master上的AttachDetachController負(fù)責(zé)澈圈,為這個PV完成 Attach 操作,為宿主機(jī)掛載遠(yuǎn)程磁盤帆啃;
? 第二階段是運(yùn)行在每個節(jié)點上kubelet組件的內(nèi)部瞬女,把第一步attach的遠(yuǎn)程磁盤 mount 到宿主機(jī)目錄。這個控制循環(huán)叫VolumeManagerReconciler努潘,運(yùn)行在獨立的Goroutine诽偷,不會阻塞kubelet主循環(huán)。
? 完成這兩步疯坤,PV對應(yīng)的“持久化 Volume”就準(zhǔn)備好了报慕,POD可以正常啟動,將“持久化 Volume”掛載在容器內(nèi)指定的路徑压怠。
Local Persistent Volume
? 本地持久化存儲眠冈,必須具備數(shù)據(jù)備份和恢復(fù)的能力。在開始使用 Local Persistent Volume 之前菌瘫,首先需要在集群里配置好磁盤或者塊設(shè)備蜗顽。
- 第一種,當(dāng)然就是給宿主機(jī)掛載并格式化一個可用的本地磁盤雨让,這也是最常規(guī)的操作雇盖;
- 第二種,對于實驗環(huán)境宫患,可以在宿主機(jī)上掛載幾個 RAM Disk(內(nèi)存盤)來模擬本地磁盤刊懈。
hostPath volume存在的問題
? 過去我們經(jīng)常會通過hostPath volume
讓Pod能夠使用本地存儲,將Node文件系統(tǒng)中的文件或者目錄掛載到容器內(nèi)娃闲,但是hostPath volume
的使用是很難受的虚汛,并不適合在生產(chǎn)環(huán)境中使用。
- 由于集群內(nèi)每個節(jié)點的差異化皇帮,要使用hostPath Volume卷哩,我們需要通過NodeSelector等方式進(jìn)行精確調(diào)度,這種事情多了属拾,你就會不耐煩了将谊。
- 注意DirectoryOrCreate和FileOrCreate兩種類型的hostPath冷溶,當(dāng)Node上沒有對應(yīng)的File/Directory時,你需要保證kubelet有在Node上Create File/Directory的權(quán)限尊浓。
- 另外逞频,如果Node上的文件或目錄是由root創(chuàng)建的,掛載到容器內(nèi)之后栋齿,你通常還要保證容器內(nèi)進(jìn)程有權(quán)限對該文件或者目錄進(jìn)行寫入苗胀,比如你需要以root用戶啟動進(jìn)程并運(yùn)行于privileged容器,或者你需要事先修改好Node上的文件權(quán)限配置瓦堵。
- Scheduler并不會考慮hostPath volume的大小基协,hostPath也不能申明需要的storage size,這樣調(diào)度時存儲的考慮菇用,就需要人為檢查并保證澜驮。
- StatefulSet無法使用hostPath volume,已經(jīng)寫好的使用共享存儲的Helm Chart不能兼容hostPath volume惋鸥,需要修改的地方還不少杂穷,這也挺難受的。
local persistent volume工作機(jī)制
通常什么情況會使用Local PV呢揩慕?
- 比如節(jié)點上的目錄數(shù)據(jù)是從遠(yuǎn)程的網(wǎng)絡(luò)存儲上掛載或者預(yù)先讀取到本地的亭畜,為了能加速Pod讀取這些數(shù)據(jù)的速度,相當(dāng)于起Cache作用迎卤,這種情況下因為只讀拴鸵,不存在懼怕數(shù)據(jù)丟失。這種AI訓(xùn)練中存在需要重復(fù)利用并且訓(xùn)練數(shù)據(jù)巨大的時候可能會采取的方式蜗搔。
- 如果本地節(jié)點上目錄/磁盤實際是具有副本/分片機(jī)制的分布式存儲(比如gluster, ceph等)掛載過來的劲藐,這種情況也可以使用local pv。
和HostPath Volume的區(qū)別
Local PV出現(xiàn)之前樟凄,使用本地磁盤的方法是HostPath Volume聘芜,同為使用本地磁盤,區(qū)別在哪呢缝龄?
- 最重要的區(qū)別汰现,就是Local PV和具體節(jié)點是有關(guān)聯(lián)的,這意味著使用了Local PV的pod叔壤,重啟多次都會被Kubernetes scheduler調(diào)度到同一節(jié)點瞎饲,而如果用的是HostPath Volume,每次重啟都可能被Kubernetes scheduler調(diào)度到新的節(jié)點炼绘,然后使用同樣的本地路徑嗅战;
- 當(dāng)我們要用HostPath Volume的時候,既可以在PVC聲明俺亮,又可以直接寫到Pod的配置中驮捍,但是Local PV只能在PVC聲明疟呐,對于PV資源,通常都有專人管理东且,這樣就避免了Pod開發(fā)者擅自使用本地磁盤帶來的沖突和風(fēng)險启具;
- 另外要注意的是,HostPath Volume和Local PV都是在使用本地磁盤苇倡,和常見的分布式文件系統(tǒng)相比富纸,本地磁盤故障會導(dǎo)致數(shù)據(jù)丟失,保存重要數(shù)據(jù)請勿使用HostPath Volume和Local PV旨椒;
? StorageClass 里的 volumeBindingMode=WaitForFirstConsumer 的含義,就是告訴 Kubernetes 里的 Volume 控制循環(huán)(“紅娘”):雖然你已經(jīng)發(fā)現(xiàn)這個 StorageClass 關(guān)聯(lián)的 PVC 與 PV 可以綁定在一起堵漱,但請不要現(xiàn)在就執(zhí)行綁定操作(即:設(shè)置 PVC 的 VolumeName 字段)综慎。而要等到第一個聲明使用該 PVC 的 Pod 出現(xiàn)在調(diào)度器之后,調(diào)度器再綜合考慮所有的調(diào)度規(guī)則勤庐,當(dāng)然也包括每個 PV 所在的節(jié)點位置示惊,來統(tǒng)一決定,這個 Pod 聲明的 PVC愉镰,到底應(yīng)該跟哪個 PV 進(jìn)行綁定米罚。
? 所以,通過這個延遲綁定機(jī)制丈探,原本實時發(fā)生的 PVC 和 PV 的綁定過程录择,就被延遲到了 Pod 第一次調(diào)度的時候在調(diào)度器中進(jìn)行,從而保證了這個綁定結(jié)果不會影響 Pod 的正常調(diào)度碗降。
Flexvolume 與CSI
? 無論是 FlexVolume隘竭,還是 Kubernetes 內(nèi)置的其他存儲插件,它們實際上擔(dān)任的角色讼渊,僅僅是 Volume 管理中的“Attach 階段”和“Mount 階段”的具體執(zhí)行者动看。而像 Dynamic Provisioning 這樣的功能,就不是存儲插件的責(zé)任爪幻,而是 Kubernetes 本身存儲管理功能的一部分菱皆。
? 相比之下,CSI 插件體系的設(shè)計思想挨稿,就是把這個 Provision 階段仇轻,以及 Kubernetes 里的一部分存儲管理功能,從主干代碼里剝離出來叶组,做成了幾個單獨的組件拯田。這些組件會通過 Watch API 監(jiān)聽 Kubernetes 里與存儲相關(guān)的事件變化,比如 PVC 的創(chuàng)建甩十,來執(zhí)行具體的存儲管理動作船庇。而這些管理動作吭产,比如“Attach 階段”和“Mount 階段”的具體操作,實際上就是通過調(diào)用 CSI 插件來完成的鸭轮。
? 可以看到臣淤,這套存儲插件體系多了三個獨立的外部組件(External Components),即:Driver Registrar窃爷、External Provisioner 和 External Attacher邑蒋,對應(yīng)的正是從 Kubernetes 項目里面剝離出來的那部分存儲管理功能。
? 最右側(cè)的部分按厘,就是需要我們編寫代碼來實現(xiàn)的 CSI 插件医吊。一個 CSI 插件只有一個二進(jìn)制文件,但它會以 gRPC 的方式對外提供三個服務(wù)(gRPC Service)逮京,分別叫作:CSI Identity卿堂、CSI Controller 和 CSI Node。
其中懒棉,Driver Registrar 組件草描,負(fù)責(zé)將插件注冊到 kubelet 里面(這可以類比為,將可執(zhí)行文件放在插件目錄下)策严。而在具體實現(xiàn)上穗慕,Driver Registrar 需要請求 CSI 插件的 Identity 服務(wù)來獲取插件信息。
-
而External Provisioner 組件妻导,負(fù)責(zé)的正是 Provision 階段逛绵。在具體實現(xiàn)上,External Provisioner 監(jiān)聽(Watch)了 APIServer 里的 PVC 對象栗竖。當(dāng)一個 PVC 被創(chuàng)建時暑脆,它就會調(diào)用 CSI Controller 的 CreateVolume 方法,為你創(chuàng)建對應(yīng) PV狐肢。
此外添吗,如果你使用的存儲是公有云提供的磁盤(或者塊設(shè)備)的話,這一步就需要調(diào)用公有云(或者塊設(shè)備服務(wù))的 API 來創(chuàng)建這個 PV 所描述的磁盤(或者塊設(shè)備)了份名。
最后一個External Attacher 組件碟联,負(fù)責(zé)的正是“Attach 階段”。在具體實現(xiàn)上僵腺,它監(jiān)聽了 APIServer 里 VolumeAttachment 對象的變化鲤孵。VolumeAttachment 對象是 Kubernetes 確認(rèn)一個 Volume 可以進(jìn)入“Attach 階段”的重要標(biāo)志
- CSI 插件的 CSI Identity 服務(wù),負(fù)責(zé)對外暴露這個插件本身的信息
- CSI Controller 服務(wù)辰如,定義的則是對 CSI Volume(對應(yīng) Kubernetes 里的 PV)的管理接口普监,比如:創(chuàng)建和刪除 CSI Volume、對 CSI Volume 進(jìn)行 Attach/Dettach(在 CSI 里,這個操作被叫作 Publish/Unpublish)凯正,以及對 CSI Volume 進(jìn)行 Snapshot 等
- CSI Volume 需要在宿主機(jī)上執(zhí)行的操作毙玻,都定義在了 CSI Node 服務(wù)里面。
總結(jié):相比于 FlexVolume廊散,CSI 的設(shè)計思想桑滩,把插件的職責(zé)從“兩階段處理”,擴(kuò)展成了 Provision允睹、Attach 和 Mount 三個階段运准。其中,Provision 等價于“創(chuàng)建磁盤”缭受,Attach 等價于“掛載磁盤到虛擬機(jī)”胁澳,Mount 等價于“將該磁盤格式化后,掛載在 Volume 的宿主機(jī)目錄上”米者。
? 有了 StorageClass听哭,External Provisoner 就會為集群中新出現(xiàn)的 PVC 自動創(chuàng)建出 PV,然后調(diào)用 CSI 插件創(chuàng)建出這個 PV 對應(yīng)的 Volume塘雳,這正是 CSI 體系中 Dynamic Provisioning 的實現(xiàn)方式。
部署 CSI 插件的常用原則是:
- 通過 DaemonSet 在每個節(jié)點上都啟動一個 CSI 插件普筹,來為 kubelet 提供 CSI Node 服務(wù)**败明。
- 通過 StatefulSet 在任意一個節(jié)點上再啟動一個 CSI 插件,為 External Components 提供 CSI Controller 服務(wù)太防。
當(dāng)用戶創(chuàng)建了一個 PVC 之后妻顶,你前面部署的 StatefulSet 里的 External Provisioner 容器,就會監(jiān)聽到這個 PVC 的誕生蜒车,然后調(diào)用同一個 Pod 里的 CSI 插件的 CSI Controller 服務(wù)的 CreateVolume 方法讳嘱,為你創(chuàng)建出對應(yīng)的 PV。
這時候酿愧,運(yùn)行在 Kubernetes Master 節(jié)點上的 Volume Controller沥潭,就會通過 PersistentVolumeController 控制循環(huán),發(fā)現(xiàn)這對新創(chuàng)建出來的 PV 和 PVC嬉挡,并且看到它們聲明的是同一個 StorageClass钝鸽。所以,它會把這一對 PV 和 PVC 綁定起來庞钢,使 PVC 進(jìn)入 Bound 狀態(tài)拔恰。
然后,用戶創(chuàng)建了一個聲明使用上述 PVC 的 Pod基括,并且這個 Pod 被調(diào)度器調(diào)度到了宿主機(jī) A 上颜懊。這時候,Volume Controller 的 AttachDetachController 控制循環(huán)就會發(fā)現(xiàn),上述 PVC 對應(yīng)的 Volume河爹,需要被 Attach 到宿主機(jī) A 上匠璧。所以,AttachDetachController 會創(chuàng)建一個 VolumeAttachment 對象昌抠,這個對象攜帶了宿主機(jī) A 和待處理的 Volume 的名字患朱。
這樣,StatefulSet 里的 External Attacher 容器炊苫,就會監(jiān)聽到這個 VolumeAttachment 對象的誕生裁厅。于是,它就會使用這個對象里的宿主機(jī)和 Volume 名字侨艾,調(diào)用同一個 Pod 里的 CSI 插件的 CSI Controller 服務(wù)的 ControllerPublishVolume 方法执虹,完成“Attach 階段”。
上述過程完成后唠梨,運(yùn)行在宿主機(jī) A 上的 kubelet袋励,就會通過 VolumeManagerReconciler 控制循環(huán),發(fā)現(xiàn)當(dāng)前宿主機(jī)上有一個 Volume 對應(yīng)的存儲設(shè)備(比如磁盤)已經(jīng)被 Attach 到了某個設(shè)備目錄下当叭。于是 kubelet 就會調(diào)用同一臺宿主機(jī)上的 CSI 插件的 CSI Node 服務(wù)的 NodeStageVolume 和 NodePublishVolume 方法茬故,完成這個 Volume 的“Mount 階段”。
至此蚁鳖,一個完整的持久化 Volume 的創(chuàng)建和掛載流程就結(jié)束了磺芭。