天天看點

【K8S專欄】Kubernetes有狀态應用管理什麼是有狀态應用如何使用StatefulSet總結

大家好,我是喬克,一個愛折騰的運維工程,一個睡覺都被自己醜醒的雲原生愛好者。

作者:喬克

公衆号:運維開發故事

我們在《Kubernetes工作負載管理》中主要介紹了無狀态應用的管理,當時也有提到有狀态應用,但是由于那時候還沒有解釋資料如何持久化就沒有做深度的介紹,而在這章,我們會着重介紹如何進行有狀态應用的管理。

什麼是有狀态應用

執行個體之間的不等關系以及執行個體對外資料有依賴關系的應用,就被稱為"有狀态應用"。

所謂執行個體之間的不等關系即對分布式應用來說,各執行個體,各應用之間往往有比較大的依賴關系,比如某個應用必須先于其他應用啟動,否則其他應用将不能啟動等。

對外資料有依賴關系的應用,最顯著的就是資料庫應用,對于資料庫應用,我們是需要持久化儲存其資料的,如果是無狀态應用,在資料庫重新開機資料和應用就失去了聯系,這顯然是違背我們的初衷,不能投入生産的。

是以,為了解決Kubernetes中有狀态應用的有效支援,Kubernetes使用StatefulSet來編排管理有狀态應用。

StatefulSet類似于ReplicaSet,不同之處在于它可以控制Pod的啟動順序,它為每個Pod設定唯一的辨別。其具有一下功能:

  • 穩定的,唯一的網絡辨別符
  • 穩定的,持久化存儲
  • 有序的,優雅部署和縮放
  • 有序的,自動滾動更新

StatefulSet的設計很容易了解,它把現實世界抽象為以下兩種情況:

(1)、拓撲狀态。這就意味着應用之間是不對等關系,應用要按某種順序啟動,即使應用重新開機,也必須按其規定的順序重新開機,并且重新開機後其網絡辨別必須和原來的一樣,這樣才能保證原通路者能通過同樣的方法通路新的Pod;

(2)、存儲狀态 。這就意味着應用綁定了存儲資料,不論什麼時候,不論什麼情況,對應用來說,隻要存儲裡的資料沒有變化,讀取到的資料應該是同一份;

是以StatefulSet的核心功能就是以某種方式記錄Pod的狀态,然後在Pod被重新建立時,通過某種方法恢複其狀态。

如何使用StatefulSet

在《Kubernetes應用通路管理》中,我們介紹了Service,它是為一組Pod提供外部通路的一種方式。通常,我們使用 Service通路Pod有一下兩種方式:

(1)、通過Cluster IP,這個Clustre IP就相當于VIP,我們通路這個IP,就會将請求轉發到後端Pod上;

(2)、通過DNS方式,通過這種方式首先得確定Kubernetes叢集中有DNS服務。這個時候我們隻要通路"my-service.my-namespace.svc,cluster.local",就可以通路到名為my-service的Service所代理的後端Pod;

而對于第二種方式,有下面兩種處理方法:

(1)、Normal Service,即解析域名,得到的是Cluster IP,然後再按照方式一通路;

(2)、Headless Service,即解析域名,得到的是後端某個Pod的IP位址,這樣就可以直接通路;

而在使用StatefulSet的時候,主要用到Headless Service,還記得Headless Service怎麼定義的嗎?

我們隻需要把ClusterIP設定為None即可,如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx-headless-service
  labels:
    name: nginx-headless-service
spec:
  clusterIP: None
  selector:
    name: nginx
  ports:
  - port: 8000
    targetPort: 80
           

了解了Headless Service,還需要了解PV、PVC是怎麼使用的,如果忘記了,可以移步《Kubernetes資料持久化管理》回顧,這裡就不再贅述了。

下面,我們開始使用StatefulSet。

首先,我們建立兩個個PV,因為準備為有狀态應用建立兩個副本,如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-pv01
  labels:
    storage: pv
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/k8s
    server: 192.168.205.128
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-pv02
  labels:
    storage: pv
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/k8s
    server: 192.168.205.128
           

然後編寫StatefulSet需要的YAML檔案,如下:

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
    role: stateful

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
        role: stateful
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
           

注意上面的 YAML 檔案中和volumeMounts進行關聯的是一個新的屬性:volumeClaimTemplates,該屬性會自動聲明一個 pvc 對象和 pv 進行管理,而serviceName: "nginx"表示在執行控制循環的時候,用nginx這個Headless Service來儲存Pod的可解析身份。

建立完成後,可以看到會起兩個Pod:

$ kubectl get pod | grep web
web-0                                  1/1     Running   0             2m45s
web-1                                  1/1     Running   0             2m41s
           

從這兩個Pod的指令可以看到,它們的名字不像Deployment那樣随機生成的字元串,而是0,1這樣的序号。這是因為StatefulSet要保證每個Pod順序,確定每次重新開機或者更新,每個Pod依然保持以前的資料,不會錯亂。是以StatefulSet會以

[statefulset-name]-[index]

規則進行命名,其中index從0開始。而且每個Pod的建立是有順序的,如上隻有web-0進入running狀态後,web-1才建立。

當兩個Pod都進入running狀态後,就可以檢視其各自的網絡身份了,我們通過kubectl exec來檢視,如下:

$ kubectl exec web-0 -- sh -c 'hostname'
web-0
$ kubectl exec web-1 -- sh -c 'hostname'
web-1
           

可以看到這兩個pod的hostname和pod的名字是一緻的,都被配置設定為對應的編号,接下來我們用DNS的方式來通路Headless Service。

我們先啟動一個調試Pod,如下:

apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
  namespace: default
spec:
  containers:
  - name: dnsutils
    image: lansible/dnstools
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
           

然後進入dnsutils容器進行解析,如下:

$ kubectl exec -it dnsutils -- /bin/sh
/ # nslookup web-0.nginx
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-0.nginx.default.svc.cluster.local
Address: 172.16.51.247

/ # nslookup web-1.nginx
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-1.nginx.default.svc.cluster.local
Address: 172.16.51.251

/ #
           

從nslookup的結果分析,在通路web-0.nginx的時候解析的是web-0這個Pod的IP,另一個亦然。這表示,如果我們在應用中配置web-0.nginx,則隻會調用web-0這個Pod,在配置有狀态應用,比如Zookeeper的時候,我們需要在配置檔案裡指定zkServer,這時候就可以指定類似:zk-0.zookeeper,zk-1.zookeeper。

如果我們現在更新StatefuleSet,起更新順序是怎麼樣的呢?

首先,我們新開一個終端,輸入以下指令用以觀察:

$ kubectl get pods -w -l role=stateful
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          67m
web-1   1/1     Running   0          67m
           

然後使用以下指令更新應用的鏡像,如下:

$ kubectl set image statefulset/web nginx=nginx:1.8
           

然後觀察web應用的更新順序,如下:

$ kubectl get pods -w -l role=stateful
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          67m
web-1   1/1     Running   0          67m
web-1   1/1     Terminating   0          68m
web-1   1/1     Terminating   0          68m
web-1   0/1     Terminating   0          68m
web-1   0/1     Terminating   0          68m
web-1   0/1     Terminating   0          68m
web-1   0/1     Pending       0          0s
web-1   0/1     Pending       0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   0/1     ContainerCreating   0          1s
web-1   1/1     Running             0          10s
web-0   1/1     Terminating         0          69m
web-0   1/1     Terminating         0          69m
web-0   0/1     Terminating         0          69m
web-0   0/1     Terminating         0          69m
web-0   0/1     Terminating         0          69m
web-0   0/1     Pending             0          0s
web-0   0/1     Pending             0          0s
web-0   0/1     ContainerCreating   0          0s
web-0   0/1     ContainerCreating   0          1s
web-0   1/1     Running             0          9s
           

從整個順序可以看到,起更新是從後往前進行更新的,也就是先更新web-1的pod,再更新web-0的pod。通過這種嚴格的對應規則,StatefulSet就保證了Pod的網絡辨別的穩定性,通過這個方法,就可以把Pod的拓撲狀态按照Pod的名字+編号的方式固定起來。此外,Kubernetes還為每一個Pod提供了一個固定并且唯一的通路入口,即這個Pod的DNS記錄。

總結

繼續閱讀