K8S--有狀態服務存儲及部署(PV&PVC&StatefulSet&StrogeClass)


一、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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM