StatefulSet
簡介
實際場景中,尤其是分佈式應用,多個實例之間,往往有依賴關係,比如:主從關係、主備關係。對於數據存儲類應用,它的多個實例,往往都會在本地磁盤保存一份數據。導致這些實例一旦被殺掉,即便重建出來,實例與數據之間的對應關係也已經丟失,從而導致應用創建失敗。
這種實例之間有不對等關係,以及實例對外部數據有依賴關係的應用,稱爲“有狀態應用”
StatefulSet將應用狀態抽象成了兩種情況:
- 拓撲狀態。應用實例必須按照某種順序啓動。新創建的Pod必須和原來Pod的網絡標識一樣
- 存儲狀態。應用的多個實例分別綁定了不同存儲數據。
Headless Service
Service是Kubernetes項目用來將一組Pod暴露給外界訪問的一種機制。
Service被訪問的方式:
- Service的VIP(Virtual IP)
- Service的DNS方式,只要訪問“my-svc.my-namespace.svc.cluster.local”這條DNS記錄,就可以訪問到名爲my-svc的Service所代理的某一個Pod
在第二種Service DNS的方式下,具體還可以分爲兩種處理方法:
第一種處理方法,是Normal Service。這種情況下,訪問“my-svc.my-namespace.svc.cluster.local”解析到的,正是my-svc這個Service 的VIP,後面的流程和VIP一致
第二種處理方法,是Headless Service。這種情況下,訪問“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是my-svc代理的某一個Pod的IP地址。這裏的區別在於,Headless Service不需要分配一個VIP,而是直接以DNS記錄的方式解析出被代理Pod的IP地址。
Headless Service的作用
所謂的Headless Service仍是一個標準的Service的YAML文件,只不過spec.clusterIP的值爲None,即這個Service沒有一個VIP做爲頭。這個Service被創建後並不會分配一個VIP,而是以DNS記錄的方式暴露它所代理的Pod
當按照這樣的方式創建了一個Headless Service後,它所代理的所有Pod的IP地址,都會被綁定一個如下格式的DNS記錄,如下:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
這條DNS記錄,是Kubernetes爲Pod分配的唯一“可解析身份”
StatefulSet如何通過Headless Service維持Pod的拓撲狀態
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9.1
ports:
- containerPort: 80
name: web
然後我們創建這個service和statefulset
[root@host1 statefulset]# kubectl create -f nginx-statefulset.yaml
service/nginx created
statefulset.apps/web created
[root@host1 statefulset]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23h
nginx ClusterIP None <none> 80/TCP 9s
[root@host1 statefulset]# kubectl get statefulset
NAME READY AGE
web 2/2 22s
[root@host1 statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 104s
web-1 1/1 Running 0 103s
StatefulSet給它所管理的所有的Pod的名字進行了編號,編號規則是:-;並且這些編號都是從0開始累加,與StatefulSet的每個Pod實例一一對應;這些Pod的創建也是嚴格按照編號順序進行的。比如在web-0進入到running狀態,並且Conditions爲Ready之前,web-1一直會處於pending狀態
使用kubectl exec命令進入到容器內部查看它們的hostname:
[root@host1 statefulset]# kubectl exec web-0 -- sh -c 'hostname'
web-0
[root@host1 statefulset]# kubectl exec web-1 -- sh -c 'hostname'
web-1
並且,就算Pod被刪除後重建,重建Pod的網絡標識也不會改變,通過這種方式,Kubernetes就可以成功地將Pod的拓撲狀態按照Pod的“名字+編號”的方式固定下來,並且Kubernetes爲每個Pod提供了一個固定且唯一的訪問入口(這個Pod對應的DNS記錄)。
StatefulSet控制器的主要作用之一,就是使用Pod模板創建Pod的時候,對它們進行編號,並且按照編號順序逐一完成創建工作。當StatefulSet的控制循環發現Pod的實際狀態和期望狀態不一致的時候,需要新建或刪除Pod進行調諧的時候,它會嚴格按照這些Pod編號的順序,逐一完成這些操作。任何Pod的變化都會觸發statefulset的滾動更新
存儲管理
Kubernetes中PV和PVC的設計,類似於“接口”和“實現”的思想。PV和PVC的設計,使得StatefulSet對存儲狀態的管理成爲了可能。
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
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: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
storageClassName: rook-ceph-block
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
這裏我們使用了Rook的ceph Flex Driver,關於rook的配置可以參考官方文檔 https://rook.io/docs/rook/v1.1/ceph-block.html
然後我們創建這個Statefulset
[root@host1 statefulset]# kubectl create -f nginx-statefulset.yaml
statefulset.apps/web created
service/nginx created
[root@host1 statefulset]# kubectl get statefulset
NAME READY AGE
web 2/2 8s
[root@host1 statefulset]# kubectl get statefulset
NAME READY AGE
web 2/2 10s
[root@host1 statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
test-projected-volume 1/1 Running 1 25h
web-0 1/1 Running 0 13s
web-1 1/1 Running 0 11s
[root@host1 statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-167033a6-f56a-11e9-9319-000c296c79f3 1Gi RWO rook-ceph-block 2m19s
www-web-1 Bound pvc-17bc7e66-f56a-11e9-9319-000c296c79f3 1Gi RWO rook-ceph-block 2m17s
當我們進入上述創建的Pod,在/usr/share/nginx/html中寫入內容,即使Pod被刪除重建,Volume中的內容仍不會丟失。這是怎麼做到的?
StatefulSet控制器恢復Pod的過程分析:
首先,當你把一個Pod,比如web-0刪除之後,這個Pod對應的PV和PVC並不會被刪除,而這個Volume中寫入的數據,也依然存儲在遠程存儲服務中。
此時,StatefulSet控制器發現,一個名叫web-0的Pod消失了。所以控制器會重新創建一個新的、名字還是叫做web-0的Pod,來進行調諧。
並且這個新Pod對象的定義中,它聲明使用的PVC的名字,仍是www-web-0。所以這個新的web-0 Pod被創建出來之後,Kubernetes爲它查找名叫www-web-0的PVC時,就會將舊Pod遺留的PVC,進而找到和這個PVC綁定在一起的PV
StatefulSet工作原理
首先,StatefulSet的控制器直接管理的是Pod。
其次,Kubernetes通過Headless Serrvice,爲那些有編號的Pod,在DNS服務器中生成帶有同樣編號的DNS記錄。只要StatefulSet能夠保證這些Pod名字中的編號不變,那麼Service中類似於web-0.nginx.default.svc.cluster.clocal這樣的DNS記錄也不會變,這條記錄解析出來的Pod的IP地址,隨着後端Pod的刪除和創建而自動更新。
最後,StatefulSet還會爲每一個Pod分配並創建一個同樣編號的PVC。這樣,kubernetes就可以通過Persistent Volume機制爲這個PVC綁定對應的PV,從而保證每一個Pod都擁有一個獨立的Volume。