轉載請聲明出處哦~,本篇文章發佈於luozhiyun的博客:https://www.luozhiyun.comhtml
在上一篇中,講解了容器持久化存儲,從中咱們知道什麼是PV和PVC,這一篇咱們講經過StatefulSet來使用它們。若是以爲我講的不錯的,能夠發個郵件鼓勵一下我噢~node
咱們在第三篇講的Deployment控制器是應用於無狀態的應用的,全部的Pod啓動之間沒有順序,Deployment能夠任意的kill一個Pod不會影響到業務數據,可是這到了有狀態的應用中就無論用了。nginx
而StatefulSet就是用來對有狀態應用提供支持的控制器。web
StatefulSet把真實世界裏的應用狀態,抽象爲了兩種狀況:docker
StatefulSet 的核心功能,就是經過某種方式記錄這些狀態,而後在 Pod 被從新建立時,可以爲新 Pod 恢復這些狀態。shell
在k8s中,Service是用來將一組 Pod 暴露給外界訪問的一種機制。Service能夠經過DNS的方式,代理到某一個Pod,而後經過DNS記錄的方式解析出被代理 Pod 的 IP 地址。api
以下:bash
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx
這個Service會經過Label Selector選擇全部攜帶了 app=nginx 標籤的 Pod,都會被這個 Service 代理起來。網絡
它所代理的全部 Pod 的 IP 地址,都會被綁定一個這樣格式的 DNS 記錄,以下所示:app
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
因此經過這個DNS記錄,StatefulSet就可使用到DNS 記錄來維持 Pod 的拓撲狀態。
以下:
apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 # by default is 1 selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: containers: - name: nginx image: nginx:1.9.1 ports: - containerPort: 80 name: web
這裏使用了serviceName=nginx,代表StatefulSet 控制器會使用nginx 這個Service來進行網絡代理。
咱們能夠以下建立:
$ kubectl create -f svc.yaml $ kubectl get service nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP None <none> 80/TCP 10s $ kubectl create -f statefulset.yaml $ kubectl get statefulset web NAME DESIRED CURRENT AGE web 2 1 19s
而後咱們能夠觀察pod的建立狀況:
$ kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 76m web-1 1/1 Running 0 76m
咱們經過-w命令能夠看到pod建立狀況,StatefulSet所建立的pod編號都是從0開始累加,在 web-0 進入到 Running 狀態、而且細分狀態(Conditions)成爲 Ready 以前,web-1 會一直處於 Pending 狀態。
而後咱們使用exec查看pod的hostname:
$ kubectl exec web-0 -- sh -c 'hostname' web-0 $ kubectl exec web-1 -- sh -c 'hostname' web-1
而後咱們能夠啓動一個一次性的pod用 nslookup 命令,解析一下 Pod 對應的 Headless Service:
$ kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh $ nslookup web-0.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.20.0.56 web-0.nginx.default.svc.cluster.local $ nslookup web-1.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 172.20.0.57 web-1.nginx.default.svc.cluster.local
若是咱們刪除了這兩個pod,而後觀察pod狀況:
$ kubectl delete pod -l app=nginx $ kubectl get pod -w -l app=nginx web-0 1/1 Terminating 0 83m web-1 1/1 Terminating 0 83m web-0 0/1 Pending 0 0s web-1 0/1 Terminating 0 83m web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 1s
當咱們把這兩個 Pod 刪除以後,Kubernetes 會按照原先編號的順序,建立出了兩個新的 Pod。而且,Kubernetes 依然爲它們分配了與原來相同的「網絡身份」:web-0.nginx 和 web-1.nginx。
可是網絡結構雖然沒變,可是pod對應的ip是改變了的,咱們再進入到pod進行DNS解析:
$ nslookup web-0.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.20.0.59 web-0.nginx.default.svc.cluster.local $ nslookup web-1.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 172.20.0.60 web-1.nginx.default.svc.cluster.local
在講存儲狀態的時候,須要你們掌握上一節有關pv和pvc的知識才好往下繼續,建議你們看完再來看本節。
在上一節中,咱們瞭解到Kubernetes 中 PVC 和 PV 的設計,實際上相似於「接口」和「實現」的思想。而 PVC、PV 的設計,也使得 StatefulSet 對存儲狀態的管理成爲了可能。
好比咱們聲明一個以下的StatefulSet:
apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 1 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: local-volume-a mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: local-volume-a spec: accessModes: - ReadWriteMany storageClassName: "local-volume" resources: requests: storage: 512Mi selector: matchLabels: key: local-volume-a-0
在這個StatefulSet中添加了volumeClaimTemplates字段,用來聲明對應的PVC的定義;也就是說這個PVC中使用的storageClass必須是local-volume,須要的存儲空間是512Mi,而且這個pvc對應的pv的標籤必須是key: local-volume-a-0。
而後咱們準備一個PV:
apiVersion: v1 kind: PersistentVolume metadata: name: local-volume-pv-0 labels: key: local-volume-a-0 spec: capacity: storage: 0.5Gi volumeMode: Filesystem accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: local-volume local: path: /mnt/disks/vol1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node1
我把這個PV建立在node1節點上,而且將本地磁盤掛載聲明爲PV。
而後咱們建立這個PV:
$ kubectl apply -f local-pv-web-0.yaml $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE local-volume-pv-0 512Mi RWX Retain Available default/local-vo
而後咱們在建立這個StatefulSet的時候,會自動建立PVC:
$ kubectl apply -f statefulset2.yaml $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE local-volume-a-web-0 Bound local-volume-pv-0 512Mi RWX local-volume 15m
建立的PVC名字都是由:<PVC 名字 >-<StatefulSet 名字 >-< 編號 >構成,編號從0開始,而且咱們能夠看到上面的PV已經處於Bound狀態。
這個時候咱們進入到Pod中,寫入一個文件:
$ kubectl exec -it web-0 -- /bin/bash $ echo helloword >/usr/share/nginx/html/index.html
這樣就會在Pod 的 Volume 目錄裏寫入一個文件,若是咱們把這個Pod刪除,那麼在被刪除以後這個Pod仍是會被建立出來,而且還會再和原來的PV:local-volume-pv-0綁定起來。
也就是說當StatefulSet 控制器發現一個名叫 web-0 的 Pod 消失了的時候,控制器就會從新建立一個新的、名字仍是叫做 web-0 的 Pod 來,「糾正」這個不一致的狀況。而且刪除Pod時並不會刪除這個 Pod 對應的 PVC 和 PV。須要注意的是,在這個新的 Pod 對象的定義裏,它聲明使用的 PVC 的名字,仍是叫做local-volume-a-web-0。
經過這種方式,Kubernetes 的 StatefulSet 就實現了對應用存儲狀態的管理。
在 Kubernetes 1.7 及以後的版本中,能夠爲 StatefulSet 設定 .spec.updateStrategy
字段。
若是 StatefulSet 的 .spec.updateStrategy.type
字段被設置爲 OnDelete,當您修改 .spec.template
的內容時,StatefulSet Controller 將不會自動更新其 Pod。您必須手工刪除 Pod,此時 StatefulSet Controller 在從新建立 Pod 時,使用修改過的 .spec.template
的內容建立新 Pod。
例如咱們執行下面的語句更新上面例子中建立的web:
$ kubectl set image statefulset web nginx=nginx:1.18.0 $ kubectl describe pod web-0 .... Containers: nginx: Container ID: docker://7e45cd509db74a96b4f6ca4d9f7424b3c4794f56e28bfc3fbf615525cd2ecadb Image: nginx:1.9.1 ....
而後咱們發現pod的nginx版本並無發生改變,須要咱們手動刪除pod以後才能生效。
$ kubectl delete pod web-0 pod "web-0" deleted $ kubectl describe pod web-0 ... Containers: nginx: Container ID: docker://0f58b112601a39f3186480aa97e72767b05fdfa6f9ca02182d3fb3b75c159ec0 Image: nginx:1.18.0 ...
.spec.updateStrategy.type
字段的默認值是 RollingUpdate,該策略爲 StatefulSet 實現了 Pod 的自動滾動更新。在更新完.spec.tempalte
字段後StatefulSet Controller 將自動地刪除並重建 StatefulSet 中的每個 Pod。
刪除和重建的順序也是有講究的:
當爲StatefulSet 的 RollingUpdate
字段的指定 partition
字段的時候,則全部序號大於或等於 partition
值的 Pod 都會更新。序號小於 partition
值的全部 Pod 都不會更新,即便它們被刪除,在從新建立時也會使用之前的版本。
若是 partition
值大於其 replicas
數,則更新不會傳播到其 Pod。這樣能夠實現金絲雀發佈Canary Deploy或者灰度發佈。
以下,由於咱們的web是2個pod組成,因此能夠將partition
設置爲1:
$ kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":1}}}}'
在這裏,我使用了 kubectl patch 命令。它的意思是,以「補丁」的方式(JSON 格式的)修改一個 API 對象的指定字段。
下面咱們執行更新:
$ kubectl set image statefulset web nginx=nginx:1.19.1 statefulset.apps/web image updated
並在另外一個終端中watch pod的變化:
$ kubectl get pods -l app=nginx -w NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 13m web-1 1/1 Running 0 93s web-1 0/1 Terminating 0 2m16s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 16s
可見上面只有一個web-1進行了版本的發佈。
StatefulSet把有狀態的應用抽象爲兩種狀況:拓撲狀態和存儲狀態。
拓撲狀態指的是應用的多個實例之間不是徹底對等的關係,包含啓動的順序、建立以後的網絡標識等必須保證。
存儲狀態指的是不一樣的實例綁定了不一樣的存儲,如Pod A在它的生命週期中讀取的數據必須是一致的,哪怕是重啓以後仍是須要讀取到同一個存儲。
而後講解了一下StatefulSet發佈更新該如何作,updateStrategy
策略以及經過partition
若是實現金絲雀發佈等。