概述
該篇介紹 statefulset 的特點,我們通過實踐部分驗證 statefulset 的特點 .
有狀態應用和無狀態應用
實例之間有不對等關系,以及實例對外部數據有依賴關系的應用,就被稱為“有狀態應用”(Stateful Application),例如 : web 應用和數據庫,比如我們可以用;相反,沒有與外界有關系的應用,例如一個計算模塊(輸入一個值經過計算返回一個結果的模塊應用),比如一個推送模塊 , StatefulSet 和 Deployment 可以說對應着有狀態應用和無狀態應用 ,也可以說 StatefulSet 是一種特殊的 Deployment。
StatefulSet
k8s 這個工具是能夠支持有狀態應用的部署的, 得益於“控制器模式”的設計思想,Kubernetes 項目很早就在 Deployment 的基礎上,擴展出了對“有狀態應用”的初步支持。這個編排功能,就是:StatefulSet。 StatefulSet 的設計其實非常容易理解。它把真實世界里的應用狀態,抽象為了兩種情況:
-
拓撲狀態。這種情況意味着,應用的多個實例之間不是完全對等的關系。這些應用實例,必須按照某些順序啟動,比如應用的主節點 A 要先於從節點 B 啟動。而如果你把 A 和 B 兩個 Pod 刪除掉,它們再次被創建出來時也必須嚴格按照這個順序才行。並且,新創建出來的 Pod,必須和原來 Pod 的網絡標識一樣,這樣原先的訪問者才能使用同樣的方法,訪問到這個新 Pod。(操作的時候有順序)
-
存儲狀態。這種情況意味着,應用的多個實例分別綁定了不同的存儲數據。對於這些應用實例來說,Pod A 第一次讀取到的數據,和隔了十分鍾之后再次讀取到的數據,應該是同一份,哪怕在此期間 Pod A 被重新創建過。這種情況最典型的例子,就是一個數據庫應用的多個存儲實例。(依賴於某個特定的外部條件)
我們細讀上面這兩個特點,可不可以用兩種場景來表述呢?
- 拓撲關系,我必須保證 A 優先於 B 啟動,當刪除的時候,B 必須優先於 A 先刪除
- 存儲關系,比如 A 的 DNS 是
example.a.com
, B的是example.b.com
, 當某些情況下,A 和 B 都異常退出后 ,k8s 會重新為 A B分配 node ,再次啟動 pod , 那么此時我訪問example.a.com
,必須訪問的還是 A , 訪問example.b.com
還是訪問的 。
所以,StatefulSet 的核心功能,就是通過某種方式記錄這些狀態,然后在 Pod 被重新創建時,能夠為新 Pod 恢復這些狀態。
Headless Service
以下描述來自課程,非原創
我在和你一起討論 Kubernetes 架構的時候就曾介紹過,Service 是 Kubernetes 項目中用來將一組 Pod 暴露給外界訪問的一種機制。比如,一個 Deployment 有 3 個 Pod,那么我就可以定義一個 Service。然后,用戶只要能訪問到這個 Service,它就能訪問到某個具體的 Pod。
那么,這個 Service 又是如何被訪問的呢?
-
第一種方式,是以 Service 的 VIP(Virtual IP,即:虛擬 IP)方式。比如:當我訪問 10.0.23.1 這個 Service 的 IP 地址時,10.0.23.1 其實就是一個 VIP,它會把請求轉發到該 Service 所代理的某一個 Pod 上。這里的具體原理,我會在后續的 Service 章節中進行詳細介紹。
-
第二種方式,就是以 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 對應的 YAML 文件:
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None ## 看這個 selector: app: nginx
可以看到,所謂的 Headless Service,其實仍是一個標准 Service 的 YAML 文件。只不過,它的 clusterIP 字段的值是:None,即:這個 Service,沒有一個 VIP 作為“頭”。這也就是 Headless 的含義。所以,這個 Service 被創建后並不會被分配一個 VIP,而是會以 DNS 記錄的方式暴露出它所代理的 Pod。 當你按照這樣的方式創建了一個 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都會被綁定一個這樣格式的 DNS 記錄,如下所示:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
這個 DNS 記錄,正是 Kubernetes 項目為 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。有了這個“可解析身份”,只要你知道了一個 Pod 的名字,以及它對應的 Service 的名字,你就可以非常確定地通過這條 DNS 記錄訪問到 Pod 的 IP 地址。
實踐
創建 Headless Service 和 StatefulSet
Headless Service 的 yaml 文件
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx
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
然后我們通過命令運行這兩個 yaml
$ kubectl create -f svc.yaml $ kubectl get service nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP None <none> 80/TCP 10s $ kubectl create -f statefulset.yaml $ kubectl get statefulset web NAME DESIRED CURRENT AGE web 2 1 19s [root@k8s-master ~]# kubectl get pod NAME READY STATUS RESTARTS AGE lifecycle-demo 1/1 Running 4 18d nginx-deployment-5d59d67564-bdn4d 1/1 Running 0 3h54m nginx-deployment-5d59d67564-cj9qh 1/1 Running 0 3h54m test-projected-volume 1/1 Running 3 17d web-0 1/1 Running 0 3h54m web-1 1/1 Running 0 3h54m
我們可以看到 web-0
和web-1
是 statefulset 生成的,可以看到默認生成的pod 命令上有編號,該編號就是啟動順序相關的。
$ kubectl exec web-0 -- sh -c 'hostname' web-0 $ kubectl exec web-1 -- sh -c 'hostname' web-1
上面的命令的可以查詢到這兩個pod的主機名。下面我們需要驗證是否可以通過 DNS 找到對應的 pod .
通過 DNS 查詢到對應 pod 的 ip
我們通過創建一個新的 pod ,然后在 pod 里查詢 web 的信息。
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
通過這條命令,我們啟動了一個一次性的 Pod ,對應的鏡像是 busybox:1.28.4
,因為–rm 意味着 Pod 退出后就會被刪除掉。然后,在這個 Pod 的容器里面,我們嘗試用 nslookup 命令,解析一下 Pod 對應的 Headless Service:
$ nslookup web-0.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.7 $ nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.7
然后我們在另外的會話中,我們先刪除我們剛創建的pod,不出意外的話,新的 pod 將會創建 ,那么我們重新執行上面的步驟,在新的pod里查看 DNS 信息,假如依舊是保持原有的 DNS 信息,那么就成功驗證了 statefulset 第二條的 "存儲狀態" 的特點.
$ kubectl delete pod -l app=nginx pod "web-0" deleted pod "web-1" deleted
如果手速夠快,我們還可以看到 pod 的創建過程, web-0 總是優先於 web-1 ,刪除的話則是相反的順序 .
$ kubectl get pod -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 ContainerCreating 0 0s NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 32s
總結
StatefulSet 這個控制器的主要作用之一,就是使用 Pod 模板創建 Pod 的時候,對它們進行編號,並且按照編號順序逐一完成創建工作。而當 StatefulSet 的“控制循環”發現 Pod 的“實際狀態”與“期望狀態”不一致,需要新建或者刪除 Pod 進行“調諧”的時候,它會嚴格按照這些 Pod 編號的順序,逐一完成這些操作。
參考資料
深入剖析Kubernetes
課程- 重要-動手實踐 https://kubernetes.io/zh/docs/tasks/run-application/run-replicated-stateful-application/
- https://blog.csdn.net/weixin_43936969/article/details/106289127
- https://draveness.me/kubernetes-statefulset/