k8s-儲存

一名小碼農發表於2020-11-17

一、ConfigMap

ConfigMap 功能在 Kubernetes1.2 版本中引入,許多應用程式會從配置檔案、命令列引數或環境變數中讀取配置資訊。ConfigMap API 給我們提供了向容器中注入配置資訊的機制,ConfigMap 可以被用來儲存單個屬性,也可以用來儲存整個配置檔案或者 JSON 二進位制大物件

ConfigMap 的建立

1. 資源清單建立

  1. 建立 ConfigMap 的資源清單:

    apiVersion: v1			# 版本,通過 kubectl explain cm 可以檢視
    kind: ConfigMap
    metadata:
      name: special-config	# ConfigMap 的名字
      namespace: default	# 名稱空間
    data:					# key: value 結構,配置資料
      special.how: very
      special.type: charm
    
  2. 建立

    kubectl apply -f comfigmap.yaml
    

2. 使用目錄建立

  1. 建立 /root/k8s/yaml/configmap/game.properties 檔案:

    enemies=aliens
    lives=3
    enemies.cheat=true
    enemies.cheat.level=noGoodRotten
    secret.code.passphrase=UUDDLRLRBABAS
    secret.code.allowed=true
    secret.code.lives=30
    
  2. 建立 /root/k8s/yaml/configmap/ui.properties 檔案:

    color.good=purple
    color.bad=yellow
    allow.textmode=true
    how.nice.to.look=fairlyNice
    
  3. 建立 configmap ,--from-file 指定在目錄下的所有檔案都會被用在 ConfigMap 裡面建立一個鍵值對,鍵的名字就是檔名,值就是檔案的內容

    kubectl create configmap game-config --from-file=../configmap/
    
  4. 檢視建立的 configmap(可簡寫為 cm):

    $ kubectl get cm
    NAME          DATA   AGE
    game-config   2      6m40s
    
    # 檢視詳細資訊
    kubectl get cm game-config -o yaml
    kubectl describe cm game-config
    

3. 使用檔案建立

通過 --from-file 引數只要指定為一個檔案就可以從單個檔案中建立 ConfigMap

--from-file 這個引數可以使用多次,你可以使用兩次分別指定上個例項中的那兩個配置檔案,效果就跟指定整個目錄是一樣的

kubectl create configmap game-config-2 --fromfile=game.properties

kubectl get configmaps game-config-2 -o yaml

4. 使用字面值建立

使用文字值建立,利用 --from-literal 引數傳遞配置資訊,該引數可以使用多次,格式如下

kubectl create configmap special-config --from-literal=special.how=very --fromliteral=special.type=charm

kubectl get configmaps special-config -o yaml

Pod 中使用 ConfigMap

1. 使用 ConfigMap 來替代環境變數

  1. 建立兩個 ConfigMap(configmap.yaml):

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: special-config
      namespace: default
    data:
      special.how: very
      special.type: charm
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: env-config
      namespace: default
    data:
      log_level: INFO
    
  2. 建立 Pod,並引入

    apiVersion: v1
    kind: Pod
    metadata:
      name: dapi-test-pod
    spec:
      containers:
      - name: test-container
        image: wangyanglinux/myapp:v1
        command: [ "/bin/sh", "-c", "env" ]			# 列印 env
        env:										# 從 ConfigMap 中選擇讀取的鍵,並起個別名
        - name: SPECIAL_LEVEL_KEY					# 鍵別名,在這值應該是 very
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名稱
              key: special.how						# 上句指定 ConfigMap 中的鍵名
        - name: SPECIAL_TYPE_KEY					# 鍵別名,在這值應該是 charm
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名稱
              key: special.type						# 上句指定 ConfigMap 中的鍵名
        envFrom:									# 直接從 ConfigMap 中讀取全部配置
        - configMapRef:
            name: env-config						# ComfigMap 的名稱
      restartPolicy: Never
    
  3. 檢視日誌,可以看到 ConfigMap 中的配置已經注入到了容器中
    在這裡插入圖片描述

2. 使用 ConfigMap 設定命令列引數

  1. 建立 ConfigMap

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: special-config
      namespace: default
    data:
      special.how: very
      special.type: charm
    
  2. 建立 Pod

    apiVersion: v1
    kind: Pod
    metadata:
      name: dapi-test-pod
    spec:
      containers:
      - name: test-container
        image: wangyanglinux/myapp:v1
        command: [ "/bin/sh", "-c", "echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]	#可以調整啟動Pod時的命令
        env:										# 從 ConfigMap 中選擇讀取的鍵,並起個別名
        - name: SPECIAL_LEVEL_KEY					# 鍵別名,在這值應該是 very
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名稱
              key: special.how						# 上句指定 ConfigMap 中的鍵名
        - name: SPECIAL_TYPE_KEY					# 鍵別名,在這值應該是 charm
          valueFrom:
            configMapKeyRef:
              name: special-config					# ComfigMap 的名稱
              key: special.type
      restartPolicy: Never
    
  3. 檢視日誌

    $ kubectl logs dapi-test-pod
    very charm
    

3. 通過資料卷外掛使用ConfigMap

通過 Volume 方式掛載,ConfigMap 中的鍵名就是 檔名,鍵值就是 檔案內容

  1. 建立 ConfigMap

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: special-config
      namespace: default
    data:
      special.how: very
      special.type: charm
    
  2. 建立 Pod

    apiVersion: v1
    kind: Pod
    metadata:
      name: dapi-test-pod
    spec:
      containers:
        - name: test-container
          image: wangyanglinux/myapp:v1
          command: ["/bin/sh", "-c", "cat /etc/config/special.how"]    # 列印掛載目錄下的檔案內容
          volumeMounts:                 # volume 掛載
            - name: config-volume       # 掛載下面指定的 volume
              mountPath: /etc/config    # 掛載到的目錄(容器內路徑,該目錄下,檔名就裡鍵名,檔案內容就是鍵值)
      volumes:
        - name: config-volume           # volume 名稱
          configMap:                    # 來自 ConfigMap
            name: special-config        # ConfigMap 名字
      restartPolicy: Never
    
  3. 檢視日誌

    $ kubectl logs dapi-test-pod
    very
    

4. ConfigMap 的熱更新

  1. 建立一個 ConfigMapDeployment

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: log-config
      namespace: default
    data:
      log_level: INFO
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: my-nginx
    spec:
      replicas: 1
      template:
        metadata:
          labels:
            run: my-nginx
        spec:
          containers:
            - name: my-nginx
              image: wangyanglinux/myapp:v1
              ports:
                - containerPort: 80
              volumeMounts:					# 這塊兒不懂看上一節《通過資料卷外掛使用ConfigMap》
                - name: config-volume
                  mountPath: /etc/config	# 容器內這個目錄下會有 log_level 這個檔案,內容為 INFO
          volumes:
            - name: config-volume
              configMap:
                name: log-config
    
  2. 檢視 /etc/config/log_level 檔案的內容

    $ kubectl exec my-nginx-c484b98b4-sbls9 -it -- cat /etc/config/log_level
    INFO
    
  3. 修改 ConfigMap

    kubectl edit configmap log-config
    

    在這裡插入圖片描述

  4. 稍微等一會兒,再次檢視 /etc/config/log_level 檔案的內容,可以看到,Pod 中的配置也改了

    $ kubectl exec my-nginx-c484b98b4-sbls9 -it -- cat /etc/config/log_level
    DEBUG
    
  • 注意:更新 ConfigMap 後:
    • 使用該 ConfigMap 掛載的 Env 不會同步更新
    • 使用該 ConfigMap 掛載的 Volume 中的資料需要一段時間(實測大概10秒)才能同步更新
  1. Pod 滾動更新

    ConfigMap 更新後,並不會讓相應的檔案過載。例如,Nginx 在啟動時,會載入一次配置檔案(配置檔案中有 ConfigMap 的相關引數),載入完成後,無論這個配置檔案再怎麼變化,Nginx 都不會再載入它。因此需要 ConfigMap 更新後滾動更新 Pod

    • 可以通過修改 pod annotations 的方式強制觸發滾動更新
    • 這裡我們在 .spec.template.metadata.annotations 中新增 version/config ,每次通過修改 version/config 的時間來觸發滾動更新
    kubectl patch deployment my-nginx --patch \
    '{"spec": {"template": {"metadata": {"annotations":{"version/config": "20201110" }}}}}'
    

二、Secret

Secret 解決了密碼、token、金鑰等敏感資料的配置問題,而不需要把這些敏感資料暴露到映象或者 Pod Spec 中。Secret 可以以 Volume 或者環境變數的方式使用

Secret 有三種型別:

  • Service Account:用來訪問 Kubernetes API,由 Kubernetes 自動建立,並且會自動掛載到 Pod/run/secrets/kubernetes.io/serviceaccount 目錄中
  • Opaquebase64 編碼格式的 Secret,用來儲存密碼、金鑰等。加密程度不高
  • kubernetes.io/dockerconfigjson:用來儲存私有 docker registry 的認證資訊

1. Service Account(不常用)

Service Account 用來訪問 Kubernetes API,由 Kubernetes 自動建立,並且會自動掛載到 Pod/run/secrets/kubernetes.io/serviceaccount 目錄中

# 1. 隨便找一個需要訪問 Kubernetes API 的 Pod
$ kubectl get pod -n kube-system
NAME                                   READY   STATUS    RESTARTS   AGE
kube-proxy-2pqkk                       1/1     Running   6          40d

# 2. 檢視該 Pod 中 /run/secrets/kubernetes.io/serviceaccount 目錄下的檔案
$ kubectl exec kube-proxy-2pqkk -n kube-system -it -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt:訪問 API Service 時的證書
namespace:名稱空間
token:認證的金鑰資訊

2. Opaque Secret

Opaque 型別的資料是一個 map 型別,要求 valuebase64 編碼格式:

(1)建立 Opaque Secret

  1. 給使用者名稱和密碼用 base64 加密

    $ echo -n admin | base64
    YWRtaW4=
    $ echo -n 123 | base64
    MTIz
    

    base64 解碼:

    $ echo -n YWRtaW4= | base64 -d
     admin 
    
  2. 使用加密後的使用者名稱和密碼建立 Secret

    apiVersion: v1			# kubectl explain secret 檢視
    kind: Secret
    metadata:
      name: mysecret		# Secret 名稱
    type: Opaque			# Secret 的型別
    data:
      password: MTIz		# 密碼
      username: YWRtaW4=	# 使用者名稱
    
  3. 檢視 Secret

    $ kubectl get secret
    NAME                  TYPE                                  DATA   AGE
    default-token-fm46c   kubernetes.io/service-account-token   3      40d
    mysecret              Opaque                                2      12s
    
    • default-token-xxxxx:k8s 預設會在每個名稱空間下都建立一個,用於 Pod 的掛載

(2)將 Secret 掛載到 Volume 中

  1. 建立 Pod

    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        name: secret-test
      name: secret-test
    spec:
      volumes:			# 建立一個卷
        - name: secrets		# 卷名
          secret:			# 卷使用的方案
            secretName: mysecret	# 來自於上一節建立的 mysecret
      containers:
        - image: wangyanglinux/myapp:v1
          name: db
          volumeMounts:		# 卷掛載
            - name: secrets		# 掛載的是上面宣告的 secrets
              mountPath: "/etc/secrets"		# 掛載的目錄(容器內目錄)
              readOnly: true	# 只讀 
    
  2. 檢視

    # Opaque Secret 中的使用者名稱和密碼都已經掛載進來了
    $ kubectl exec secret-test -it -- ls /etc/secrets
    password  username
    
    # 檢視內容,發現內容已經自動被解密
    $ kubectl exec secret-test -it -- cat /etc/secrets/password
    123
    $ kubectl exec secret-test -it -- cat /etc/secrets/username
    admin
    

(3)將 Secret 匯出到環境變數中

  1. 建立 Deployment:

    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: pod-deployment
    spec:
      replicas: 2
      template:
        metadata:
          labels:
            app: pod-deployment
        spec:
          containers:
            - name: pod-1
              image: wangyanglinux/myapp:v1
              ports:
                - containerPort: 80
              env:
                - name: TEST_USER			# 環境變數名
                  valueFrom:
                    secretKeyRef:			# 從 Secret 中獲取
                      name: mysecret		# Secret 的名字
                      key: username		# Secret 中的鍵名
                - name: TEST_PASSWORD		# 環境變數名
                  valueFrom:
                    secretKeyRef:			# 從 Secret 中獲取
                      name: mysecret		# Secret 的名字
                      key: password		# Secret 中的鍵名(相比 configmap,Secret 在這兒不需要使用明文,稍微安全一點)
    
  2. 檢視環境變數

    # 進入容器
    $ kubectl exec pod-deployment-747f78bc67-2w9wk -it -- /bin/sh
    
    # 檢視環境變數
    $ echo $TEST_USER
    admin
    $ echo $TEST_PASSWORD
    123
    

3. kubernetes.io/dockerconfigjson

使用 Kuberctl 建立 docker registry 認證的 secret

# kubectl create secret docker-registry \	# 建立 Secret 的型別
#    myregistrykey \  						# Secret 的名稱
#    --docker-server=hub.zyx.com \			# docker server 的地址
#    --docker-username=admin \				# docker 使用者名稱
#    --docker-password=Harbor12345 \		# docker 密碼
#    --docker-email=aa@qq.com 				# docker 郵箱
kubectl create secret docker-registry \
    myregistrykey \
    --docker-server=hub.zyx.com \
    --docker-username=admin \
    --docker-password=Harbor12345 \
    --docker-email=aa@qq.com

在建立 Pod 的時候,通過 imagePullSecrets 來引用剛建立的 myregistrykey,來拉取私有倉庫的映象

apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
    - name: foo
      image: hub.zyx.com/zyx/myapp:v1
  imagePullSecrets:			# 當去私有倉庫拉取時的認證資訊
    - name: myregistrykey	# 認證資訊,上一步建立的 docker registry

三、Volume

容器磁碟上的檔案的生命週期是短暫的,這就使得在容器中執行重要應用時會出現一些問題。

  1. 首先,當容器崩潰時,kubelet 會重啟它,但是容器中的檔案將丟失——容器以乾淨的狀態(映象最初的狀態)重新啟動。

  2. 其次,在 Pod 中同時執行多個容器時,這些容器之間通常需要共享檔案。

Kubernetes 中的 Volume 抽象就很好的解決了這些問題

Kubernetes 中的卷有明確的壽命 —— 與封裝它的 Pod 相同。所以,卷的生命比 Pod 中的所有容器都長,當這個容器重啟時資料仍然得以儲存。當然,當 Pod 不再存在時,卷也將不復存在。也許更重要的是,Kubernetes 支援多種型別的卷,Pod 可以同時使用任意數量的卷

Kubernetes 支援以下型別的卷:

  • awsElasticBlockStore azureDisk azureFile cephfs csi downwardAPI emptyDir
  • fc flocker gcePersistentDisk gitRepo glusterfs hostPath iscsi local nfs
  • persistentVolumeClaim projected portworxVolume quobyte rbd scaleIO secret
  • storageos vsphereVolume

1. emptyDir

Pod 被分配給節點時,首先建立 emptyDir 卷,並且只要該 Pod 在該節點上執行,該卷就會存在。正如卷的名字所述,它最初是空的。該卷可以掛載到 Pod 每個容器中的相同或不同路徑上,並且每個容器都可以讀取和寫入 emptyDir 卷中的檔案。當出於任何原因從節點中刪除 Pod 時, emptyDir 中的資料將被永久刪除

注意:容器崩潰不會從節點中移除 pod,因此 emptyDir 卷中的資料在容器時是安全的

emptyDir 的用法有:

  • 暫存空間,例如用於基於磁碟的合併排序
  • 用作長時間計算崩潰恢復時的檢查點
  • Web 伺服器容器提供資料時,儲存內容管理器容器提取的檔案
  1. 建立一個 Pod,裡面有兩個容器,都掛載同一個 emptyDir

    apiVersion: v1
    kind: Pod
    metadata:
      name: test-pd
    spec:
      containers:
        - image: wangyanglinux/myapp:v1
          name: test-container		# 容器名
          volumeMounts:				
            - mountPath: /cache		# 掛載到容器的哪個目錄下
              name: cache-volume	# 通過哪個 volume 掛載
        - name: test2-container
          image: busybox 
          args: 					# 執行命令,睡眠,防止容器退出
            - /bin/sh 
            - -c 
            - sleep 6000s
          volumeMounts:				
            - mountPath: /test		# 掛載到容器的哪個目錄下
              name: cache-volume	# 通過哪個 volume 掛載
      volumes:
        - name: cache-volume		# volume 名稱
          emptyDir: {}				# volume 型別
    
  2. 可以看到,兩個容器都能讀取到 emptyDir 中的資料

    # 在容器2的 /test 目錄下,建立一個 index.html 檔案
    $ kubectl exec test-pd -c test2-container -it -- touch /test/index.html
    
    # 檢視容器1的 /cache 目錄
    $ kubectl exec test-pd -c test-container -it -- ls /cache
    index.html
    

2. hostPath

hostPath 卷將主機節點的檔案系統中的檔案或目錄掛載到叢集中

hostPath 的用途如下:

  • 執行需要訪問 Docker 內部的容器;使用 /var/lib/dockerhostPath
  • 在容器中執行 cAdvisor;使用 /dev/cgroupshostPath

注意事項

  1. 由於每個節點上的檔案都不同,具有相同配置(例如從 podTemplate 建立的)的 pod 在不同節點上的行為可能會有所不同。
  2. Kubernetes 按照計劃新增資源感知排程時,將無法考慮 hostPath 使用的資源
  3. 在底層主機上建立的檔案或目錄只能由 root 寫入。您需要在特權容器中以 root 身份執行程式,或修改主機上的檔案許可權以便寫入 hostPath

(1)type 的值

行為
空字串(預設)用於向後相容,這意味著在掛載 hostPath 卷之前不會執行任何檢查。
DirectoryOrCreate如果在給定的路徑上沒有任何東西存在,那麼將根據需要在那裡建立一個空目錄,許可權設定為 0755,與 Kubelet 具有相同的組和所有權。
Directory給定的路徑下必須存在目錄
FileOrCreate如果在給定的路徑上沒有任何東西存在,那麼會根據需要建立一個空檔案,許可權設定為 0644,與 Kubelet 具有相同的組和所有權。
File給定的路徑下必須存在檔案
Socket給定的路徑下必須存在 UNIX 套接字
CharDevice給定的路徑下必須存在字元裝置
BlockDevice給定的路徑下必須存在塊裝置

(2)示例

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
    - image: wangyanglinux/myapp:v1
      name: test-container
      volumeMounts:
        - mountPath: /test-pd			# 掛載到的容器內路徑
          name: test-volume				# 選擇下面宣告的 volume 進行掛載
  volumes:
    - name: test-volume					# volume名稱
      hostPath:							# volume 型別
        path: /data						# 本機的 /data 目錄(Pod 執行在叢集的哪個節點,就是哪個節點上的 /data 目錄)
        type: Directory					# 型別(如果不存在 /data 會報錯)

注意:要確保每個 k8s 節點上都存在 /data 這個目錄

四、PV/PVC

1. 概念

  • PersistentVolume (PV):

    是由管理員設定的儲存,它是群集的一部分。就像節點是叢集中的資源一樣,PV 也是叢集中的資源。 PVVolume 之類的卷外掛,但具有獨立於使用 PVPod 的生命週期。此 API 物件包含儲存實現的細節,即 NFSiSCSI 或特定於雲供應商的儲存系統

  • PersistentVolumeClaim (PVC):

    是使用者儲存的請求。它與 Pod 相似。Pod 消耗節點資源,PVC 消耗 PV 資源。Pod 可以請求特定級別的資源(CPU 和記憶體)。PVC 可以宣告可以請求特定的大小和訪問模式(例如,可以以讀/寫一次或 只讀多次模式掛載)

生命週期

生命週期

PV的分類
  • 靜態 pv:

    叢集管理員建立一些 PV。它們帶有可供群集使用者使用的實際儲存的細節,一般儲存訪問至後端儲存的細節(怎麼連線,地址多少…)。它們存在於 Kubernetes API 中,可用於消費

靜態pv

  • 動態 pv:

    當管理員建立的靜態 PV 都不匹配使用者的 PersistentVolumeClaim 時,叢集可能會嘗試動態地為 PVC 建立卷。此配置基於 StorageClasses :PVC 必須請求 [儲存類],並且管理員必須建立並配置該類才能進行動態建立。宣告該類為 "" 可以有效地禁用其動態配置

    要啟用基於儲存級別的動態儲存配置,叢集管理員需要啟用 API server 上的 DefaultStorageClass [准入控制器]。例如,通過確保 DefaultStorageClass 位於 API server 元件的 --admission-control 標誌,使用逗號分隔的有序值列表中,可以完成此操作

動態pv

繫結:

master 中的控制環路監視新的 PVC,尋找匹配的 PV(如果可能),並將它們繫結在一起。如果為新的 PVC 動態調配 PV,則該環路將始終將該 PV 繫結到 PVC。否則,使用者總會得到他們所請求的儲存,但是容量可能超出要求的數量。

一旦 PV 和 PVC 繫結後, PersistentVolumeClaim 繫結是排他性的,不管它們是如何繫結的。 PVCPV 繫結是一對一的對映

持久化卷宣告的保護

PVC 保護的目的是確保由 Pod 正在使用的 PVC 不會從系統中移除,因為如果被移除的話可能會導致資料丟失。

注意:當 Pod 狀態為 Pending 並且 Pod 已經分配給節點或者 Pod 為 Running 狀態時,PVC 處於活動狀態。

當啟用PVC 保護 alpha 功能時,如果使用者刪除了一個 pod 正在使用的 PVC,則該 PVC 不會被立即刪除。PVC 的刪除將被推遲,直到 PVC 不再被任何 pod 使用

2. PV 一些概念

(1)PV 的型別(外掛)

PersistentVolume 型別以外掛形式實現。Kubernetes 目前支援以下外掛型別:

  • GCEPersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC (Fibre Channel)
  • FlexVolume、Flocker、NFS、iSCS、RBD(Ceph Block Device)、CephFS
  • Cinder(OpenStack block storage)、Glusterfs、VsphereVolume、Quobyte、Volumes
  • HostPath、VMware、Photon、Portworx、Volumes、ScaleIO、Volumes、StorageOS

(2)訪問模式

PersistentVolume 可以以資源提供者支援的任何方式掛載到主機上。如下表所示,供應商具有不同的功能,每個 PV 的訪問模式都將被設定為該卷支援的特定模式。例如,NFS 可以支援多個讀/寫客戶端,但特定的 NFS PV 可能以只讀方式匯出到伺服器上。每個 PV 都有一套自己的用來描述特定功能的訪問模式

  • ReadWriteOnce——該卷可以被單個節點以讀/寫模式掛載
  • ReadOnlyMany——該卷可以被多個節點以只讀模式掛載
  • ReadWriteMany——該卷可以被多個節點以讀/寫模式掛載
    在命令列中,訪問模式縮寫為:
  • RWO:ReadWriteOnce
  • ROX:ReadOnlyMany
  • RWX:ReadWriteMany

注意:一個卷一次只能使用一種訪問模式掛載,即使它支援很多訪問模式。例如,GCEPersistentDisk 可以由單個節點作為 ReadWriteOnce 模式掛載,或由多個節點以 ReadWriteMany 模式掛載,但不能同時掛載

Volume 外掛ReadWriteOnceReadOnlyManyReadWriteMany
AWSElasticBlockStoreAWSElasticBlockStore--
AzureFile
AzureDisk--
CephFS
Cinder--
FC-
FlexVolume-
Flocker--
GCEPersistentDisk-
Glusterfs
HostPath--
iSCSI-
PhotonPersistentDisk--
Quobyte
NFS
RBD-
VsphereVolume--
PortworxVolume-
ScaleIO-
StorageOS--

(3)回收策略

  • Retain(保留):手動回收
  • Recycle(回收):基本擦除( 相當於執行了 rm -rf /thevolume/*
  • Delete(刪除):關聯的儲存資產(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)將被刪除

當前,只有 NFSHostPath 支援回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支援刪除策略

(4)狀態

卷可以處於以下的某種狀態:

  • Available(可用):一塊空閒資源還沒有被任何宣告繫結
  • Bound(已繫結):卷已經被宣告繫結
  • Released(已釋放):宣告被刪除,但是資源還未被叢集重新宣告
  • Failed(失敗):該卷的自動回收失敗

命令列會顯示繫結到 PV 的 PVC 的名稱

(5)模板

apiVersion: v1
kind: PersistentVolume		# 型別:PV
metadata:
  name: pv0003				# 名稱
spec:
  capacity:
    storage: 5Gi			# 卷的大小:5G
  volumeMode: Filesystem	# 檔案型別
  accessModes:				# 訪問策略
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle	# 回收策略
  storageClassName: slow	# 儲存類的一個名稱
  mountOptions:				# 其它說明,也可以不指定,讓他自己做判斷
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp				# 掛載到哪個目錄下
    server: 172.17.0.2		# 掛載到哪個伺服器

3. NFS 持久化示例

(1)安裝 NFS

  1. 新建一臺虛擬機器,安裝 NFS,我的虛擬機器 IP 為 192.168.66.20

    yum install -y nfs-common nfs-utils rpcbind
    mkdir /nfs
    chmod 777 /nfs
    chown nfsnobody /nfs
    
    vim /etc/exports	# 檔案中寫入以下內容
    	/nfs *(rw,no_root_squash,no_all_squash,sync)
    
    systemctl start rpcbind
    systemctl start nfs
    
  2. 在 k8s 每個節點中安裝 NFS 客戶端:

    # 安裝依賴
    $ yum -y install nfs-utils rpcbind
    
  3. NFS 的一些操作

    # 檢視共享目錄
    $ showmount -e 192.168.66.20
    Export list for 192.168.66.20:
    /nfs *
    
    # 共享目錄與本地目錄掛載
    $ mkdir ~/test
    $ mount -t nfs 192.168.66.20:/nfs ~/test
    
    # 解除掛載
    $ umount ~/test
    

(2)建立 PV 和 StatefulSet

在這裡插入圖片描述

  1. 建立一個 pv.yaml

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: nfspv1
    spec:
      capacity:				# 容量
        storage: 10Gi
      accessModes:			# 訪問模式
        - ReadWriteOnce
      persistentVolumeReclaimPolicy: Retain		# 回收策略
      storageClassName: nfs
      nfs:						# nfs伺服器配置
        path: /nfs				# 目錄
        server: 192.168.66.20	# IP
    
  2. 建立 PV

    $ kubectl apply -f nfspv.yaml
    persistentvolume/nfspv1 created
    
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Available           nfs                  7s
    
  3. 建立 Service 和 StatefulSet

    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"		# 指定 Service 名稱(上面建立的,一定要是個無頭服務)
      replicas: 3		# 副本數
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:		# 容器資訊
            - name: nginx
              image: wangyanglinux/myapp:v2
              ports:
                - containerPort: 80		# 釋放的埠
                  name: web		# 埠名字
              volumeMounts:		# 掛載
                - name: www
                  mountPath: /usr/share/nginx/html	 # 容器內目錄
      volumeClaimTemplates:		# 卷請求宣告模板(pvc模板)
        - metadata:
            name: www
          spec:
            accessModes: [ "ReadWriteOnce" ]	# 指定要請求的卷的訪問模式
            storageClassName: "nfs"		# 指定要請求的卷的類名,只有與 PV 中的storageClassName 相同時,才會匹配
            resources:
              requests:
                storage: 1Gi	# 指定要請求的卷大小必須滿足 1G
    
  4. 因為前面只建立了一個 pv,所以 StatefulSet 只有一個 Pod 可以匹配,檢視 Pod:

    # 檢視pod,只有一個因為pv,所以只有一個pod匹配成功後正常執行
    # 因為是 StatefulSet,第二個沒能正常啟動,所以第三個Pod不會建立
    $ kubectl get pod
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          57s
    web-1   0/1     Pending   0          54s
    
    # 檢視 pv,可以看到只有一個繫結成功
    # 第二個繫結不成功是因為訪問模式不匹配
    # 第三個繫結不成功是因為 storageClass 不匹配
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Bound       default/www-web-0   nfs                     3m35s
    nfspv2   5Gi        ROX            Retain           Available                       nfs                     34m
    nfspv3   5Gi        RWO            Retain           Available                       nfs1                    34m
    
    # 檢視 pvc,每個Pod有一個pvc
    $ kubectl get pvc
    NAME        STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    www-web-0   Bound     nfspv1   10Gi       RWO            nfs            6m6s
    www-web-1   Pending                                      nfs            6m3s
    
  5. 在 NFS 伺服器的 /nfs 目錄中建立 index.html,寫入“/nfs訪問成功”,然後通過 nginx 來訪問:

    # 獲取IP
    $ kubectl get pod -o wide
    NAME    READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
    web-0   1/1     Running   0          27m   10.244.1.58   k8s-node01   <none>           <none>
    
    $ curl 10.244.1.58
    /nfs訪問成功
    
  6. 嘗試刪除 Pod,pv 中的資料不會丟失

    $ kubectl delete pod web-0
    pod "web-0" deleted
    
    # 可以看到 IP 已經變了,說明上一個Pod刪除後又建了個新的(Pod 的 name 一致)
    $ kubectl get pod -o wide
    NAME    READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
    web-0   1/1     Running   0          15s   10.244.2.60   k8s-node02   <none>           <none>
    
    # 可以看到仍然可以成功訪問,資料不會丟失
    $ curl 10.244.2.60
    /nfs訪問成功
    
  7. 刪除 StatefulSet 後,pvc 不會自動刪除,pv也不會自動釋放,需要手動刪除

    # 刪除 StatefulSet 後,pvc 仍然存在
    $ kubectl delete statefulset web
    $ kubectl get pvc
    NAME        STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    www-web-0   Bound     nfspv1   10Gi       RWO            nfs            13h
    
    # 刪除 pvc 後,pv 沒有自動釋放
    $ kubectl delete pvc www-web-0
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Released    default/www-web-0   nfs                     13h
    
    # 手動釋放 pv
    $ kubectl edit pv nfspv1
    # 將下面的 spec.claimRef 刪除
    	spec:
    	  claimRef:
    	    apiVersion: v1
    	    kind: PersistentVolumeClaim
    	    name: www-web-0
    	    namespace: default
    	    resourceVersion: "619064"
    	    uid: 99cea07e-339e-431c-bcb6-c398c884b29c
    
    # 再次檢視 pv 已經得到釋放
    $ kubectl get pv
    NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    nfspv1   10Gi       RWO            Retain           Available           nfs                     13h
    

3. PV 的一些說明

  • 匹配 Pod name ( 網路標識 ) 的模式為:$(StatefulSet名稱)-$(序號),比如上面的示例:web-0,web-1,web-2

  • StatefulSet 為每個 Pod 副本建立了一個 DNS 域名(意味著其它 Pod 可以通過這個域名來訪問),這個域名的格式為: $(podname).(headless server name),也就意味著服務間是通過 Pod 域名來通訊而非 Pod IP,因為當 Pod 所在 Node 發生故障時, Pod 會被飄移到其它 Node 上,Pod IP 會發生變化,但是 Pod 域名不會有變化

    # 隨便進入一個以前的 Pod,沒有就新建一個,然後ping域名,可以成功 ping 通
    $ ping web-0.nginx
    PING web-0.nginx (10.244.2.60): 56 data bytes
    64 bytes from 10.244.2.60: seq=0 ttl=62 time=0.388 ms
    64 bytes from 10.244.2.60: seq=1 ttl=62 time=0.263 ms
    
  • StatefulSet 使用 Headless 服務來控制 Pod 的域名(意味著 Pod 外部可以通過這個域名來訪問),這個域名的 FQDN(完全現定域名) 為:$(service name).(namespace).svc.cluster.local,其中,“cluster.local” 指的是叢集的域名

    # 1. 檢視 DNS 伺服器 coredns 的 ip
    $ kubectl get pod -n kube-system -o wide
    NAME                                   READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
    coredns-5c98db65d4-5ztqn               1/1     Running   10         46d   10.244.0.19     k8s-master01   <none>           <none>
    coredns-5c98db65d4-pc62t               1/1     Running   10         46d   10.244.0.18     k8s-master01   <none>           <none>
    
    # 2. 通過 coredns 來解析域名,可以看到解析後的域名對應 StatefulSet 下的 Pod 的 IP
    # 用 dig 解析域名(沒有 dig 要安裝:yum -y install bind-utils)
    # 命令格式:dig -t A 域名 @DNS伺服器IP
    $ dig -t A nginx.default.svc.cluster.local @10.244.0.19
    ...省略
    nginx.default.svc.cluster.local. 30 IN	A	10.244.2.60
    ...省略
    
  • 根據 volumeClaimTemplates,為每個 Pod 建立一個 pvcpvc 的命名規則匹配模式:
    (volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Pod
    name=web-[0-2],因此建立出來的 PVC 是 www-web-0、www-web-1、www-web-2

  • 刪除 Pod 不會刪除其 pvc,手動刪除 pvc 將自動釋放 pv

相關文章