一、PV&PVC
(一)說明
對於有狀態服務,使用Volume掛載,會存在數據丟失的問題,因此K8S使用數據持久卷(PV、PVC)來做容器的編排。
PV(PersistentVolume--持久卷)是一種特殊的Volume,其是一種Volume插件,其存在與集群內,是由管理員提供存儲的一部分。它的生命周期和使用它的Pod相互獨立。其是對抽象資源創建和使用的抽象。
PVC(PersistentVolumeClaim--持久卷聲明)是一種用戶的存儲請求。
PV和PVC是K8S提供的兩種API資源,用於抽象存儲細節。管理員關注如何通過PV提供存儲功能,而不需要關注用戶如何使用,用戶只需要掛載PVC到容器中,而不需要關注存儲卷使用核中技術實現。
(二)生命周期
PV和PVC應遵循供給、綁定、使用、釋放、回收這樣的生命周期。
1、供給
供給有兩種:靜態和動態。
靜態:集群管理員創建多個PV,他們攜帶着真是存儲的詳細信息,這些存儲對於集群用戶是可用的,它們存在於Kubernetes API中,並可用於存儲使用。
動態:當管理員創建的靜態PV都不匹配用戶的PVC是,集群會嘗試專門的供給Volume給PVC,這種供給基於StrorageClass。PVC請求的等級必須是管理員已經創建和配置過的登記,以防止這種動態供給的發生。如果請求是一個登記配置為空的PVC,那么說明禁止了動態供給。
2、綁定
就是PVC和PV的綁定,當用戶發起一個PVC請求時,master中有一個控制回路用於監控新的PVC請求,然后根據PVC請求(要求的存儲大小和訪問模式)來尋找PV,如果尋找到相匹配的PV,則將PVC和PV進行綁定,一旦綁定后,該PV就只屬於這一個PVC。如果沒有找到對應的PV,那么該PVC會一直處於unbond(未綁定)的狀態,直到出現與PVC匹配的PV。舉個栗子,一個提供了很多50G存儲的PV集群,如果一個PVC請求的容量是100G,則不會被匹配成功,直到有100G的PV加入到該集群。
3、使用
Pod使用PVC和使用Volume一樣,集群檢查PVC,查找綁定的PV,將PV映射給Pod。對於支持多種訪問模式的PV,用戶可以指定訪問模式。一旦用戶擁有了PVC且PVC已經綁定了PV,那么這個PV就一致屬於該用戶。用戶調度Pod的Volume塊中包含的PVC來訪問PV。
4、釋放
當用戶使用PV完成后,他們可以通過API來刪除PVC對象。當PVC被刪除后,對應的PV就被認為已經released了,但是此時還不能給其他的PVC使用,因為原來的PVC還存在該PV中,必須使用策略將其刪除,該PV才可以被其他PVC使用
5、回收
PV的回收策略是告訴集群當PV被釋放后應該如何處理該PV,當前的PV可以被設置為Retained(保留)、Recycled(再利用)、Deleted(刪除)。保留允許手動的再次聲明資源,對於支持刪除操作的PV卷,刪除操作會從K8S中移除PV對象及對應的外部存儲。動態供給的卷一定會被刪除。
(三)Pod和PVC
1、提前創建好pv,以及掛載目錄
這里使用nfs作為文件服務器,nfs服務的搭建可以參看 NFS網絡存儲,我這里已經搭建好,ip為192.168.124.16
創建兩個pv的配置文件如下所示(注意一點,掛載目錄/opt/k8s/demo1和/opt/k8s/demo2要提前在nfs上創建好),然后在K8S中創建如下PV
apiVersion: v1 kind: PersistentVolume metadata: name: my-pv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteMany nfs: path: /opt/k8s/demo1 server: 192.168.124.16
# vim pv2.yaml apiVersion: v1 kind: PersistentVolume metadata: name: my-pv2 spec: capacity: storage: 2Gi accessModes: - ReadWriteMany nfs: path: /opt/k8s/demo2 server: 192.168.124.16
配置解釋:
kind表明了這是要創建一個PV
name是PV的名稱
spec是描述信息
capacity.storage是PV的存儲大小
accessModes中的值表示允許多節點訪問
nfs表示持久化存儲使用的是nfs
nfs下面的內容就是對應nfs服務器的ip和目錄
上面的配置就是將實際的持久化存儲抽象成為一個PV。
創建PV:
kubectl apply -f pv1.yaml
kubectl apply -f pv2.yaml

2、然后現在創建一下我們的pod和pvc,這里我寫在一起了
# vim pod.yaml apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: nginx image: nginx ports: - containerPort: 80 volumeMounts: - name: www # 使用名字為www的volume,掛載到下面的目錄中 mountPath: /usr/share/nginx/html volumes: - name: www # 創建一個名字為www的volume persistentVolumeClaim: claimName: my-pvc #使用的是名為my-pvc的PVC --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc # pvc的名字 spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi #對應PV的選擇條件
配置文件解釋:
首先創建了一個pod,名字為my-pod,容器端口為80,掛載名稱為www,容器中的掛載目錄為/usr/share/nginx/html
然后創建了一個名為www的volume,類型是PVC,名字為my-pvc
然后創建了一個PVC,名字為my-pvc,對應的PV的條件為1G
執行創建命令
kubectl apply -f pod.yaml
查看pvc和pod

可以看到pvc對應的是my-pv1(使用大小選擇到my-pv1),my-pv1使用的是nfs服務器的/opt/k8s/demo1目錄,pod中的目錄是/usr/share/nginx/html,可以在nfs的/opt/k8s/demo1目錄中創建一個文件,然后在pod中查看

二、statefulset
有狀態服務器本身就是有實時的數據需要存儲,那么現在數據存儲的問題已經解決了,現在就來一個statefulset的實例部署
(一)headless services
Headless Services是一種特殊的service,其spec:clusterIP表示為None,這樣在實際運行時就不會被分配ClusterIP。
Service的主要作用就是對一組Pod做負載均衡,但是有的場景不需要Service來做負責均衡,例如部署Kafka集群時,客戶端需要的是每一個Kafka的IP,而不需要Service做負載均衡,因此K8S提供了headless Service。字面意思就是無service,其實就是改service對外無提供IP。
1、普通的service創建
# 創建一個service資源對象 # kubectl expose deployment xxName –port=80 –target-port=80 k8s指令模式創建一個service # k8s部署yaml方式 apiVersion: v1 kind: Service metadata: name: nginx namespace: default spec: selector: app: nginx ports: - port: 80 targetPort: 80 --- # 部署deployment對象,k8s創建3個資源對象:Deployment,ReplicaSet,POD apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: default spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.16 ports: - containerPort: 80
2、headless service創建
apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx-demo ports: - port: 80 name: nginx clusterIP: None --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: nginx-dp spec: selector: matchLabels: app: nginx-demo replicas: 2 template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx ports: - containerPort: 80 name: web

實際上,Headless service和普通的Service的yaml文件唯一的區別就是Headless中需要設置clusterIP為None,而普通的需要設置type(NodePort或ClusterIP)
可以看一下service:

可以看到,普通的sarvice有ClusterIP,而Headless Service沒有。
可以再看一下詳細的差異:

這里可以詳細看到,headless service沒有clusterIP,直接使用的是Pod的ip,而普通的service使用的是ClusterIP進行負載均衡。這里也可以進入headless Service的pod中,去ping另外一個pod,都是可以ping通的。
(二)使用statefulSet部署有狀態服務
1、有狀態服務
有狀態服務使用statefulSet + PVC進行創建,其中PVC對應PV,而PV則對應文件服務器。在使用statefulSet創建pod時,pod的退出和進入都是有序的,pod的名字為 statefulSetName + 有序數字(0-N)。
StatefulSet: 是一種給Pod提供唯一標志的控制器,它可以保證部署和擴展的順序。
Pod一致性:包含次序(啟動、停止次序)、網絡一致性。此一致性與Pod相關,與被調度到哪個node節點無關。
穩定的次序:對於N個副本的StatefulSet,每個Pod都在[0,N)的范圍內分配一個數字序號,且是唯一的。
穩定的網絡:Pod的hostname模式為(statefulset名稱)- (序號)。 穩定的存儲:通過VolumeClaimTemplate為每個Pod創建一個PV。刪除、減少副本,不會刪除相關的卷。
2、先創建5個PV
這個沒什么好說的,就是創建pod的准備,和之前的一樣,需要在nfs服務器上提前創建好相應的掛載文檔。
apiVersion: v1 kind: PersistentVolume metadata: name: pv001 labels: name: pv001 spec: nfs: path: /opt/k8s/v1 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv002 labels: name: pv002 spec: nfs: path: /opt/k8s/v2 server: 172.20.10.6 accessModes: ["ReadWriteOnce"] capacity: storage: 2Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv003 labels: name: pv003 spec: nfs: path: /opt/k8s/v3 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv004 labels: name: pv004 spec: nfs: path: /opt/k8s/v4 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv005 labels: name: pv005 spec: nfs: path: /opt/k8s/v5 server: 172.20.10.6 accessModes: ["ReadWriteMany", "ReadWriteOnce"] capacity: storage: 1Gi
3、使用statefulSet創建服務----手動指定PVC進行創建pod
# 部署stateful類型的有狀態服務, 指定pvc apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: "nginx" replicas: 3 template: metadata: labels: app: nginx spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: nginx ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumes: - name: www persistentVolumeClaim: claimName: my-pvc --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi
配置說明:
等待 terminationGracePeriodSeconds 這么長的時間。(默認為30秒)、超過terminationGracePeriodSeconds等待時間后, K8S 會強制結束老POD
4、使用statefulSet創建服務----使用volumeClaimTemplates直接指定pvc
在上面創建statefulset的配置文件中,需要手動的創建pvc的配置,但是如果當pvc越來越多,則會管理越來越困難,這時就可以用到使用volumeClaimTemplates直接指定pvc,其可以幫助我們自動創建pvc,不需要我們手動定義pvc
# 使用volumeClaimTemplates直接指定pvc,申請pv apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: nginx replicas: 3 template: metadata: labels: app: nginx spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: nginx ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "my-storage-class" resources: requests: storage: 1Gi
配置說明:
PVC和PV是通過StorageClassName進行綁定,如果定義PVC時沒有指定StorageClassName,就要看admission插件是否開啟了DefaultStorageClass功能:
如果開啟了DefaultStorageClass功能:那么此PVC的StorageClassName就會被指定為DefaultStorageClass。(在定義StorageClass時,可以在Annotation中添加一個鍵值對storageclass.kubernetes.io/is-default-class: true,那么此StorageClass就變成默認的StorageClass了)
如果沒有開啟DefaultStorageClass功能:沒有指定StorageClassName的PVC只能被綁定到同樣沒有指定StorageClassName的PV上。
可以對比一下配置文件

可以看到,兩者的配置文件,差一點在一個使用手動創建PVC,然后使用volumes進行掛載,而另外一個使用volumeClaimTemplates自動創建PVC,並進行綁定。
三、StorageClass
(一)StorageClass簡述
簡單地說,StorageClass就是用來自動創建PV的。
在一個大規模的K8S集群中,可能有成千上萬的PVC,隨着時間的推移,可能還會有很多的PVC被不斷提交,那么就需要運維人員人工的進行創建PV,如果創建的不及時,就會導致PVC綁定不到PV而導致Pod創建失敗,同時,通過PVC請求到的存儲空間也很有可能不滿足需求。
K8S提供了一套可以自動創建PV的機制,即Dynamic Provisioning,而這個機制的核心在於StorageClass這個API對象
StorageClass對象會定義以下兩部分內容:
1、PV的屬性,例如存儲類型,Volume的大小等
2、創建這種PV需要用到的插件
有了這兩部分信息后,K8S就能根據用戶提交的PVC,找到一個對應的StorageClass,之后K8S會調用該StorageClass聲明的存儲插件,進而創建出需要的PV。而實際使用的話,只是需要根據自己的需求,編寫yaml文件,然后使用kubectl create命令執行即可。(應用程序對存儲的性能要求也可能不同,例如讀寫速度、並發性能等,在StorageClass中可以定義,管理員可以將存儲資源定義為某種類型的資源,比如快速存儲,慢速存儲等,然后用戶根據自己的需求進行申請)
(二)運行原理及部署流程
要使用 StorageClass,我們就得安裝對應的自動配置程序,比如我們這里存儲后端使用的是 nfs,那么我們就需要使用到一個 nfs-client 的自動配置程序,我們也叫它 Provisioner,這個程序使用我們已經配置好的 nfs 服務器,來自動創建持久卷,也就是自動幫我們創建 PV。
自動創建的 PV 以${namespace}-${pvcName}-${pvName}這樣的命名格式創建在 NFS 服務器上的共享數據目錄中。而當這個 PV 被回收后會以archieved-${namespace}-${pvcName}-${pvName}這樣的命名格式存在 NFS 服務器上。
搭建StorageClass+NFS,大致有以下幾個步驟:
1、創建一個可用的NFS Server
2、創建Service Account,這是用來管控NFS provisioner在k8s集群中運行的權限
3、創建StorageClass,負責建立PVC並調用NFS provisioner進行預定的工作,並讓PV與PVC建立管理
4、創建NFS provisioner,有兩個功能,一個是在NFS共享目錄下創建掛載點(volume),另一個則是建了PV並將PV與NFS的掛載點建立關聯
(三)StrogeClass&statefulset實踐
使用StrogeClass和StatefulSet創建服務
1、首先創建權限
# rbac.yaml:#唯一需要修改的地方只有namespace,根據實際情況定義 apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner namespace: default --- 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 namespace: default 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 namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
2、創建StrogeClass
# 創建NFS資源的StorageClass apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage provisioner: qgg-nfs-storage parameters: archiveOnDelete: "false" --- # 創建NFS provisioner apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner namespace: default spec: replicas: 1 selector: matchLabels: app: nfs-client-provisioner 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: nfs-client-provisioner volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: qgg-nfs-storage - name: NFS_SERVER value: 172.20.10.6 - name: NFS_PATH value: /opt/k8s volumes: - name: nfs-client-root nfs: server: 172.20.10.6 path: /opt/k8s
3、創建pod進行測試
# 創建pod進行測試 kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim annotations: volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi # 創建測試pod,查看是否可以正常掛載 kind: Pod apiVersion: v1 metadata: name: test-pod spec: containers: - name: test-pod image: busybox:1.24 command: - "/bin/sh" args: - "-c" - "touch /mnt/SUCCESS && exit 0 || exit 1" #創建一個SUCCESS文件后退出 volumeMounts: - name: nfs-pvc mountPath: "/mnt" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim #與PVC名稱保持一致 # StateFulDet+volumeClaimTemplates自動創建PV --- apiVersion: v1 kind: Service metadata: name: nginx-headless labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 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" spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi
