12 . Kubernetes之Statefulset 和 Operator

men發表於2020-07-04

Statefulset簡介

k8s權威指南這樣介紹的

“在Kubernetes系統中,Pod的管理物件RC、Deployment、DaemonSet和Job都面向無狀態的服務。但現實中有很多服務是有狀態的,特別是一些複雜的中介軟體叢集,例如MySQL叢集、MongoDB叢集、Akka叢集、ZooKeeper叢集等,這些應用叢集有4個共同點。"

(1)每個節點都有固定的身份ID,通過這個ID,叢集中的成員可以相互發現並通訊。
(2)叢集的規模是比較固定的,叢集規模不能隨意變動。
(3)叢集中的每個節點都是有狀態的,通常會持久化資料到永久儲存中。
(4)如果磁碟損壞,則叢集裡的某個節點無法正常執行,叢集功能受損。

如果通過RC或Deployment控制Pod副本數量來實現上述有狀態的叢集,就會發現第1點是無法滿足的,因為Pod的名稱是隨機產生的,Pod的IP地址也是在執行期才確定且可能有變動的,我們事先無法為每個Pod都確定唯一不變的ID。另外,為了能夠在其他節點上恢復某個失敗的節點,這種叢集中的Pod需要掛接某種共享儲存,為了解決這個問題,Kubernetes從1.4版本開始引入了PetSet這個新的資源物件,並且在1.5版本時更名為StatefulSet,StatefulSet從本質上來說,可以看作Deployment[…]”

“StatefulSet除了要與PV卷捆綁使用以儲存Pod的狀態資料,還要與Headless Service配合使用,即在每個StatefulSet定義中都要宣告它屬於哪個Headless Service。Headless Service與普通Service的關鍵區別在於,它沒有Cluster IP,如果解析Headless Service的DNS域名,則返回的是該Service對應的全部Pod的Endpoint列表。StatefulSet在Headless Service的基礎上又為StatefulSet控制的每個Pod例項都建立了一個DNS域名,這個域名的格式為:

$(podname).(headless server name)   
FQDN: $(podname).(headless server name).namespace.svc.cluster.local

比如一個3節點的Kafka的StatefulSet叢集對應的Headless Service的名稱為kafka,StatefulSet的名稱為kafka,則StatefulSet裡的3個Pod的DNS名稱分別為kafka-0.kafka、kafka-1.kafka、kafka-3.kafka,這些DNS名稱可以直接在叢集的配置檔案中固定下來。”

StatefulSet本質上是Deployment的一種變體,在v1.9版本中已成為GA版本,它為了解決有狀態服務的問題,它所管理的Pod擁有固定的Pod名稱,啟停順序,在StatefulSet中,Pod名字稱為網路標識(hostname),還必須要用到共享儲存。
在Deployment中,與之對應的服務是service,而在StatefulSet中與之對應的headless service,headless service,即無頭服務,與service的區別就是它沒有Cluster IP,解析它的名稱時將返回該Headless Service對應的全部Pod的Endpoint列表。

應用場景

StatefulSet是為了解決有狀態服務的問題(對應Deployments和ReplicaSets是為無狀態服務而設計),其應用場景包括

  • 1、穩定的持久化儲存,即Pod重新排程後還是能訪問到相同的持久化資料,基於PVC來實現
  • 2、穩定的網路標誌,即Pod重新排程後其PodName和HostName不變,基於Headless Service(即沒有Cluster IP的Service)來實現
  • 3、有序部署,有序擴充套件,即Pod是有順序的,在部署或者擴充套件的時候要依據定義的順序依次依次進行(即從0到N-1,在下一個Pod執行之前所有之前的Pod必須都是Running和Ready狀態),基於init containers來實現有序收縮,有序刪除(即從N-1到0)
特點
# 1. 穩定且唯一的網路識別符號
# 2. 穩定且持久的儲存.
# 3. 有序、平滑地部署和擴充套件.
# 4. 有序、平滑的刪除和終止.
# 5. 有序的滾動更新
三個元件
# headless service(無頭服務)
# statefuleset
# volumeClaimTemplate(儲存卷申請模板)

Operator

Kubernetes operator是把專家平常的經驗,流程,比如寫在wiki裡面的訣竅,使用Operator來固化。這些經驗就會整合在Operator軟體裡面。

換句話說,這個叫做Best practices. 也是軟體工程裡面把logic封裝起來的一個例子。It’s a fancy script.

比如failover,用軟體來建立模型。使用K8S的原語,比如stateless workload,stateful workload,服務發現等來實現。

好處是到處可以使用,跨平臺。

Kubernetes提供了一個Operator框架,供大家使用。控制叢集裡面的節點。

Operator SDK for Build
Operator Lifecycle manager for Run, 多個Operator的生命週期,比如版本。
Operator Metering for Operate,收集統計資訊。
業界Open operators, 提供在github。比如etcd,Spark, MongoDB.

例如,mongoDB提供了operator,封裝了創造ReplicaSet的best practice。在PROD namespace,使用者提供High level configuration,提供Intention about如何部署。Operator會幫助deploy。

另外一個例子,根據Luke Bond的video, Cassandra需要把3節點擴充套件到5節點的時候,因為是stateful應用,需要做一系列的動作,這個知識就可以封裝在operator。就像Kubectl增加instance的數量一樣。

Statefulset應用

Example1

建立pv
cat /root/volume/pod-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    name: pv001
spec:
  nfs:
    path: /data/volumes/v1
    server: nfs1
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    name: pv002
spec:
  nfs:
    path: /data/volumes/v2
    server: nfs1
  accessModes: ["ReadWriteMany"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
  labels:
    name: pv003
spec:
  nfs:
    path: /data/volumes/v3
    server: nfs1
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
建立StatefulSet
cat pod-statefulset.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: myapp-pod
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  serviceName: myapp 
  replicas: 3
  selector:
    matchLabels:
      app: myapp-pod 
  template:
    metadata:
      labels:
        app: myapp-pod 
    spec:
      containers:
      - name: myapp 
        image: ikubernetes/myapp:v1 
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: myappdata
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: myappdata
    spec: 
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 2Gi
檢視建立的Statefulset
kubectl get svc
# NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
# kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   7d
# myapp        ClusterIP   None         <none>        80/TCP    44s

kubectl get sts
# NAME    READY   AGE
# myapp   3/3     46s

kubectl get pods
# NAME           READY   STATUS    RESTARTS   AGE
# myapp-0        1/1     Running   0          48s
# myapp-1        1/1     Running   0          40s
# myapp-2        1/1     Running   0          39s
StateFulSet擴縮容
kubectl scale sts myapp --replicas=4
kubectl patch sts myapp -p '{"spec":{"replicas":4}}'
kubectl get pods -w
NAME      READY   STATUS    RESTARTS   AGE
myapp-0   1/1     Running   0          15m
myapp-1   1/1     Running   0          15m
myapp-2   1/1     Running   0          15m
myapp-3   0/1     Pending   0          0s
myapp-3   0/1     Pending   0          0s
myapp-3   0/1     ContainerCreating   0          0s
myapp-3   1/1     Running             0          2s


# 我們進入pod建立一個檔案,即使我們縮容刪掉Pod,但是儲存卷資料還在
[root@master statefulset]# kubectl exec -it myapp-0 -- sh
/ # ls
bin    etc    lib    mnt    root   sbin   sys    usr
dev    home   media  proc   run    srv    tmp    var
/ # ls /usr/share/nginx/html/
/ # echo 1324 > /usr/share/nginx/html/index.html

[root@nfs1 ~]# tree /data/volumes/
/data/volumes/
├── v1
│   └── index.html
├── v2
├── v3
└── v4	

[root@master statefulset]# kubectl scale sts myapp --replicas=1

[root@master statefulset]# kubectl scale sts myapp --replicas=3

[root@master statefulset]# kubectl exec -it myapp-0 /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ # ls /usr/share/nginx/html/
index.html
控制更新策略
# 預設為滾動更新
kubectl describe sts myapp |grep Update 
Update Strategy:    RollingUpdate
kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
# 留下兩個不更新

# 更換映象
kubectl set image sts/myapp myapp=ikubernetes/myapp:v2

# 檢視映象版本
kubectl get sts -o wide
NAME    READY   AGE   CONTAINERS   IMAGES
myapp   4/4     29m   myapp        ikubernetes/myapp:v2


# 我們會發現有兩個Pod版本還是以前的,可以算金絲雀釋出
kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
# 全部更新.

相關文章