5.深刻k8s:StatefulSet控制器

轉載請聲明出處哦~,本篇文章發佈於luozhiyun的博客:https://www.luozhiyun.comhtml

image-20200807220814361

在上一篇中,講解了容器持久化存儲,從中咱們知道什麼是PV和PVC,這一篇咱們講經過StatefulSet來使用它們。若是以爲我講的不錯的,能夠發個郵件鼓勵一下我噢~node

咱們在第三篇講的Deployment控制器是應用於無狀態的應用的,全部的Pod啓動之間沒有順序,Deployment能夠任意的kill一個Pod不會影響到業務數據,可是這到了有狀態的應用中就無論用了。nginx

而StatefulSet就是用來對有狀態應用提供支持的控制器。web

StatefulSet把真實世界裏的應用狀態,抽象爲了兩種狀況:docker

  1. 拓撲狀態。應用的多個實例之間不是徹底對等的關係。這些應用實例,必須按照某些順序啓動,好比應用的主節點 A 要先於從節點 B 啓動。而且,新建立出來的 Pod,必須和原來 Pod 的網絡標識同樣。
  2. 存儲狀態。應用的多個實例分別綁定了不一樣的存儲數據,對於這些應用實例來講,Pod A 第一次讀取到的數據,和隔了十分鐘以後再次讀取到的數據,應該是同一份,哪怕在此期間 Pod A 被從新建立過。

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 字段。

OnDelete

若是 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
...

Rolling Updates

.spec.updateStrategy.type 字段的默認值是 RollingUpdate,該策略爲 StatefulSet 實現了 Pod 的自動滾動更新。在更新完.spec.tempalte 字段後StatefulSet Controller 將自動地刪除並重建 StatefulSet 中的每個 Pod。

刪除和重建的順序也是有講究的:

  • 刪除的時候從序號最大的開始刪,每刪除一個會更新一個。
  • 只有更新完的pod已是ready狀態了才往下繼續更新。

爲 RollingUpdate 進行分區

當爲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若是實現金絲雀發佈等。

相關文章
相關標籤/搜索