­

12 . Kubernetes之Statefulset 和 Operator

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}}}}'
# 全部更新.