關注微信公眾號:CodingTechWork,一起學習進步。
引言
我們都知道在k8s集群中,Deployment是用來部署無狀態的服務,那有狀態的服務是用什么資源對象來部署呢?無狀態和有狀態服務部署的區別是什么?有狀態的pod肯定需要獨立的存儲卷,這樣才能保證故障后尋找數據就地恢復原狀態,那如何實現多個pod擁有自己獨立存儲卷?下面我們來看看如何演進方案。
演進
手動創建多個pod
手動創建多個pod,每個pod使用一個獨立的持久卷聲明,但是需要我們手動管理這些pod,當發生故障后,需要重新手動創建這些pod,從而保證有狀態恢復。
1個RS對應1個pod
手動創建pod,肯定不便於維護,我們在每個pod的上一層來操作,創建多個ReplicaSet,每個ReplicaSet的副本數設置為1,這樣pod和ReplicaSet是一一對應的,每個ReplicaSet的pod模板都關聯一個獨立的持久卷聲明。這種可以達到某個節點故障或pod誤刪時自動重新調度創建pod的效果,但是對於伸縮副本時,又需要手動創建或刪除ReplicaSet,還是達不到一次性創建、更新、刪除后,后期自動調度的效果。
所有pod共享同一個PV
創建多個ReplicaSet對應多個pod,還是會有伸縮問題,且不好維護,如果只創建一個ReplicaSet,讓所有pod共享同一持久卷,但每個pod是使用同一持久卷的不同目錄。
創建StatefulSet
所有pod使用同一數據卷中不同目錄要求實例之間相互協作,由於不能在一個pod模板中給所有pod做不同目錄的指定,需要讓pod自己識別選擇一個其他實例沒有使用的目錄,這種共享存儲將會給集群帶來性能問題。
若每個pod擁有自己的網絡標識,就算失敗了,再恢復時,還是原有的穩定的網絡標識。這就需要使用StatefulSet資源來部署這些服務,這些服務中每個實例都是不可替代的,都有穩定的名字和網絡標識。
StatefulSet介紹
有狀態服務集群特點
- 每個節點都有固定的身份ID,通過ID可以使集群內成員相互發現並通信;
- 集群規模比較固定;
- 集群內每個節點有狀態,一般會持久化數據到永久存儲中,這樣失敗了的實例可以通過持久化數據再恢復原有狀態;
- 磁盤損壞,則某個節點無法正常工作,集群功能將受損受阻;
Statefulset概述
StatefulSet是k8s從1.4版本引入的PetSet
資源對象發展到1.5版本更名而來,在k8s集群中用於部署有狀態服務
。為何之前叫PetSet?是因為拿寵物和牛作類比,把應用看做是寵物,給每個實例都起一個名字,在寵物店里,若一個寵物死掉,我們買不到一只一模一樣的,用戶肯定會察覺到差異,若要代替這只寵物,我們必須找到一只屬性及行為和之前完全一致的寵物,同樣的,對於應用而言,我們需要找到狀態和標識和之前一致的實例來代替之前故障實例。
StatefulSet特點
- StatefulSet中每個Pod有穩定、唯一的網絡標識(用於發現集群內其他成員),Pod名稱由StatefulSet名+有序數字組成,如ZK服務對應的StatefulSet名為test-zk,副本數為3,則第一個pod名為test-zk-0,第二個pod名稱為test-zk-1,第三個pod名稱為test-zk-2。
- StatefulSet所控制的pod副本啟停順序是有序的。
- StatefulSet中的Pod采用穩定的持久化存儲卷(PV或者PVC實現),刪除pod時,默認不會刪除與StatefulSet相關的存儲卷。
- StatefulSet需要和
Headless Service
(沒有Cluster IP的Service)進行配合,一般在StatefulSet中指定spec.serviceName
的名稱與Service資源中的metadata.name
保持一致。
穩定的網絡標識
pod名有序
StatefulSet部署有狀態應用時,創建出的每個pod都有命名規則,pod名是由StatefulSet名+有序數字組成,每個pod都有一個從0開始的順序索引,這個順序索引體現在pod名稱、主機名以及pod對應的固定存儲
上(pvc名稱同樣會有順序索引)。如:3節點的zk的StatefulSet集群對應的StatefulSet名稱為test-zk-ss,則對應的3個pod名稱為test-zk-ss-0,test-zk-ss-1,test-zk-ss-2。
pvc名有序
如果k8s集群中沒有StorageClass的動態存儲卷,我們也可以提前手動創建多個PV、PVC,手動創建的PVC名稱必須符合之后創建的StatefulSet命名規則:$(volumeClaimTemplates.name)-$(pod_name)
,如Statefulset控制的3個pod對應名稱為test-zk-ss-0,test-zk-ss-1,test-zk-ss-2,volumeClaimTemplates.name=test-pvc,則自動創建出來的pvc名稱分別為:test-pvc-test-zk-ss-0、test-pvc-test-zk-ss-1、test-pvc-test-zk-ss-2。
Headless Service
Headless Service是沒有Cluster IP的Service(與普通Service的區別),在Headless Service中可以看到spec.ClusterIP=None
。
若解析Headless Service的DNS域名,返回的是該Service對應的全部pod的Endpoint列表,StatefulSet在Headless Service基礎上為StatefulSet控制的每個pod實例創建DNS域名,格式為$(pod_name).$(headless_service_name)
,全限定域名為:FQDN:$(pod_name).$(headless_service_name).$(namespace_name).svc.cluster.local
如:3節點的zk的StatefulSet集群對應的StatefulSet名稱為test-zk-ss,Headless Service名稱為test-zk-svc,則StatefulSet控制的3個pod對應DNS分別為:test-zk-ss-0.test-zk-svc,test-zk-ss-1.test-zk-svc,test-zk-ss-2.test-zk-svc。若命名空間名稱為test-ns,則3個pod對應的FQDN分別為test-zk-ss-0.test-zk-svc.test-ns.svc.cluster.local,test-zk-ss-1.test-zk-svc.test-ns.svc.cluster.local,test-zk-ss-2.test-zk-svc.test-ns.svc.cluster.local。
StatefulSet運行原理
pod啟停過程
StatefulSet控制的pod啟停過程,類似於擴縮容過程。
假設有N個副本數。
啟動時,先啟動pod序號為0的,然后依次遞增至N-1,操作第N個pod時,前N-1個pod已經是運行並准備好的狀態(Running狀態)。
停止時,先停止pod序號為最大的N-1,然后依次遞減至0,操作第N-1個pod時,第N個pod已經是停止狀態。
重啟pod流程
我們先看一下ReplicaSet管理的一個pod如果消失時,如何重啟一個新的pod來替換舊的。
當一個StatefulSet管理的一個pod因為發生故障或被人為刪除而消失后,StatefulSet可以保證再去重啟一個新的pod實例去替換它,這個新pod實例與之前的pod保持完全一致的行為(pod名、主機名)。
我們可以從ReplicaSet和StatefulSet重啟pod替換消失pod的過程中看出兩種過程中的標識是很明顯的差別,也是無狀態和有狀態的差異。
pod擴縮容過程
pod擴容過程
假設StatefulSet名稱為A,從副本數1擴容到3。擴容一個StatefulSet時會使用下一個還沒有使用到的順序索引值進行新pod實例的創建,依次遞增1。
pod縮容過程
假設StatefulSet名稱為A,從副本數3縮容到1。縮容一個StatefulSet時,StatefulSet是明確知道先刪除最高索引值的實例,縮容刪除哪個pod是可控預知的,而ReplicaSet是不知道會刪除哪個實例。
由於StatefulSet縮容時是從高索引挨個刪除,每次只會操作一個pod實例,所以有狀態應用的縮容過程很慢。
StatefulSet的at-most-one語義
在副本數縮容再擴容時,如果k8s沒有保障機制,很容易出現正在縮容的pod還在運行,又新建一個一樣標識的pod進行pvc綁定,這會帶來問題。
對於ReplicaSet的pod來說,會以一個隨機的標識來創建pod,不會存在兩個相同標識的進程同時運行。而StatefulSet是必須在准確確認一個pod不再運行后,才會去創建替換的pod
,從而保證兩個擁有相同標記和綁定相同PVC的有狀態的pod實例不會同時運行,這便是at-most-one
的語義。
StatefulSet提供穩定的獨立存儲
一個有狀態的pod需要有自己專屬的存儲,該pod被重新調度室,新的pod與舊pod保持一致的標識,且新的pod實例掛載相同的存儲。
持久卷聲明模板
如何在同一個pod模板中為所有pod實例關聯不同的持久卷?
Statefulset在pod模板中添加了卷聲明模板,自動創建的pvc名稱將會符合規則:$(volumeClaimTemplates.metadata.name).$(pod_name)
持久卷創建和刪除
當StatefulSet增加一個副本時,會創建對應的pvc持久卷聲明。創建N個副本,就會有N個PVC與之對應。
當StatefulSet減少副本時,會從高索引值的pod名開始刪除pod,但是PVC不會被刪除。如果需要釋放特定的持久卷,需要手動刪除對應的持久卷聲明
。
當先縮容,再擴容時,由於舊pod對應的pvc不會被自動刪除,擴容重建的pod實例會綁定到對應序號的pvc上。
從上圖我們可以看出,StatefulSet A先從副本2縮容為副本1,對應的Pod A-1會自動刪除,但是PVC A-1仍然保留不刪除。當StatefulSet A從副本1擴容到副本2時,自動創建新的Pod A-1與舊pod保持一致的標識,PVC A-1被重新掛載到Pod A-1上。
StatefulSet使用命令
假設某個應用的StatefulSet的yaml模板為test-zk-ss.yaml,StatefulSet名稱為test-zk-ss,副本數為3個。更新后的模板為test-zk-ss-new.yaml
創建
基於模板創建kubectl create -f test-zk-ss.yaml
刪除
- 基於模板刪除:
kubectl delete -f test-zk-ss.yaml
- 基於名稱刪除:
kubectl delete statefulset test-zk-ss
更新
- 基於模板更新:
kubectl apply -f test-zk-ss-new.yaml
- 基於名稱更新:
kubectl edit statefulset test-zk-ss
查詢
- 基於模板查看:
kubectl get statefulset test-zk-ss -o yaml
- 基於名稱查看:
kubectl describe statefulset test-zk-ss
Q&A
k8s中無狀態服務和有狀態服務部署的區別?
無狀態:
- pod命名:pod名由資源名+隨機的字符串組成;
- 數據存儲:多個實例pod可以共享相同的持久化數據,存儲不是必要條件;
- 擴縮容:可以隨意擴縮容某個pod,不會指定某個pod進行擴縮容;
- 啟停順序:因為pod名的序號是隨機串,無啟停順序之分;
- 無狀態k8s資源:ReplicaSet、ReplicationController、Deployment、DaemonSet、Job等資源;
- 無狀態服務:tomcat、nginx等;
有狀態
這里假設有N個pod;
- pod命名:pod名由statefulset資源名+有序的數字組成(0,1,2...N-1),且pod有特定的網絡標識;
- 數據存儲:有狀態的服務對應實例需要有自己的獨立持久卷存儲;
- 擴縮容:擴縮容不可隨意,縮容是從數字最大的開始遞減,擴容是在原有pod序號基礎上遞增1。
- 啟停順序:pod啟停是有順序的,啟動時,先啟動pod序號為0的,然后依次遞增至N-1;停止時,先停止pod序號為最大的N-1,然后依次遞減至0;
- 有狀態k8s資源:StatefulSet資源;
- 有狀態服務:Kafka、ZooKeeper、MySql、MongoDB以及一些需要保存日志的應用等服務;