StatefulSet在Kubernetes中的應用
概述
Kubernetes中有很多的控制器,比如常用的ReplicaSet,Deployment,DaemonSet,StatefulSet等。以這四種為例,我們可以將它們分為兩種,一種為有狀態控制器,一種為無狀態控制器,StatefulSet則為有狀態控制器,通常用於管理有狀態的服務,如:MySQL,Redis,MongoDB等。
有狀態控制器通常有以下幾個特點:
穩定的,唯一的網路標識
穩定的,持久的儲存
有序的,優雅的部署和伸縮
有序的,優雅的刪除和停止
有序的,自動的滾更新
也就是說如果我們的應用如有以上的任何一個特點我們就可以使用statefulset控制器來完成應用的部署。
網路標識:
Pod名稱:唯一且不會發生變化,由 $(statefulset name)-(0-N)組成,N為一個無限大的正整數,從0開始。
容器內計算機名:與Pod名稱一至且不會發生變化。
DNS A記錄:每一個副本都擁有唯一且不變的一條A記錄指定自己,格式組成:$(pod name).$(service name).$(namespace name).svc。
唯一的Pod標籤,通過StatefulSet建立的每個副本都擁有一個唯一的標籤,格式為:statefulset.kubernetes.io/pod-name=$(pod name),通常可以將新的service單獨關聯到此標籤,來解決某個Pod的問題等。
以上的標識只要配置不發生改變,發生重啟,升級,刪除後再建立這些標識都不會發生變化。
備註:Pod的IP地址會發生變化,但我們也可以使用另外的方案來結合StatefulSet實現固定IP的方案,通常不推薦這麼做。
持久儲存:
每個Pod都對應一個PVC,PVC的名稱組成:$(volumeClaimTemplates name)-$(pod name)-(0-N),N為一個無限大的正整數,從0開始。
當Pod被刪除的時候不會自動刪除對應的PVC,需要手動刪除。
每次Pod升級,重啟,刪除重新建立後都呆以保證使用的是首次使用的PVC,保證資料的唯一性與持久化。
有序:
當部署有N個副本的StatefulSet時候,嚴格按照從0到N的順序建立,並且下一個Pod建立的前提是上一個Pod已經Running狀態。
當刪除有N個副本的StatefulSet時候,嚴格按照從N到0的順序刪除,並且下一個Pod刪除的前提是上一個Pod已經完全Delete。
當擴容StatefulSet副本的時候,每增加一個pod前提是上一個Pod已經Running狀態。
當減少StatefulSet副本的時候,每刪除一個pod前提是上一個Pod已經完全Delete。
當升級StatefulSet的時候,嚴格按照從N到1的順序升級,並且下一個Pod升級的前提是上一個Pod已經Running狀態。
無頭服務(headless service)
無頭服務是Kubernetes中service型別的一種,為什麼需要無頭服務?通常我們在使用Deployment部署Pod的時候名稱是隨機的字串,Pod也是無序的。但 StatefulSet是有序且名稱唯一,每一個Pod不可以被取代,所以這時候就需要使用無頭服務來實現。
無頭服務特點:當使用Kubernetes內DNS解析去訪問Service的時候,會將解析的IP指向Service後端的Pod。當無頭服務與StatefulSet控制器使用的時候,因為每個Pod的資料可能是不一樣的,所以在Service名前面再加了一個Pod名,這樣我們的每個StatefulSet控制器的Pod就擁有了唯一的網路標識(A記錄)。
無頭服務案例:
apiVersion:v1 kind:Service metadata: namespace:test name:mysql-test spec: selector: app:mysql-test clusterIP:None ports: -port:3306 targetPort:3306
ClusterIP:當此項的值為“None”的時候,此服務就為無頭服務,Kubernetes不會給此服務分配IP地址。當使用type為NodePort的時候不可以使用無頭服務。
volumeClaimTemplates:
此為Kubernetes中的一個物件,物件的路徑為“statefulset.spec.volumeClaimTemplates”,用於給StatefulSet的Pod申請PVC,稱為卷申請模板,它會為每個Pod生成不同的PVC,關繫結到PV,從而實現Pod的專有儲存。
通常我們在使用Deployment中使用“deployment.spec.template.spec.volumes”與PVC建立關聯的時候,所有的副本使用的是相同的PV與PVC,他們的資料是相同的。而StatefulSet為實現各Pod的專有儲存,所以才有了“statefulset.spec.volumeClaimTemplates”此方法。
實際應用場景
以MySQL主從為例,我們將實現通過同一個StatefulSet控制器創建出主庫和從庫,主庫和從庫的my.cnf配置自定義,和從庫的自動擴容(目前實現半自動)
思路:
使用ConfigMap實現主庫和從庫的自定義配置
使用initContainers實現主容器在啟動之前初始化配置
關於initContainers簡單介紹
從上圖我們可以看出一個Pod的生命週期包含:initContainer,PostStart,PreStop,Liveness和Readiness。其中容器(Container)的生命週期只包含PostStart,PreStop,Liveness和Readiness,而initContainer是屬於在容器啟動之前的。
initContainer應用場景:
可以解決服務之間的依賴問題,如我們有一個Web服務和MySQL,如果我們將兩個服務同時啟動有可能會出現資料庫連線異常等問題,那麼我們可以通過initContainer去監測當只有MySQL啟動成功的時候再啟動Web。
做初始化配置,如我們現在做的MySQL主從案例,主庫與從庫my.cnf的server_id不一樣,我們無法使用同一配置,為減少管理上的麻煩也不推薦使用兩個StatefulSet控制器,那麼這時候我們就可以使用initContainer來完成我們的需求。我們可以將資料卷掛載到initContainer,在initContainer內產生的資料是可以被主容器所使用的,所以我們server_id通過initContainer內生成並寫入my.cnf。
在此不對初始化容器做過多介紹,詳情可參考:初始化容器,或者檢視官方文件。
首先建立名稱空間
apiVersion:v1 kind:Namespace metadata: name:test
定義ConfigMap配置檔案
apiVersion:v1 kind:ConfigMap metadata: namespace:test name:mysql-cnf data: master.cnf:| [mysqld] log-bin=mysql-bin binlog-ignore-db=information_schema,performance_schema,mysql,sys expire_logs_days=7 slave.cnf:| [mysqld] slave_parallel_workers=16 slave_parallel_type=logical_clock master_info_repository=TABLE relay_log_info_repository=TABLE relay_log_recovery=ON
根據自身情況,可自定義自己的選項。其中server_id不需要指定,將在初始化容器的時候寫入。
定義StatefulSet配置檔案
apiVersion:apps/v1 kind:StatefulSet metadata: namespace:test name:mysql-test spec: serviceName:mysql-test replicas:2 selector: matchLabels: app:mysql-test template: metadata: namespace:test name:mysql-test labels: app:mysql-test spec: initContainers: -name:init-mysql image:mysql:5.7 command: -bash -"-c" -| hostname=`echo$HOSTNAME|awk-F'-''{print$NF}'` server_id=$[hostname+100] if[[$hostname-eq0]];then cp/mnt/my.cnf.d/master.cnf/mnt/conf.d/my.cnf else cp/mnt/my.cnf.d/slave.cnf/mnt/conf.d/my.cnf fi echo"server_id=${server_id}">>/mnt/conf.d/my.cnf volumeMounts: -name:mysql-cnf mountPath:/mnt/my.cnf.d/ -name:conf mountPath:/mnt/conf.d/ containers: -name:mysql-test image:mysql:5.7 args:["--character_set_server=utf8","--collation-server=utf8_unicode_ci"] env: -name:MYSQL_ROOT_PASSWORD value:"!gogen123" volumeMounts: -name:conf mountPath:/etc/mysql/conf.d/ -name:mysql-data mountPath:/var/lib/mysql/ volumes: -name:mysql-cnf configMap: name:mysql-cnf -name:conf emptyDir:{} volumeClaimTemplates: -metadata: namespace:test name:mysql-data spec: accessModes:["ReadWriteOnce"] resources: requests: storage:2Gi storageClassName:nas --- apiVersion:v1 kind:Service metadata: namespace:test name:mysql-test spec: selector: app:mysql-test clusterIP:None ports: -port:3306 targetPort:3306
initContainers:該配置段主要實現主容器啟動時的配置初始化,初始化容器。因為StatefulSet控制器擁有唯一的網路標識,如計算機機名是唯一,所以我們根據計算機名做判斷 ,讓一個副本做Master,其餘的做Slave,從而將不同的配置複製到共享資料卷“conf”。此卷由我們自己定義,並採用emptyDir方式,前面我們講了在initContainers中產生的資料可以在主容器中使用,那麼同樣主容器也掛載此資料卷,那麼就可以使用我們自定義的配置,我們通過volumes直接將conf掛載到/etc/mysql/conf.d/目錄即可使用。
volumeClaimTemplates:關於此塊配置用於自動申請pvc並於pv進行關聯,因為我們使用的是阿里雲的NAS,預設情況下無法自動申請成功,需要定義一個儲存類(storageClassName),具體此引數的值可以通過 pv的配置獲取,可以將pv輸出為yaml檢視。
最後:
我們的應用已經啟動成功,還剩下最後一步。進入從庫的MySQL Shell內執行以下命令。
changemastertomaster_host='mysql-test-0.mysql-test.test.svc',master_port=3306,master_user='root',master_password='!gogen123',master_log_file='mysql-bin.000003',master_log_pos=154; startslave;
正常情況下這時候檢視我們的從庫狀態就已經成功,如果需要擴容,擴容後的從庫同樣也只需要執行上面兩條SQL命令即可。
總結:最後一步還需要手動完成,不是特別完善,也大大提高了我們部署時的麻煩,擴容也相對方便,後續考慮將最後一步也實現自動化。
轉載於:https://blog.51cto.com/270142877/2366067