大家好,我是喬克,一個愛折騰的運維工程,一個睡覺都被自己醜醒的雲原生愛好者。
作者:喬克
公衆号:運維開發故事
我們在《Kubernetes工作負載管理》中主要介紹了無狀态應用的管理,當時也有提到有狀态應用,但是由于那時候還沒有解釋資料如何持久化就沒有做深度的介紹,而在這章,我們會着重介紹如何進行有狀态應用的管理。
什麼是有狀态應用
執行個體之間的不等關系以及執行個體對外資料有依賴關系的應用,就被稱為"有狀态應用"。
所謂執行個體之間的不等關系即對分布式應用來說,各執行個體,各應用之間往往有比較大的依賴關系,比如某個應用必須先于其他應用啟動,否則其他應用将不能啟動等。
對外資料有依賴關系的應用,最顯著的就是資料庫應用,對于資料庫應用,我們是需要持久化儲存其資料的,如果是無狀态應用,在資料庫重新開機資料和應用就失去了聯系,這顯然是違背我們的初衷,不能投入生産的。
是以,為了解決Kubernetes中有狀态應用的有效支援,Kubernetes使用StatefulSet來編排管理有狀态應用。
StatefulSet類似于ReplicaSet,不同之處在于它可以控制Pod的啟動順序,它為每個Pod設定唯一的辨別。其具有一下功能:
- 穩定的,唯一的網絡辨別符
- 穩定的,持久化存儲
- 有序的,優雅部署和縮放
- 有序的,自動滾動更新
StatefulSet的設計很容易了解,它把現實世界抽象為以下兩種情況:
(1)、拓撲狀态。這就意味着應用之間是不對等關系,應用要按某種順序啟動,即使應用重新開機,也必須按其規定的順序重新開機,并且重新開機後其網絡辨別必須和原來的一樣,這樣才能保證原通路者能通過同樣的方法通路新的Pod;
(2)、存儲狀态 。這就意味着應用綁定了存儲資料,不論什麼時候,不論什麼情況,對應用來說,隻要存儲裡的資料沒有變化,讀取到的資料應該是同一份;
是以StatefulSet的核心功能就是以某種方式記錄Pod的狀态,然後在Pod被重新建立時,通過某種方法恢複其狀态。
如何使用StatefulSet
在《Kubernetes應用通路管理》中,我們介紹了Service,它是為一組Pod提供外部通路的一種方式。通常,我們使用 Service通路Pod有一下兩種方式:
(1)、通過Cluster IP,這個Clustre IP就相當于VIP,我們通路這個IP,就會将請求轉發到後端Pod上;
(2)、通過DNS方式,通過這種方式首先得確定Kubernetes叢集中有DNS服務。這個時候我們隻要通路"my-service.my-namespace.svc,cluster.local",就可以通路到名為my-service的Service所代理的後端Pod;
而對于第二種方式,有下面兩種處理方法:
(1)、Normal Service,即解析域名,得到的是Cluster IP,然後再按照方式一通路;
(2)、Headless Service,即解析域名,得到的是後端某個Pod的IP位址,這樣就可以直接通路;
而在使用StatefulSet的時候,主要用到Headless Service,還記得Headless Service怎麼定義的嗎?
我們隻需要把ClusterIP設定為None即可,如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-headless-service
labels:
name: nginx-headless-service
spec:
clusterIP: None
selector:
name: nginx
ports:
- port: 8000
targetPort: 80
了解了Headless Service,還需要了解PV、PVC是怎麼使用的,如果忘記了,可以移步《Kubernetes資料持久化管理》回顧,這裡就不再贅述了。
下面,我們開始使用StatefulSet。
首先,我們建立兩個個PV,因為準備為有狀态應用建立兩個副本,如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv01
labels:
storage: pv
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
persistentVolumeReclaimPolicy: Recycle
nfs:
path: /data/k8s
server: 192.168.205.128
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv02
labels:
storage: pv
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
persistentVolumeReclaimPolicy: Recycle
nfs:
path: /data/k8s
server: 192.168.205.128
然後編寫StatefulSet需要的YAML檔案,如下:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
role: stateful
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
role: stateful
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
注意上面的 YAML 檔案中和volumeMounts進行關聯的是一個新的屬性:volumeClaimTemplates,該屬性會自動聲明一個 pvc 對象和 pv 進行管理,而serviceName: "nginx"表示在執行控制循環的時候,用nginx這個Headless Service來儲存Pod的可解析身份。
建立完成後,可以看到會起兩個Pod:
$ kubectl get pod | grep web
web-0 1/1 Running 0 2m45s
web-1 1/1 Running 0 2m41s
從這兩個Pod的指令可以看到,它們的名字不像Deployment那樣随機生成的字元串,而是0,1這樣的序号。這是因為StatefulSet要保證每個Pod順序,確定每次重新開機或者更新,每個Pod依然保持以前的資料,不會錯亂。是以StatefulSet會以
[statefulset-name]-[index]
規則進行命名,其中index從0開始。而且每個Pod的建立是有順序的,如上隻有web-0進入running狀态後,web-1才建立。
當兩個Pod都進入running狀态後,就可以檢視其各自的網絡身份了,我們通過kubectl exec來檢視,如下:
$ kubectl exec web-0 -- sh -c 'hostname'
web-0
$ kubectl exec web-1 -- sh -c 'hostname'
web-1
可以看到這兩個pod的hostname和pod的名字是一緻的,都被配置設定為對應的編号,接下來我們用DNS的方式來通路Headless Service。
我們先啟動一個調試Pod,如下:
apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: lansible/dnstools
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always
然後進入dnsutils容器進行解析,如下:
$ kubectl exec -it dnsutils -- /bin/sh
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-0.nginx.default.svc.cluster.local
Address: 172.16.51.247
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-1.nginx.default.svc.cluster.local
Address: 172.16.51.251
/ #
從nslookup的結果分析,在通路web-0.nginx的時候解析的是web-0這個Pod的IP,另一個亦然。這表示,如果我們在應用中配置web-0.nginx,則隻會調用web-0這個Pod,在配置有狀态應用,比如Zookeeper的時候,我們需要在配置檔案裡指定zkServer,這時候就可以指定類似:zk-0.zookeeper,zk-1.zookeeper。
如果我們現在更新StatefuleSet,起更新順序是怎麼樣的呢?
首先,我們新開一個終端,輸入以下指令用以觀察:
$ kubectl get pods -w -l role=stateful
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 67m
web-1 1/1 Running 0 67m
然後使用以下指令更新應用的鏡像,如下:
$ kubectl set image statefulset/web nginx=nginx:1.8
然後觀察web應用的更新順序,如下:
$ kubectl get pods -w -l role=stateful
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 67m
web-1 1/1 Running 0 67m
web-1 1/1 Terminating 0 68m
web-1 1/1 Terminating 0 68m
web-1 0/1 Terminating 0 68m
web-1 0/1 Terminating 0 68m
web-1 0/1 Terminating 0 68m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 10s
web-0 1/1 Terminating 0 69m
web-0 1/1 Terminating 0 69m
web-0 0/1 Terminating 0 69m
web-0 0/1 Terminating 0 69m
web-0 0/1 Terminating 0 69m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 0/1 ContainerCreating 0 1s
web-0 1/1 Running 0 9s
從整個順序可以看到,起更新是從後往前進行更新的,也就是先更新web-1的pod,再更新web-0的pod。通過這種嚴格的對應規則,StatefulSet就保證了Pod的網絡辨別的穩定性,通過這個方法,就可以把Pod的拓撲狀态按照Pod的名字+編号的方式固定起來。此外,Kubernetes還為每一個Pod提供了一個固定并且唯一的通路入口,即這個Pod的DNS記錄。