本篇是基於k8s-v1.19.0版本
1.介紹
- RC、Deployment、DaemonSet都是面向無狀態的服務,它們所管理的Pod的IP、名字,啟停順序等都是隨機的,而StatefulSet是什么?顧名思義,有狀態的集合,管理所有有狀態的服務,比如MySQL、MongoDB集群等。
- StatefulSet本質上是Deployment的一種變體,在v1.9版本中已成為GA版本,它為了解決有狀態服務的問題,它所管理的Pod擁有固定的Pod名稱,啟停順序,在StatefulSet中,Pod名字稱為網絡標識(hostname),還必須要用到共享存儲。
- 在Deployment中,與之對應的服務是service,而在StatefulSet中與之對應的headless service,即無頭服務,與service的區別就是它沒有Cluster IP,解析它的名稱時將返回該Headless Service對應的全部Pod的Endpoint列表。
- 除此之外,StatefulSet在Headless Service的基礎上又為StatefulSet控制的每個Pod副本創建了一個DNS域名,這個域名的格式為: $(podname).(headless server name) FQDN:$(podname).(headless server name).namespace.svc.cluster.local
2.特點
Pod一致性:包含次序(啟動、停止次序)、網絡一致性。此一致性與Pod相關,與被調度到哪個node節點無關;
- 穩定的次序:對於N個副本的StatefulSet,每個Pod都在[0,N)的范圍內分配一個數字序號,且是唯一的;
- 穩定的網絡:Pod的hostname模式為( StatefulSet名稱 ) − (statefulset名稱)-(statefulset名稱)−(序號);
- 穩定的存儲:通過VolumeClaimTemplate為每個Pod創建一個PV。刪除、減少副本,不會刪除相關的卷。
3.組成部分
- Headless Service:用來定義Pod網絡標識( DNS domain);
- volumeClaimTemplates :存儲卷申請模板,創建PVC,指定pvc名稱大小,將自動創建pvc,且pvc必須由存儲類供應;
- StatefulSet :定義具體應用,名為Nginx,有三個Pod副本,並為每個Pod定義了一個域名部署statefulset。
1)為什么需要 headless service 無頭服務?
在用Deployment時,每一個Pod名稱是沒有順序的,是隨機字符串,因此是Pod名稱是無序的,但是在statefulset中要求必須是有序 ,每一個pod不能被隨意取代,pod重建后pod名稱還是一樣的。而pod IP是變化的,
所以是以Pod名稱來識別。pod名稱是pod唯一性的標識符,必須持久穩定有效。這時候要用到無頭服務,它可以給每個Pod一個唯一的名稱 。
2)為什么需要volumeClaimTemplate?
對於有狀態的副本集都會用到持久存儲,對於分布式系統來講,它的最大特點是數據是不一樣的,所以各個節點不能使用同一存儲卷,每個節點有自已的專用存儲,但是如果在Deployment中的Pod template里定義的存儲卷,
是所有副本集共用一個存儲卷,數據是相同的,因為是基於模板來的 ,而statefulset中每個Pod都要自已的專有存儲卷,所以statefulset的存儲卷就不能再用Pod模板來創建了,於是statefulSet使用volumeClaimTemplate,稱為卷申請模板,
它會為每個Pod生成不同的pvc,並綁定pv,從而實現各pod有專用存儲。這就是為什么要用volumeClaimTemplate的原因。
4.StatefulSet詳解
- kubectl explain sts.spec :主要字段解釋
- replicas :副本數
- selector:那個pod是由自己管理的
- serviceName:必須關聯到一個無頭服務商
- template:定義pod模板(其中定義關聯那個存儲卷)
- volumeClaimTemplates :生成PVC
5.部署一個statefulset服務
本教程假設你的集群被配置為動態的提供 PersistentVolume,動態storeclass參考https://www.cnblogs.com/wuxinchun/p/15266445.html;如果沒有這樣配置,在開始本教程之前,你需要手動准備存儲卷。
如果集群中沒有StorageClass的動態供應PVC的機制,也可以提前手動創建多個PV、PVC,手動創建的PVC名稱必須符合之后創建的StatefulSet命名規則:(volumeClaimTemplates.name)-(pod_name)
5.1statefulset控制器創建nginx
[root@k8s-master statefulset]# pwd /root/k8s_practice/statefulset [root@k8s-master statefulset]# cat nginx-statefulset.yaml apiVersion: v1 kind: Service metadata: name: nginx-headless labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None #注意此處的值,None表示無頭服務 selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx #has to match .spec.template.metadata.labels serviceName: "nginx-headless" replicas: 3 #兩個副本 template: metadata: labels: app: nginx spec: containers: - name: nginx image: ikubernetes/myapp:v1 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www annotations: volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" #managed-nfs-storage為我們創建的storage-class名稱,動態storeclass參考https://www.cnblogs.com/wuxinchun/p/15266445.html spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi [root@k8s-master statefulset]# kubectl apply -f nginx-statefulset.yaml service/nginx-headless created statefulset.apps/web created
5.2驗證解析
1)每個 Pod 都擁有一個基於其順序索引的穩定的主機名
[root@k8s-master statefulset]# kubectl get pods NAME READY STATUS RESTARTS AGE dns-test 1/1 Running 0 87m nfs-client-provisioner-677fc9c97c-9cj92 1/1 Running 2 7h44m web-0 1/1 Running 0 53s web-1 1/1 Running 0 50s web-2 1/1 Running 0 46s [root@k8s-master statefulset]# kube kubeadm kubectl kubelet [root@k8s-master statefulset]# kubectl exec -it web-0 sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. / # hostname web-0 / # exit [root@k8s-master statefulset]# kubectl exec -it web-1 sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. / # hostname web-1 / # exit [root@k8s-master statefulset]# kubectl exec -it web-2 sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. / # hostname web-2 / # exit
2)使用 kubectl run 運行一個提供 nslookup 命令的容器,該命令來自於 dnsutils 包。通過對 Pod 的主機名執行 nslookup,你可以檢查他們在集群內部的 DNS 地址
[root@k8s-master statefulset]# kubectl run -i --tty --image busybox:1.28.4 dns-test1 --restart=Never --rm #創建dns-test測試pod If you don't see a command prompt, try pressing enter. / # nslookup web-0 Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'web-0' / # nslookup web-1 Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'web-1' / # nslookup web-2 Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'web-2' / # nslookup nginx-headless Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: nginx-headless #無頭service服務名,下面是代理的三個pod地址及其DNS域名 Address 1: 10.244.1.32 web-2.nginx-headless.default.svc.cluster.local Address 2: 10.244.2.29 web-0.nginx-headless.default.svc.cluster.local Address 3: 10.244.1.31 web-1.nginx-headless.default.svc.cluster.local / #
3)刪除pod,自動拉起的pod ip地址會變,但是主機名和域名是不變的
[root@k8s-master statefulset]# kubectl delete pod web-0 web-1 web-2 #刪除pod pod "web-0" deleted pod "web-1" deleted pod "web-2" deleted [root@k8s-master statefulset]# kubectl get pod #pod自動拉起 NAME READY STATUS RESTARTS AGE nfs-client-provisioner-677fc9c97c-9cj92 1/1 Running 2 7h54m web-0 1/1 Running 0 23s web-1 1/1 Running 0 21s web-2 1/1 Running 0 20s [root@k8s-master statefulset]# kubectl run -i --tty --image busybox:1.28.4 dns-test1 --restart=Never --rm If you don't see a command prompt, try pressing enter. / # nslookup nginx-headless Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: nginx-headless #如下查看,pod的IP地址變化了,主機名和dns域名無變化(網絡唯一標識) Address 1: 10.244.2.31 web-0.nginx-headless.default.svc.cluster.local Address 2: 10.244.1.33 web-1.nginx-headless.default.svc.cluster.local Address 3: 10.244.2.32 web-2.nginx-headless.default.svc.cluster.local
5.3寫入穩定的存儲
1)將 Pod 的主機名寫入它們的index.html文件並驗證 NGINX web 服務器使用該主機名提供服務
[root@k8s-master statefulset]# kubectl exec -it web-0 sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. / # echo $(hostname) > /usr/share/nginx/html/index.html / # exit [root@k8s-master statefulset]# kubectl exec -it web-1 sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. / # echo $(hostname) > /usr/share/nginx/html/index.html / # exit [root@k8s-master statefulset]# kubectl exec -it web-2 sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. / # echo $(hostname) > /usr/share/nginx/html/index.html / # exit
2)刪除pod,重新調度后,依然掛載原先PV
[root@k8s-master statefulset]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nfs-client-provisioner-677fc9c97c-9cj92 1/1 Running 2 8h 10.244.1.18 k8s-node1 <none> <none> web-0 1/1 Running 0 6m59s 10.244.2.31 k8s-node2 <none> <none> web-1 1/1 Running 0 6m57s 10.244.1.33 k8s-node1 <none> <none> web-2 1/1 Running 0 6m56s 10.244.2.32 k8s-node2 <none> <none> [root@k8s-master statefulset]# curl 10.244.2.31 web-0 [root@k8s-master statefulset]# curl 10.244.1.33 web-1 [root@k8s-master statefulset]# curl 10.244.2.32 web-2 [root@k8s-master statefulset]# kubectl delete pod web-0 web-1 web-2 pod "web-0" deleted pod "web-1" deleted pod "web-2" deleted [root@k8s-master statefulset]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nfs-client-provisioner-677fc9c97c-9cj92 1/1 Running 2 8h 10.244.1.18 k8s-node1 <none> <none> web-0 1/1 Running 0 14s 10.244.2.33 k8s-node2 <none> <none> web-1 1/1 Running 0 12s 10.244.1.35 k8s-node1 <none> <none> web-2 1/1 Running 0 11s 10.244.2.34 k8s-node2 <none> <none> [root@k8s-master statefulset]# curl 10.244.2.33 web-0 [root@k8s-master statefulset]# curl 10.244.1.35 web-1 [root@k8s-master statefulset]# curl 10.244.2.34 web-2 [root@k8s-master statefulset]#
雖然 web-0 、web-1 和web-2被重新調度了,但它們仍然繼續監聽各自的主機名,因為和它們的 PersistentVolumeClaim 相關聯的 PersistentVolume 被重新掛載到了各自的 volumeMount 上。不管 web-0、web-1、web-3 被調度到了哪個節點上,它們的 PersistentVolumes 將會被掛載到合適的掛載點上
5.4擴容/縮容 StatefulSet
擴容/縮容StatefulSet 指增加或減少它的副本數。這通過更新replicas字段完成。你可以使用kubectl scale 或者kubectl patch來擴容/縮容一個 StatefulSet。
kubectl scale sts web --replicas=4 -n nginx-ss #擴容 kubectl scale sts web --replicas=2 -n nginx-ss #縮容
或者
kubectl patch sts web -p '{"spec":{"replicas":4}}' -n nginx-ss #擴容 kubectl patch sts web -p '{"spec":{"replicas":2}}' -n nginx-ss #縮容
6.案例(部署mysql)
1)yaml文件
[root@k8s-master statefulset]# pwd /root/k8s_practice/statefulset [root@k8s-master statefulset]# cat mysql-sts.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: selector: matchLabels: app: mysql #必須匹配 .spec.template.metadata.labels serviceName: "mysql" #聲明它屬於哪個Headless Service. replicas: 3 #副本數 template: metadata: labels: app: mysql # 必須配置 .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: mysql image: mysql:5.7 ports: - containerPort: 3306 name: mysql env: - name: MYSQL_ROOT_PASSWORD value: "123456" volumeMounts: - name: mysql-pvc mountPath: /var/lib/mysql volumeClaimTemplates: #可看作pvc的模板 - metadata: name: mysql-pvc spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "managed-nfs-storage" #存儲類名,改為集群中已存在的 resources: requests: storage: 1Gi
2)statefulset創建mysql
[root@k8s-master statefulset]# kubectl apply -f mysql-sts.yaml statefulset.apps/mysql created [root@k8s-master statefulset]# kubectl get pod NAME READY STATUS RESTARTS AGE mysql-0 1/1 Running 0 2m1s mysql-1 1/1 Running 0 116s mysql-2 1/1 Running 0 110s
如果創建mysql報錯:chown: changing ownership of ‘/var/lib/mysql/‘: Operation not permitted
解決方案:在NFS做共享時,添加no_root_squash權限后,重啟rpcbind和nfs服務即可
/nfsdata/volumes/ 10.3.104.0/21(rw,sync,no_root_squash)
3)訪問測試
[root@k8s-master statefulset]# kubectl exec -it mysql-0 sh #進入mysql-0 pod kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. # mysql -uroot -p123456 #yaml文件定義root密碼為123456 mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 2 Server version: 5.7.35 MySQL Community Server (GPL) Copyright (c) 2000, 2021, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.01 sec) mysql>
4)mysql存儲卷
[root@k8s-master volumes]# pwd /nfsdata/volumes [root@k8s-master volumes]# ll total 24 drwxrwxrwx 5 polkitd root 4096 Sep 14 19:35 default-mysql-pvc-mysql-0-pvc-3586c119-3d58-426a-8bbf-a43c6220b27d drwxrwxrwx 5 polkitd root 4096 Sep 14 19:35 default-mysql-pvc-mysql-1-pvc-c2b8838a-ab13-413b-8b80-99fd5d4e5840 drwxrwxrwx 5 polkitd root 4096 Sep 14 19:35 default-mysql-pvc-mysql-2-pvc-614539b7-a3e1-450f-a643-ca980cdb73ff [root@k8s-master volumes]# cd default-mysql-pvc-mysql-0-pvc-3586c119-3d58-426a-8bbf-a43c6220b27d/ [root@k8s-master default-mysql-pvc-mysql-0-pvc-3586c119-3d58-426a-8bbf-a43c6220b27d]# ll total 188476 -rw-r----- 1 polkitd ssh_keys 56 Sep 14 19:35 auto.cnf -rw------- 1 polkitd ssh_keys 1680 Sep 14 19:35 ca-key.pem -rw-r--r-- 1 polkitd ssh_keys 1112 Sep 14 19:35 ca.pem -rw-r--r-- 1 polkitd ssh_keys 1112 Sep 14 19:35 client-cert.pem -rw------- 1 polkitd ssh_keys 1680 Sep 14 19:35 client-key.pem -rw-r----- 1 polkitd ssh_keys 1359 Sep 14 19:35 ib_buffer_pool -rw-r----- 1 polkitd ssh_keys 79691776 Sep 14 19:35 ibdata1 -rw-r----- 1 polkitd ssh_keys 50331648 Sep 14 19:35 ib_logfile0 -rw-r----- 1 polkitd ssh_keys 50331648 Sep 14 19:35 ib_logfile1 -rw-r----- 1 polkitd ssh_keys 12582912 Sep 14 19:35 ibtmp1 drwxr-x--- 2 polkitd ssh_keys 4096 Sep 14 19:35 mysql drwxr-x--- 2 polkitd ssh_keys 4096 Sep 14 19:35 performance_schema -rw------- 1 polkitd ssh_keys 1676 Sep 14 19:35 private_key.pem -rw-r--r-- 1 polkitd ssh_keys 452 Sep 14 19:35 public_key.pem -rw-r--r-- 1 polkitd ssh_keys 1112 Sep 14 19:35 server-cert.pem -rw------- 1 polkitd ssh_keys 1676 Sep 14 19:35 server-key.pem drwxr-x--- 2 polkitd ssh_keys 12288 Sep 14 19:35 sys
注:三個mysql的pod數據是相互獨立的
5)創建service使用Nodeport外部訪問
[root@k8s-master statefulset]# pwd /root/k8s_practice/statefulset [root@k8s-master statefulset]# cat mysql-svc.yaml apiVersion: v1 kind: Service metadata: name: mysql labels: app: mysql spec: type: NodePort ports: - port: 3306 nodePort: 30001 name: mysql selector: app: mysql [root@k8s-master statefulset]# kubectl delete -f mysql-svc.yaml service "mysql" deleted [root@k8s-master statefulset]# vim mysql-svc.yaml [root@k8s-master statefulset]# kubectl apply -f mysql-svc.yaml service/mysql created [root@k8s-master statefulset]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d8h mysql NodePort 10.104.90.83 <none> 3306:30001/TCP 3s nginx-headless ClusterIP None <none> 80/TCP 48m [root@k8s-master statefulset]# kubectl get ep NAME ENDPOINTS AGE kubernetes 10.3.104.51:6443 7d8h mysql 10.244.1.36:3306,10.244.1.37:3306,10.244.2.35:3306 6s nginx-headless 10.244.1.35:80,10.244.2.33:80,10.244.2.34:80 48m wxc-nfs-storage <none> 8h [root@k8s-master statefulset]# netstat -antpl | grep 30001 tcp 0 0 0.0.0.0:30001 0.0.0.0:* LISTEN 32270/kube-proxy

注:上述代表通過Nodeport端口訪問mysql成功
