1 StatefulSet解決的問題
對於kubernetes中的應用,如果同一個deployment中的pod存在依賴,或者對於數據存儲應用往往有多個實例,但每個實例都會在本地保存一份數據,如果應用實例重建,
那麼實例與本地數據的對應關係會丟失。這種實例的不對等關係以及對外部數據有依賴的應用,我們稱之爲“有狀態的應用”。
對於這種“有狀態的應用”如何管理,StatefulSet爲這個問題提供瞭解決辦法。
2 StatefulSet功能
StatefulSet把“有狀態的應用”抽象爲了兩種情況:
- 拓撲狀態,應用之間不對等,啓動有先後順序
- 存儲狀態,一個數據庫應用的多個實例
StatefulSet核心功能就是通過某種方式記錄這些狀態,然後在 Pod 被重新創建時,能夠爲新 Pod 恢復這些狀態。
3 StatefulSet之拓撲狀態
3.1 原理
Service 將一組Pod 暴露給外界訪問,那麼Service又改如何被訪問呢?
-
以 Service 的 VIP(Virtual IP,即:虛擬 IP)方式
-
以 Service 的 DNS 方式。比如:這時候,只要我訪問“my-svc.my-namespace.svc.cluster.local”這條 DNS 記錄,就可以訪問到名叫 my-svc 的 Service 所代理的某一個 Pod。可以分爲兩種處理方法:
-
一是 Normal Service。這種情況下,你訪問“my-svc.my-namespacece.svc.cluster.local”解析到的,正是 my-svc 這個 Service 的 VIP,後面的流程就跟 VIP 方式一致了。
-
二是 Headless Service。這種情況下,你訪問“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是my-svc 代理的某一個 Pod 的 IP 地址。
Normal Service和Headless Service 的區別是,後者不需要分配一個 VIP,可以直接以 DNS 記錄的方式解析出被代理 Pod 的 IP 地址。
-
Headless Service,其實仍是一個標準Service的YAML文件。只不過,它的 clusterIP 字段的值是:None,沒有VIP。
3.2 實例
statefulset.yaml 文件如下
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
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
執行操作:
kubectl create -f statefulset.yaml -n cfd
# 查看過程
kubectl get pod -w -l app=nginx -n cfd
# 創建一個pod,用於訪問web pod
kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh -n cfd
# 查看dns是否正常
nslookup web-0.nginx
nslookup web-1.nginx
# 刪除pod,查看啓動過程
kubectl delete pod -l app=nginx -n cfd
kubectl get pod -w -l app=nginx -n cfd
4 StatefulSet之存儲狀態
StatefulSet對存儲狀態的管理,主要使用的是Persistent Volume Claim的功能,同時也避免了用戶名、授權文件位置等信息的暴露。
對於一個pvc對象,Kubernetes會自動爲它綁定一個符合條件的Volume,這個Volume一般由運維人員維護。對於PVC和PV的設計,類似於“接口”和“實現”的思想。
4.1 實例
PV的YAML文件如下:
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-volume
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
rbd:
monitors:
- '10.16.154.78:6789'
- '10.16.154.82:6789'
- '10.16.154.83:6789'
pool: kube
image: foo
fsType: ext4
readOnly: true
user: admin
keyring: /etc/ceph/keyring
imageformat: "2"
imagefeatures: "layering"
使用PVC的StatefulSet的YAML文件如下:
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
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
執行的操作:
kubectl create -f statefulset.yaml
# pvc 以“pvc名字-statefulset名字-編號”的方式命名
kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s
# 在volume中寫入index.html文件
for i in 0 1; do kubectl exec web-$i -- sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done
# 訪問nginx服務器,返回index.html的內容
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
hello web-0
hello web-1
# 刪除pod
kubectl delete pod -l app=nginx
# 在被重新創建出來的 Pod 容器裏訪問 http://localhost
$ kubectl exec -it web-0 -- curl localhost
hello web-0
4.2 原理
根據以上的實例發現,在pod刪除時,並沒有刪除Pod對應的PVC和PV,而Volume寫入的數據也依然保存在遠程存儲服務中。
基於以上的現象,我們得出StatefulSet管理存儲狀態的原理如下所示:
-
- StatefulSet的控制器直接管理的是Pod,StatefulSet會在每個Pod實例後加一個編號,用於區分實例的不同;
-
- Kubernetes通過Headless Service,爲這些有編號的Pod,在DNS服務器中生成帶同樣編號的DNS記錄;
-
- StatefulSet爲每個pod創建一個同樣編號的PVC,這樣Kubernetes通過PV機制爲每個PVC綁定上對應的PV,保證每一個Pod一直擁有同一個獨立的Volume。