目錄
- StatefulSet 對(duì)存儲(chǔ)狀態(tài)的管理機(jī)制
- 第一步:定義一個(gè) PVC,聲明想要的 Volume 的屬性
- 第二步:在應(yīng)用的 Pod 中,聲明使用這個(gè) PVC
- 常見的 PV 對(duì)象的 YAML 文件
StatefulSet 對(duì)存儲(chǔ)狀態(tài)的管理機(jī)制
這個(gè)機(jī)制,主要使用的是一個(gè)叫作 Persistent Volume Claim 的功能。
要在一個(gè) Pod 里聲明 Volume,只要在 Pod 里加上 spec.volumes 字段即可。然后,你就可以在這個(gè)字段里定義一個(gè)具體類型的 Volume 了,比如:hostPath。
可是,你有沒有想過這樣一個(gè)場景:如果你并不知道有哪些 Volume 類型可以用,要怎么辦呢?
更具體地說,作為一個(gè)應(yīng)用開發(fā)者,我可能對(duì)持久化存儲(chǔ)項(xiàng)目(比如 Ceph、GlusterFS 等)一竅不通,也不知道公司的 Kubernetes 集群里到底是怎么搭建出來的,我也自然不會(huì)編寫它們對(duì)應(yīng)的 Volume 定義文件。
這些關(guān)于 Volume 的管理和遠(yuǎn)程持久化存儲(chǔ)的知識(shí),不僅超越了開發(fā)者的知識(shí)儲(chǔ)備,還會(huì)有暴露公司基礎(chǔ)設(shè)施秘密的風(fēng)險(xiǎn)。
比如,下面這個(gè)例子,就是一個(gè)聲明了 Ceph RBD 類型 Volume 的 Pod:
apiVersion: v1 kind: Pod metadata: name: rbd spec: containers: - image: kubernetes/pause name: rbd-rw volumeMounts: - name: rbdpd mountPath: /mnt/rbd volumes: - name: rbdpd rbd: monitors: - '10.16.154.78:6789' - '10.16.154.82:6789' - '10.16.154.83:6789' pool: kube image: foo fsType: ext4 readOnly: true user: admin keyring: /etc/ceph/keyring imageformat: "2" imagefeatures: "layering"
其一,如果不懂得 Ceph RBD 的使用方法,那么這個(gè) Pod 里 Volumes 字段,你十有八九也完全看不懂。其二,這個(gè) Ceph RBD 對(duì)應(yīng)的存儲(chǔ)服務(wù)器的地址、用戶名、授權(quán)文件的位置,也都被輕易地暴露給了全公司的所有開發(fā)人員,這是一個(gè)典型的信息被“過度暴露”的例子。
這也是為什么,在后來的演化中,Kubernetes 項(xiàng)目引入了一組叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 對(duì)象,大大降低了用戶聲明和使用持久化 Volume 的門檻。
舉個(gè)例子,有了 PVC 之后,一個(gè)開發(fā)人員想要使用一個(gè) Volume,只需要簡單的兩步即可。
第一步:定義一個(gè) PVC,聲明想要的 Volume 的屬性
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pv-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
可以看到,在這個(gè) PVC 對(duì)象里,不需要任何關(guān)于 Volume 細(xì)節(jié)的字段,只有描述性的屬性和定義。比如,storage: 1Gi,表示我想要的 Volume 大小至少是 1 GiB;accessModes: ReadWriteOnce,表示這個(gè) Volume 的掛載方式是可讀寫,并且只能被掛載在一個(gè)節(jié)點(diǎn)上而非被多個(gè)節(jié)點(diǎn)共享。
第二步:在應(yīng)用的 Pod 中,聲明使用這個(gè) PVC
apiVersion: v1 kind: Pod metadata: name: pv-pod spec: containers: - name: pv-container image: nginx ports: - containerPort: 80 name: "http-server" volumeMounts: - mountPath: "/usr/share/nginx/html" name: pv-storage volumes: - name: pv-storage persistentVolumeClaim: claimName: pv-claim
可以看到,在這個(gè) Pod 的 Volumes 定義中,我們只需要聲明它的類型是 persistentVolumeClaim,然后指定 PVC 的名字,而完全不必關(guān)心 Volume 本身的定義。
這時(shí)候,只要我們創(chuàng)建這個(gè) PVC 對(duì)象,Kubernetes 就會(huì)自動(dòng)為它綁定一個(gè)符合條件的 Volume。可是,這些符合條件的 Volume 又是從哪里來的呢?
答案是,它們來自于由運(yùn)維人員維護(hù)的 PV(Persistent Volume)對(duì)象。
常見的 PV 對(duì)象的 YAML 文件
kind: PersistentVolume apiVersion: v1 metadata: name: pv-volume labels: type: local spec: capacity: storage: 10Gi rbd: monitors: - '10.16.154.78:6789' - '10.16.154.82:6789' - '10.16.154.83:6789' pool: kube image: foo fsType: ext4 readOnly: true user: admin keyring: /etc/ceph/keyring imageformat: "2" imagefeatures: "layering"
可以看到,這個(gè) PV 對(duì)象的 spec.rbd 字段,正是我們前面介紹過的 Ceph RBD Volume 的詳細(xì)定義。而且,它還聲明了這個(gè) PV 的容量是 10 GiB。這樣,Kubernetes 就會(huì)為我們剛剛創(chuàng)建的 PVC 對(duì)象綁定這個(gè) PV。
所以,Kubernetes 中 PVC 和 PV 的設(shè)計(jì),實(shí)際上類似于“接口”和“實(shí)現(xiàn)”的思想。開發(fā)者只要知道并會(huì)使用“接口”,即:PVC;而運(yùn)維人員則負(fù)責(zé)給“接口”綁定具體的實(shí)現(xiàn),即:PV。
這種解耦,就避免了因?yàn)橄蜷_發(fā)者暴露過多的存儲(chǔ)系統(tǒng)細(xì)節(jié)而帶來的隱患。此外,這種職責(zé)的分離,往往也意味著出現(xiàn)事故時(shí)可以更容易定位問題和明確責(zé)任,從而避免“扯皮”現(xiàn)象的出現(xiàn)。
而 PVC、PV 的設(shè)計(jì),也使得 StatefulSet 對(duì)存儲(chǔ)狀態(tài)的管理成為了可能。
apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.9.1 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
這次,我們?yōu)檫@個(gè) StatefulSet 額外添加了一個(gè) volumeClaimTemplates 字段。從名字就可以看出來,它跟 Deployment 里 Pod 模板(PodTemplate)的作用類似。也就是說,凡是被這個(gè) StatefulSet 管理的 Pod,都會(huì)聲明一個(gè)對(duì)應(yīng)的 PVC;而這個(gè) PVC 的定義,就來自于 volumeClaimTemplates 這個(gè)模板字段。更重要的是,這個(gè) PVC 的名字,會(huì)被分配一個(gè)與這個(gè) Pod 完全一致的編號(hào)。
這個(gè)自動(dòng)創(chuàng)建的 PVC,與 PV 綁定成功后,就會(huì)進(jìn)入 Bound 狀態(tài),這就意味著這個(gè) Pod 可以掛載并使用這個(gè) PV 了。
如果你還是不太理解 PVC 的話,可以先記住這樣一個(gè)結(jié)論:PVC 其實(shí)就是一種特殊的 Volume。只不過一個(gè) PVC 具體是什么類型的 Volume,要在跟某個(gè) PV 綁定之后才知道。關(guān)于 PV、PVC 更詳細(xì)的知識(shí),我會(huì)在容器存儲(chǔ)部分做進(jìn)一步解讀。
當(dāng)然,PVC 與 PV 的綁定得以實(shí)現(xiàn)的前提是,運(yùn)維人員已經(jīng)在系統(tǒng)里創(chuàng)建好了符合條件的 PV(比如,我們?cè)谇懊嬗玫降?pv-volume);或者,你的 Kubernetes 集群運(yùn)行在公有云上,這樣 Kubernetes 就會(huì)通過 Dynamic Provisioning 的方式,自動(dòng)為你創(chuàng)建與 PVC 匹配的 PV。
所以,我們?cè)谑褂?kubectl create 創(chuàng)建了 StatefulSet 之后,就會(huì)看到 Kubernetes 集群里出現(xiàn)了兩個(gè) PVC
可以看到,這些 PVC,都以“<PVC 名字 >-<StatefulSet 名字 >-< 編號(hào) >”的方式命名,并且處于 Bound 狀態(tài)。
我們前面已經(jīng)講到過,這個(gè) StatefulSet 創(chuàng)建出來的所有 Pod,都會(huì)聲明使用編號(hào)的 PVC。比如,在名叫 web-0 的 Pod 的 volumes 字段,它會(huì)聲明使用名叫 www-web-0 的 PVC,從而掛載到這個(gè) PVC 所綁定的 PV。
所以,我們就可以使用如下所示的指令,在 Pod 的 Volume 目錄里寫入一個(gè)文件,來驗(yàn)證一下上述 Volume 的分配情況
for i in 0 1; do kubectl exec web-$i — sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done
如上所示,通過 kubectl exec 指令,我們?cè)诿總€(gè) Pod 的 Volume 目錄里,寫入了一個(gè) index.html 文件。這個(gè)文件的內(nèi)容,正是 Pod 的 hostname。比如,我們?cè)?web-0 的 index.html 里寫入的內(nèi)容就是 "hello web-0"。
此時(shí),如果你在這個(gè) Pod 容器里訪問“http://localhost”,你實(shí)際訪問到的就是 Pod 里 Nginx 服務(wù)器進(jìn)程,而它會(huì)為你返回 /usr/share/nginx/html/index.html 里的內(nèi)容。這個(gè)操作的執(zhí)行方法如下所示:
$ for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done hello web-0 hello web-1
如果你使用 kubectl delete 命令刪除這兩個(gè) Pod,這些 Volume 里的文件會(huì)不會(huì)丟失呢?
可以看到,正如我們前面介紹過的,在被刪除之后,這兩個(gè) Pod 會(huì)被按照編號(hào)的順序被重新創(chuàng)建出來。而這時(shí)候,如果你在新創(chuàng)建的容器里通過訪問“http://localhost”的方式去訪問 web-0 里的 Nginx 服務(wù)
就會(huì)發(fā)現(xiàn),這個(gè)請(qǐng)求依然會(huì)返回:hello web-0。也就是說,原先與名叫 web-0 的 Pod 綁定的 PV,在這個(gè) Pod 被重新創(chuàng)建之后,依然同新的名叫 web-0 的 Pod 綁定在了一起。對(duì)于 Pod web-1 來說,也是完全一樣的情況。
這是怎么做到的呢?
其實(shí),我和你分析一下 StatefulSet 控制器恢復(fù)這個(gè) Pod 的過程,你就可以很容易理解了。
首先,當(dāng)你把一個(gè) Pod,比如 web-0,刪除之后,這個(gè) Pod 對(duì)應(yīng)的 PVC 和 PV,并不會(huì)被刪除,而這個(gè) Volume 里已經(jīng)寫入的數(shù)據(jù),也依然會(huì)保存在遠(yuǎn)程存儲(chǔ)服務(wù)里(比如,我們?cè)谶@個(gè)例子里用到的 Ceph 服務(wù)器)。
此時(shí),StatefulSet 控制器發(fā)現(xiàn),一個(gè)名叫 web-0 的 Pod 消失了。所以,控制器就會(huì)重新創(chuàng)建一個(gè)新的、名字還是叫作 web-0 的 Pod 來,“糾正”這個(gè)不一致的情況。
需要注意的是,在這個(gè)新的 Pod 對(duì)象的定義里,它聲明使用的 PVC 的名字,還是叫作:www-web-0。這個(gè) PVC 的定義,還是來自于 PVC 模板(volumeClaimTemplates),這是 StatefulSet 創(chuàng)建 Pod 的標(biāo)準(zhǔn)流程。
所以,在這個(gè)新的 web-0 Pod 被創(chuàng)建出來之后,Kubernetes 為它查找名叫 www-web-0 的 PVC 時(shí),就會(huì)直接找到舊 Pod 遺留下來的同名的 PVC,進(jìn)而找到跟這個(gè) PVC 綁定在一起的 PV。
這樣,新的 Pod 就可以掛載到舊 Pod 對(duì)應(yīng)的那個(gè) Volume,并且獲取到保存在 Volume 里的數(shù)據(jù)。
通過這種方式,Kubernetes 的 StatefulSet 就實(shí)現(xiàn)了對(duì)應(yīng)用存儲(chǔ)狀態(tài)的管理。
看到這里,你是不是已經(jīng)大致理解了 StatefulSet 的工作原理呢?現(xiàn)在,我再為你詳細(xì)梳理一下吧。
首先,StatefulSet 的控制器直接管理的是 Pod。這是因?yàn)椋琒tatefulSet 里的不同 Pod 實(shí)例,不再像 ReplicaSet 中那樣都是完全一樣的,而是有了細(xì)微區(qū)別的。比如,每個(gè) Pod 的 hostname、名字等都是不同的、攜帶了編號(hào)的。而 StatefulSet 區(qū)分這些實(shí)例的方式,就是通過在 Pod 的名字里加上事先約定好的編號(hào)。
其次,Kubernetes 通過 Headless Service,為這些有編號(hào)的 Pod,在 DNS 服務(wù)器中生成帶有同樣編號(hào)的 DNS 記錄。只要 StatefulSet 能夠保證這些 Pod 名字里的編號(hào)不變,那么 Service 里類似于 web-0.nginx.default.svc.cluster.local 這樣的 DNS 記錄也就不會(huì)變,而這條記錄解析出來的 Pod 的 IP 地址,則會(huì)隨著后端 Pod 的刪除和再創(chuàng)建而自動(dòng)更新。這當(dāng)然是 Service 機(jī)制本身的能力,不需要 StatefulSet 操心。
最后,StatefulSet 還為每一個(gè) Pod 分配并創(chuàng)建一個(gè)同樣編號(hào)的 PVC。這樣,Kubernetes 就可以通過 Persistent Volume 機(jī)制為這個(gè) PVC 綁定上對(duì)應(yīng)的 PV,從而保證了每一個(gè) Pod 都擁有一個(gè)獨(dú)立的 Volume。
在這種情況下,即使 Pod 被刪除,它所對(duì)應(yīng)的 PVC 和 PV 依然會(huì)保留下來。所以當(dāng)這個(gè) Pod 被重新創(chuàng)建出來之后,Kubernetes 會(huì)為它找到同樣編號(hào)的 PVC,掛載這個(gè) PVC 對(duì)應(yīng)的 Volume,從而獲取到以前保存在 Volume 里的數(shù)據(jù)。
以上就是k8s編排之StatefulSet知識(shí)點(diǎn)詳解二的詳細(xì)內(nèi)容,更多關(guān)于k8s編排StatefulSet的資料請(qǐng)關(guān)注其它相關(guān)文章!