作者:Jack47
轉載請保留作者和原文出處
PS:如果喜歡我寫的文章,歡迎關注我的微信公眾賬號程序員傑克,兩邊的文章會同步,也可以添加我的RSS訂閱源。
Kubernetes對無狀態服務有完善的支持,但是對於有狀態的服務,是從1.3版本開始,才逐漸支持的。
有狀態的應用程序
一般情況下,nginx或者web server(不包含MySQL)自身都是不需要保存數據的,對於 web server,數據會保存在專門做持久化的節點上。所以這些節點可以隨意擴容或者縮容,只要簡單的增加或減少副本的數量就可以。但是很多有狀態的程序都需要集群式的部署,意味着節點需要形成群組關系,每個節點需要一個唯一的ID(例如Kafka BrokerId, Zookeeper myid)來作為集群內部每個成員的標識,集群內節點之間進行內部通信時需要用到這些標識。傳統的做法是管理員會把這些程序部署到穩定的,長期存活的節點上去,這些節點有持久化的存儲和靜態的IP地址。這樣某個應用的實例就跟底層物理基礎設施比如某台機器,某個IP地址耦合在一起了。Kubernets中StatefulSet
的目標是通過把標識分配給應用程序的某個不依賴於底層物理基礎設施的特定實例來解耦這種依賴關系。(消費方不使用靜態的IP,而是通過DNS域名去找到某台特定機器)
StatefulSet
前提
使用StatefulSet
的前提:
- Kubernetes集群的版本 >=1.5
- 安裝好DNS集群插件,版本 >=15
特點
StatefulSet
(1.5版本之前叫做PetSet)為什么適合有狀態的程序,因為它相比於Deployment
有以下特點:
- 穩定的,唯一的網絡標識,可以用來發現集群內部的其他成員。比如StatefulSet的名字叫kafka,那么第一個起來的Pet叫kafka-0,第二個叫 kafk-1,依次類推。
- 穩定的持久化存儲:通過Kubernetes的PV/PVC或者外部存儲(預先提供的)來實現
- 啟動或關閉時保證有序:優雅的部署和伸縮性: 操作第n個pod時,前n-1個pod已經是運行且准備好的狀態。 有序的,優雅的刪除和終止操作:從 n, n-1, ... 1, 0 這樣的順序刪除
上述提到的“穩定”指的是Pod在多次重新調度時保持穩定,即存儲,DNS名稱,hostname都是跟Pod綁定到一起的,跟Pod被調度到哪個節點沒關系。
所以Zookeeper, Etcd 或 Elasticsearch這類需要穩定的集群成員的應用時,就可以用StatefulSet
。通過查詢無頭服務域名的A記錄,就可以得到集群內成員的域名信息。
限制
StatefulSet
也有一些限制:
- Pod的存儲必須是通過 PersistentVolume Provisioner基於
storeage類
來提供,或者是管理員預先提供的外部存儲 - 刪除或者縮容不會刪除跟StatefulSet相關的卷,這是為了保證數據的安全
- StatefulSet現在需要一個無頭服務(Headless Service)來負責生成Pods的唯一網絡標示,需要開發人員創建這個服務
- 對
StatefulSet
的升級是一個手工的過程
無頭服務(Headless Service)
要定義一個服務(Service)為無頭服務(Headless Service),需要把Service定義中的ClusterIP配置項設置為空: spec.clusterIP:None。和普通Service相比,Headless Service沒有ClusterIP(所以沒有負載均衡),它會給一個集群內部的每個成員提供一個唯一的DNS域名來作為每個成員的網絡標識,集群內部成員之間使用域名通信。無頭服務管理的域名是如下的格式:$(service_name).$(k8s_namespace).svc.cluster.local
。其中的 "cluster.local"是集群的域名,除非做了配置,否則集群域名默認就是cluster.local。StatefulSet下創建的每個Pod,得到一個對應的DNS子域名,格式如下:
$(podname).$(governing_service_domain),這里 governing_service_domain
是由StatefulSet中定義的serviceName來決定。舉例子,無頭服務管理的kafka的域名是:kafka.test.svc.cluster.local, 創建的Pod得到的子域名是 kafka-1.kafka.test.svc.cluster.local。注意這里提到的域名,都是由kuber-dns組件管理的集群內部使用的域名,可以通過命令來查詢:
$ nslookup my-nginx
Server: 192.168.16.53
Address 1: 192.168.16.53
Name: my-nginx
Address 1: 192.168.16.132
而普通Service情況下,Pod名字后面是隨機數,需要通過Service來做負載均衡。
當一個StatefulSet掛掉,新創建的StatefulSet會被賦予跟原來的Pod一樣的名字,通過這個名字來匹配到原來的存儲,實現了狀態保存。因為上文提到了,每個Pod的標識附着在Pod上,無論pod被重新調度到了哪里。
成員發現
一個Pod可以通過 Downward api機制來知道自己的pod名字,也可以運行hostname
來發現自己的DNS名字。StatefuleSet的服務名(governing service)在創建的時刻就已知了,所以只需要通過一個約定的環境變量把服務名傳遞給POD就可以。
一點八卦
為什么從PetSet
改名字到StatefulSet
,也是很有意思的,感興趣的同學可以去這里看看:
Please Consider changing the name of PetSet before General Availability
實例
Kubernetes官方博客上有一個在Kubernetes上運行MogoDB的例子,見這里。
借用一下里面的架構圖:
在StatefulSet之上運行MongoDB
如果您看了本篇博客,覺得對您有所收獲,請點擊右下角的“推薦”,讓更多人看到!

