1. 程式人生 > 實用技巧 >StatefulSet在Kubernetes中的應用

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簡單介紹

loap

從上圖我們可以看出一個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