Kubernetes 有狀態(tài)應用基本概念&Nginx部署
1、無狀態(tài)與有狀態(tài)
Deployment控制器設計原則:管理的所有Pod一模一樣,提供同一個服務,也不考慮在哪臺Node運行,可隨意擴容和縮容。這種應用稱為“無狀態(tài)”,例如Web服務
但是,在實際的場景中,并不能滿足所有應用,尤其是分布式應用,會部署多個實例,這些實例之間往往有依賴關(guān)系,例如主從關(guān)系、主備關(guān)系,這種應用稱為“有狀態(tài)”,例如MySQL主從、Etcd集群、redis-cluster 等等
2、StatefulSet 控制器概述
StatefulSet控制器用于部署有狀態(tài)應用,滿足一些有狀態(tài)應用的需求:
- Pod有序的部署、擴容、刪除和停止
- Pod分配一個穩(wěn)定的且唯一的網(wǎng)絡標識
- Pod分配一個獨享的存儲
3、StatefulSet 控制器:網(wǎng)絡標識
穩(wěn)定的網(wǎng)絡標識:
使用無頭服務 Headless Service(相比普通Service只是將spec.clusterIP定義為None,也就是沒有clusterIP,使用endport 來通信)來維護Pod網(wǎng)絡身份,會為每個Pod分配一個數(shù)字編號并且按照編號順序部署。還需要在StatefulSet添加serviceName: “nginx”字段指定StatefulSet控制器要使用這個Headless Service。
穩(wěn)定主要體現(xiàn)在主機名和Pod A記錄:
- 主機名:<statefulset名稱>-<編號>
- Pod DNS A記錄:<statefulset名稱-編號>.<service-name> .<namespace>.svc.cluster.local (POD 之間通過DNS A 記錄通信)
例如: web-0.web.default.svc.cluster.local
- 備注:
- A記錄: 將域名指向一個IPv4地址(例如:100.100.100.100),需要增加A記錄
- CNAME記錄: 如果將域名指向一個域名,實現(xiàn)與被指向域名相同的訪問效果,需要增加CNAME記錄。這個域名一般是主機服務商提供的一個域名
- MX記錄: 建立電子郵箱服務,將指向郵件服務器地址,需要設置MX記錄。建立郵箱時,一般會根據(jù)郵箱服務商提供的MX記錄填寫此記錄
- NS記錄: 域名解析服務器記錄,如果要將子域名指定某個域名服務器來解析,需要設置NS記錄
- TXT記錄: 可任意填寫,可為空。一般做一些驗證記錄時會使用此項,如:做SPF(反垃圾郵件)記錄
- AAAA記錄: 將主機名(或域名)指向一個IPv6地址(例如:ff03:0:0:0:0:0:0:c1),需要添加AAAA記錄
案例:
通過創(chuàng)建一個 nginx 應用的statefluset 控制器
創(chuàng)建 Headless Service ,定義 clusterIP: None (表示K8S 不會在給這個service 去頒發(fā)一個clusterIP 了;相比 deployment 控制器 的每個POD 都是相同的,而 statefuset 控制器的每個POD 都是有狀態(tài)的
需要單獨去訪問 )
- [root@master-1 statefulset]# vim service.yaml
- apiVersion: v1
- kind: Service
- metadata:
- name: web
- spec:
- clusterIP: None
- ports:
- - protocol: TCP
- port: 80
- selector:
- app: nginx
- [root@master-1 statefulset]# kubectl apply -f service.yaml
- [root@master-1 statefulset]# kubectl get service
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- web ClusterIP None <none> 80/TCP 8m18s
#創(chuàng)建statefulset,指定serviceName
- [root@master-1 statefulset]# cat statefulset.yaml
- apiVersion: apps/v1
- kind: StatefulSet
- metadata:
- name: web
- spec:
- serviceName: "web"
- replicas: 3
- selector:
- matchLabels:
- app: nginx
- template:
- metadata:
- labels:
- app: nginx
- spec:
- containers:
- - name: nginx
- image: nginx
- ports:
- - containerPort: 80
- name: web
- [root@master-1 statefulset]# kubectl apply -f statefulset.yaml
- statefulset.apps/web created
- #發(fā)現(xiàn)pod名稱 帶有序號
- [root@master-1 statefulset]# kubectl get pods
- NAME READY STATUS RESTARTS AGE
- web-0 1/1 Running 0 16s
- web-1 1/1 Running 0 13s
- web-2 1/1 Running 0 6s
- #查了POD的主機名
- 主機名 默認與 POD 名一致,即使POD 飄逸到其他node 上 或者刪除后重建 主機名 都是和POD 名一致。有個穩(wěn)定的主機名
- [root@master-1 statefulset]# kubectl exec -it web-0 -- hostname
- web-0
- [root@master-1 statefulset]# kubectl exec -it web-1 -- hostname
- web-1
- [root@master-1 statefulset]# kubectl exec -it web-2 -- hostname
- web-2
- #臨時啟動一個busybox pod ,測試dns 解析(注意這里的busybox版本為1.28.4 最新版的busybox nslookup 會有問題)
- [root@master-1 statefulset]# kubectl run -it dns-test --rm --image=busybox:1.28.4 -- sh
- / # nslookup web
- Server: 10.0.0.2
- Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
- Name: web
- Address 1: 10.244.2.114 web-2.web.default.svc.cluster.local
- Address 2: 10.244.2.113 web-0.web.default.svc.cluster.local
- Address 3: 10.244.1.65 web-1.web.default.svc.cluster.local
- 可以看到解析出3條記錄出來,解析出對應的三個Pod IP記錄,其他Pod可使用這個名稱訪問
- 模擬測試刪除這些 pod ,升級鏡像版本,發(fā)現(xiàn)pod ip雖然發(fā)生變化,但是 主機名,Pod DNS A記錄 不會發(fā)生變化
- / # nslookup web
- Server: 10.0.0.2
- Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
- Name: web
- Address 1: 10.244.2.116 web-2.web.default.svc.cluster.local
- Address 2: 10.244.2.115 web-0.web.default.svc.cluster.local
- Address 3: 10.244.1.66 web-1.web.default.svc.cluster.local
這個就驗證了 statefulset 的 Pod是 有序的部署、擴容、刪除和停止 且 給每一個POD 分配一個穩(wěn)定的且唯一的網(wǎng)絡標識
4、StatefulSet 控制器:獨享存儲
獨享存儲:StatefulSet的存儲卷使用VolumeClaimTemplate創(chuàng)建,稱為卷申請模板,當StatefulSet使用VolumeClaimTemplate創(chuàng)建一個PersistentVolume時,
同樣也會為每個Pod分配并創(chuàng)建一個編號的PVC,每個PVC綁定對應的PV,從而保證每個Pod都擁有獨立的存儲。
在創(chuàng)建StatefulSet 控制器 獨享存儲前,需要先定義好存儲卷,使用pv 作為持久化存儲卷,后端存儲為NFS
這里采用動態(tài)PV 的方式(NFS server 搭建的過程省略)
一、部署NFS服務器
- #服務器安裝nfs服務,提供nfs存儲功能
- 1、安裝nfs-utils
- yum install nfs-utils (centos)
- 或者 apt-get install nfs-kernel-server (ubuntu)
- 2、啟動服務
- systemctl enable nfs-server
- systemctl start nfs-server
- 3、創(chuàng)建共享目錄完成共享配置
- mkdir /home/nfs #創(chuàng)建共享目錄
- 4、編輯共享配置
- vim /etc/exports
- #語法格式: 共享文件路徑 客戶機地址(權(quán)限) #這里的客戶機地址可以是IP,網(wǎng)段,域名,也可以是任意*
- /home/nfs *(rw,async,no_root_squash)
- 服務自檢命令
- exportfs -arv
- 5、重啟服務
- systemctl restart nfs-server
- 6、本機查看nfs 共享目錄
- #showmount -e 服務器IP地址 (如果提示命令不存在,則需要yum install showmount)
- showmount -e 127.0.0.1
- /home/nfs/nginx *
- 7、客戶端模擬掛載[所有k8s的節(jié)點都需要安裝客戶端]
- [root@master-1 ~]# yum install nfs-utils (centos)
- 或者 apt-get install nfs-common (ubuntu)
- [root@master-1 ~]# mkdir /test
- [root@master-1 ~]# mount -t nfs 172.16.201.209:/home/nfs /test
- #取消掛載
- [root@master-1 ~]# umount /test
二、配置PV 動態(tài)供給(NFS StorageClass),創(chuàng)建pvc
#部署NFS實現(xiàn)自動創(chuàng)建PV插件: 一共設計到4個yaml 文件 ,官方的文檔有詳細的說明
https://github.com/kubernetes-incubator/external-storage


- root@k8s-master1:~ # mkdir /root/pvc
- root@k8s-master1:~ # cd /root/pvc
創(chuàng)建rbac.yaml 文件
- root@k8s-master1:pvc # cat rbac.yaml
- kind: ServiceAccount
- apiVersion: v1
- metadata:
- name: nfs-client-provisioner
- ---
- kind: ClusterRole
- apiVersion: rbac.authorization.k8s.io/v1
- metadata:
- name: nfs-client-provisioner-runner
- rules:
- - apiGroups: [""]
- resources: ["persistentvolumes"]
- verbs: ["get", "list", "watch", "create", "delete"]
- - apiGroups: [""]
- resources: ["persistentvolumeclaims"]
- verbs: ["get", "list", "watch", "update"]
- - apiGroups: ["storage.k8s.io"]
- resources: ["storageclasses"]
- verbs: ["get", "list", "watch"]
- - apiGroups: [""]
- resources: ["events"]
- verbs: ["create", "update", "patch"]
- ---
- kind: ClusterRoleBinding
- apiVersion: rbac.authorization.k8s.io/v1
- metadata:
- name: run-nfs-client-provisioner
- subjects:
- - kind: ServiceAccount
- name: nfs-client-provisioner
- namespace: default
- roleRef:
- kind: ClusterRole
- name: nfs-client-provisioner-runner
- apiGroup: rbac.authorization.k8s.io
- ---
- kind: Role
- apiVersion: rbac.authorization.k8s.io/v1
- metadata:
- name: leader-locking-nfs-client-provisioner
- rules:
- - apiGroups: [""]
- resources: ["endpoints"]
- verbs: ["get", "list", "watch", "create", "update", "patch"]
- ---
- kind: RoleBinding
- apiVersion: rbac.authorization.k8s.io/v1
- metadata:
- name: leader-locking-nfs-client-provisioner
- subjects:
- - kind: ServiceAccount
- name: nfs-client-provisioner
- # replace with namespace where provisioner is deployed
- namespace: default
- roleRef:
- kind: Role
- name: leader-locking-nfs-client-provisioner
- apiGroup: rbac.authorization.k8s.io
創(chuàng)建deployment.yaml 文件
#官方默認的鏡像地址,國內(nèi)可能無法下載,可以使用 image:
fxkjnj/nfs-client-provisioner:latest
#定義NFS 服務器的地址,共享目錄名稱
- root@k8s-master1:pvc # cat deployment.yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
- name: nfs-client-provisioner
- ---
- kind: Deployment
- apiVersion: apps/v1
- metadata:
- name: nfs-client-provisioner
- spec:
- replicas: 1
- strategy:
- type: Recreate
- selector:
- matchLabels:
- app: nfs-client-provisioner
- template:
- metadata:
- labels:
- app: nfs-client-provisioner
- spec:
- serviceAccountName: nfs-client-provisioner
- containers:
- - name: nfs-client-provisioner
- image: fxkjnj/nfs-client-provisioner:latest
- volumeMounts:
- - name: nfs-client-root
- mountPath: /persistentvolumes
- env:
- - name: PROVISIONER_NAME
- value: fuseim.pri/ifs
- - name: NFS_SERVER
- value: 172.16.201.209
- - name: NFS_PATH
- value: /home/nfs
- volumes:
- - name: nfs-client-root
- nfs:
- server: 172.16.201.209
- path: /home/nfs
創(chuàng)建class.yaml
# archiveOnDelete: "true" 表示當PVC 刪除后,后端數(shù)據(jù)不直接刪除,而是歸檔
- root@k8s-master1:pvc # cat class.yaml
- apiVersion: storage.k8s.io/v1
- kind: StorageClass
- metadata:
- name: managed-nfs-storage
- provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
- parameters:
- archiveOnDelete: "true"
查看存儲類
- root@k8s-master1:~/kubernetes/redis# kubectl get sc
- NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
- managed-nfs-storage fuseim.pri/ifs Delete Immediate false 6s
基于上面的內(nèi)容創(chuàng)建 statefulset.yaml 文件
- root@k8s-master1:~ # mkdir /root/statefulset
- root@k8s-master1:~ # cd /root/statefulset
- root@k8s-master1:statefulset # vim statefulset.yaml
- apiVersion: apps/v1
- kind: StatefulSet
- metadata:
- name: web
- spec:
- serviceName: "web"
- replicas: 3
- selector:
- matchLabels:
- app: nginx
- template:
- metadata:
- labels:
- app: nginx
- spec:
- containers:
- - name: nginx
- image: nginx:1.16
- ports:
- - containerPort: 80
- name: web
- volumeMounts:
- - name: nginx-pvc #指定PVC名稱
- mountPath: /usr/share/nginx/html
- volumeClaimTemplates: #相當于pvc模板
- - metadata:
- name: nginx-pvc #創(chuàng)建的PVC名稱
- spec:
- storageClassName: "managed-nfs-storage" #指定動態(tài)PV名稱
- accessModes:
- - ReadWriteOnce #訪問模式,讀寫在單臺機器
- resources:
- requests:
- storage: 1Gi
- root@k8s-master1:statefulset # kubectl apply -f statefulset.yaml
- statefulset.apps/web created
- root@k8s-master1:~/kubernetes/statefulset# kubectl get pv
- NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
- pvc-8eacbe25-3875-4f78-91ca-ba83b6967a8a 100Gi RWX Delete Bound redis/nfs-redis managed-nfs-storage 6d
- pvc-935033b7-9ac8-4346-8543-1f95492dcde9 1Gi RWO Delete Bound default/nginx-pvc-web-1 managed-nfs-storage 39s
- pvc-bd3a8c59-b66d-457b-a6f2-90f3b7f9ebf0 1Gi RWO Delete Bound default/nginx-pvc-web-2 managed-nfs-storage 19s
- pvc-be5cf42a-aeaa-4667-901c-77e1d2350f49 1Gi RWO Delete Bound default/nginx-pvc-web-0 managed-nfs-storage 61s
- root@k8s-master1:~/kubernetes/statefulset# kubectl get pvc
- NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
- nginx-pvc-web-0 Bound pvc-be5cf42a-aeaa-4667-901c-77e1d2350f49 1Gi RWO managed-nfs-storage 82s
- nginx-pvc-web-1 Bound pvc-935033b7-9ac8-4346-8543-1f95492dcde9 1Gi RWO managed-nfs-storage 61s
- nginx-pvc-web-2 Bound pvc-bd3a8c59-b66d-457b-a6f2-90f3b7f9ebf0 1Gi RWO managed-nfs-storage 40s
- oot@k8s-master1:~/kubernetes/statefulset# kubectl get pods --show-labels
- NAME READY STATUS RESTARTS AGE LABELS
- web-0 1/1 Running 0 4m50s app=nginx,controller-revision-hash=web-b56c497b,statefulset.kubernetes.io/pod-name=web-0
- web-1 1/1 Running 0 4m29s app=nginx,controller-revision-hash=web-b56c497b,statefulset.kubernetes.io/pod-name=web-1
- web-2 1/1 Running 0 4m8s app=nginx,controller-revision-hash=web-b56c497b,statefulset.kubernetes.io/pod-name=web-2
- #分別進入到3個pod 中,寫入一個數(shù)據(jù),驗證各自的獨享存儲
- root@k8s-master1:~/kubernetes/statefulset# kubectl get pods -o wide --selector app=nginx
- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
- web-0 1/1 Running 0 7m6s 10.244.169.179 k8s-node2 <none> <none>
- web-1 1/1 Running 0 6m45s 10.244.107.228 k8s-node3 <none> <none>
- web-2 1/1 Running 0 6m24s 10.244.169.180 k8s-node2 <none> <none>
- [root@master-1 ~]# kubectl exec -it web-0 -- bash -c "echo 'congratulations web-0 for k8s' > /usr/share/nginx/html/index.html"
- [root@master-1 ~]# kubectl exec -it web-1 -- bash -c "echo 'congratulations web-1 for k8s' > /usr/share/nginx/html/index.html"
- [root@master-1 ~]# kubectl exec -it web-2 -- bash -c "echo 'congratulations web-2 for k8s' > /usr/share/nginx/html/index.html"
- #直接訪問pod IP 測試內(nèi)容:
- root@k8s-master1:~/kubernetes/statefulset# curl 10.244.169.179
- congratulations web-0 for k8s
- root@k8s-master1:~/kubernetes/statefulset# curl 10.244.107.228
- congratulations web-1 for k8s
- root@k8s-master1:~/kubernetes/statefulset# curl 10.244.169.180
- congratulations web-2 for k8s
刪除statefulset
刪除statefulset 有兩張方法,級聯(lián)刪除 和 非級聯(lián)刪除
- 使用非級聯(lián)刪除 statefulset 時,statefulset 的POD 不會被刪除
- 使用級聯(lián)刪除時,statefulset 和 pod 都會被刪除
- (1)、非級聯(lián)刪除
- 使用kubectl delete statefulset XXXX 刪除 statefulset ,只需要提供 --cascade=false 參數(shù),就會采用非聯(lián)機刪除,此時刪除statefulset 不會刪除pod
- kubectl delete statefulset web --cascade=false
- (2)、級聯(lián)刪除
- 省略 --cascade=false 參數(shù) 即可
- kubectl delete statefulset web