kubernetes控制器之StatefulSet


一、簡介

實例之間的不等關系以及實例對外數據有依賴關系的應用,就被稱為"有狀態應用"。
所謂實例之間的不等關系即對分布式應用來說,各實例,各應用之間往往有比較大的依賴關系,比如某個應用必須先於其他應用啟動,否則其他應用將不能啟動等。
對外數據有依賴關系的應用,最顯著的就是數據庫應用,對於數據庫應用,我們是需要持久化保存其數據的,如果是無狀態應用,在數據庫重啟數據和應用就失去了聯系,這顯然是違背我們的初衷,不能投入生產的。

所以,為了解決Kubernetes中有狀態應用的有效支持,Kubernetes使用StatefulSet來編排管理有狀態應用。
StatefulSet類似於ReplicaSet,不同之處在於它可以控制Pod的啟動順序,它為每個Pod設置唯一的標識。其具有一下功能:

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

StatefulSet的設計很容易理解,它把現實世界抽象為以下兩種情況:
(1)、拓撲狀態。這就意味着應用之間是不對等關系,應用要按某種順序啟動,即使應用重啟,也必須按其規定的順序重啟,並且重啟后其網絡標識必須和原來的一樣,這樣才能保證原訪問者能通過同樣的方法訪問新的Pod;
(2)、存儲狀態 。這就意味着應用綁定了存儲數據,不論什么時候,不論什么情況,對應用來說,只要存儲里的數據沒有變化,讀取到的數據應該是同一份;

所以StatefulSet的核心功能就是以某種方式記錄Pod的狀態,然后在Pod被重新創建時,通過某種方法恢復其狀態。

二、Headless Service

在介紹StatefulSet之前先了解一下什么是Headless Service。
我們知道,在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地址,這樣就可以直接訪問;

對於第二種處理方法我們可以看到這里並不需要Cluster IP,而是通過域名直接解析后端Pod的IP地址進行訪問。
下面即為一個簡單的Headless Service的YAML文件定義:

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

通過上面的YAML文件可以看出和我們普通的Service定義沒有太大的區別,唯一的不同就是clusterIP設置為None,也就是不需要cluster,我們通過kubectl apply -f創建這個Service,然后查看這個Service的Cluster IP為None。
image.png

這樣創建的Service就是一個Headless Service,它沒有VIP,所以會以DNS記錄的方式暴露它所代理的Pod。

三、使用StatefulSet

在創建StatefulSet之前先創建PV,如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv01
  labels:
    release: stable
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  hostPath:
    path: /tmp/data

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv02
  labels:
    release: stable
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  hostPath:
    path: /tmp/data

然后執行kubectl apply -f 啟動PV,如下:

[root@master statefulset]# kubectl apply -f pv.yaml 
persistentvolume/pv01 created
persistentvolume/pv02 created
[root@master statefulset]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv01   1Gi        RWO            Recycle          Available                                   10s
pv02   1Gi        RWO            Recycle          Available                                   9s

可以看到兩個PV的狀態都為可用狀態,然后編寫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: cnych/nginx-slim:0.8
        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的可解析身份。

然后這里我們開啟兩個終端窗口。在第一個終端中,使用 kubectl get 來查看 StatefulSet 的 Pods 的創建情況。

$ kubectl get pods -w -l role=stateful

在另一個終端中,使用 kubectl create 來創建定義在 statefulset-demo.yaml 中的 Headless Service 和 StatefulSet。

$ kubectl create -f statefulset-demo.yaml
service "nginx" created
statefulset.apps "web" created

然后我們觀察Pod的啟動順序:

[root@master ~]# kubectl get pods -w -l role=stateful
web-0   0/1   Pending   0     0s
web-0   0/1   Pending   0     0s
web-0   0/1   ContainerCreating   0     0s
web-0   1/1   Running             0     3m12s
web-1   0/1   Pending             0     0s
web-1   0/1   Pending             0     0s
web-1   0/1   Pending             0     1s
web-1   0/1   ContainerCreating   0     1s
web-1   1/1   Running             0     5s

通過 上面的創建過程,我們可以看出,StatefulSet給它所管理的Pod進行了編號,其命名規則為[statefulset-name]-[index],其index從0開始,與StatefulSet的每個Pod實例一一對應,絕不重復。更重要的是其Pod的創建過程是順序的,如上web-0進入running狀態后web-1才進入pending狀態。

我們通過命令來查看其創建結果:

[root@master statefulset]# kubectl get statefulset web
NAME   READY   AGE
web    2/2     17m
[root@master statefulset]# kubectl get pods -l role=stateful
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          17m
web-1   1/1     Running   0          14m
[root@master statefulset]# kubectl get svc nginx
NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   None         <none>        80/TCP    17m

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

[root@master statefulset]# kubectl exec web-0 -- sh -c 'hostname'
web-0
[root@master statefulset]# kubectl exec web-1 -- sh -c 'hostname'
web-1

可以看到這兩個pod的hostname和pod的名字是一致的,都被分配為對應的編號,接下來我們用DNS的方式來訪問Headless Service。
我們先用如下命令啟動一個Pod:

kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh 

然后在這個Pod里用nslookup解析一個Pod對應的Headless Service:

kubectl run -i --tty --image centos dns-test --restart=Never --rm /bin/sh
sh-4.2# yum install bind-utils -y
sh-4.2# nslookup web-0.nginx
Server:		10.68.0.2
Address:	10.68.0.2#53

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

sh-4.2# nslookup web-1.nginx
Server:		10.68.0.2
Address:	10.68.0.2#53

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

從nslookup的結果分析,在訪問web-0.nginx的時候解析的是web-0這個Pod的IP,另一個亦然。
這時候如果我們把兩個Pod刪掉,再觀察其重啟Pod的過程:

[root@master statefulset]# kubectl delete pod -l role=stateful
pod "web-0" deleted
pod "web-1" deleted

然后查看其重啟順序如下:

[root@master ~]# kubectl get pods -w -l role=stateful
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     ContainerCreating   0          0s
web-0   1/1     Running             0          2s
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   1/1     Running             0          2s

我們可以看到,我們把Pod刪除后,其重啟順序還是按原先的編號重啟,並且其網絡標識和原來依然一樣。
通過這種嚴格的對應規則,StatefulSet就保證了Pod的網絡標識的穩定性,通過這個方法,就可以把Pod的拓撲狀態按照Pod的名字+編號的方式固定起來。此外,Kubernetes還為每一個Pod提供了一個固定並且唯一的訪問入口,即這個Pod的DNS記錄。

我們還可以查看一下PV和PVC的綁定情況:

[root@master statefulset]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pv01   1Gi        RWO            Recycle          Bound    default/www-web-0                           129m
pv02   1Gi        RWO            Recycle          Bound    default/www-web-1                           129m
[root@master statefulset]# kubectl get pvc
NAME        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pv01     1Gi        RWO                           124m
www-web-1   Bound    pv02     1Gi        RWO                           116m

由此,我們對StatefulSet梳理如下:
(1)、StatefulSet直接管理的是Pod。這是因為StatefulSet里的Pod實例不像ReplicaSet中的Pod實例完全一樣,它們是有細微的區別,比如每個Pod的名字、hostname等是不同的,而且StatefulSet區分這些實例的方式就是為Pod加上編號;
(2)、Kubernetes通過Headless Service為這個編號的Pod在DNS服務器中生成帶同樣編號的記錄。只要StatefulSet能保證這個Pod的編號不變,那么Service中類似於web-0.nginx.default.svc.cluster.local這樣的DNS記錄就不會變,而這條記錄所解析的Pod IP地址會隨着Pod的重新創建自動更新;
(3)、StatefulSet還可以為每個Pod分配並創建一個和Pod同樣編號的PVC。這樣Kubernetes就可以通過Persitent Volume機制為這個PVC綁定對應的PV,從而保證每一個Pod都擁有獨立的Volume。這種情況下即使Pod被刪除,它所對應的PVC和PV依然會保留下來,所以當這個Pod被重新創建出來過后,Kubernetes會為它找到同樣編號的PVC,掛載這個PVC對應的Volume,從而獲取到以前Volume以前的數據;

四、總結

StatefulSet這個控制器的主要作用之一,就是使用Pod模板創建Pod的時候,對它們進行編號,並且按照編號順序完成作業,當StatefulSet的控制循環發現Pod的實際狀態和期望狀態不一致的時候,也會按着順序對Pod進行操作。

當然 StatefulSet 還擁有其他特性,在實際的項目中,我們還是很少回去直接通過 StatefulSet 來部署我們的有狀態服務的,除非你自己能夠完全能夠 hold 住,對於一些特定的服務,我們可能會使用更加高級的 Operator 來部署,比如 etcd-operator、prometheus-operator 等等,這些應用都能夠很好的來管理有狀態的服務,而不是單純的使用一個 StatefulSet 來部署一個 Pod就行,因為對於有狀態的應用最重要的還是數據恢復、故障轉移等等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM