Kubernetes 跨 StorageClass 遷移 Persistent Volumes 完全指南

大家好,我是米開朗基楊。

KubeSphere 3.3.0 (不出意外的話~)本週就要 GA 了,作爲一名 KubeSphere 腦殘粉,我迫不及待地先安裝 RC 版嚐嚐鮮,一頓操作猛如虎開啓所有組件,裝完之後發現有點尷尬:我用錯了持久化存儲。

我的 K8s 集羣中有兩個存儲類(StorageClass),一個是 OpenEBS 提供的本地存儲,另一個是 QingCloud CSI 提供的分佈式存儲,而且默認的 StorageClass 是 OpenEBS 提供的 local-hostpath,所以 KubeSphere 的有狀態組件默認便使用本地存儲來保存數據。

失誤失誤,我本來是想用分佈式存儲作爲默認存儲的,但是我忘記將 csi-qingcloud 設置爲默認的 StorageClass 了,反正不管怎樣,就這麼稀裏糊塗地搞錯了。雖然重裝可以解決 99% 的問題,但作爲一名成熟的 YAML 工程師,重裝是不可能的,必須在不重裝的情況下解決這個問題,才能體現出我的氣質!

事實上不止我一個人遇到過這種情況,很多人都會稀裏糊塗地裝完一整套產品之後發現 StorageClass 用錯了,這時候再想改回去恐怕就沒那麼容易了。這不巧了麼這不是,本文就是來幫助大家解決這個問題的。

思路

我們先來思考一下換 StorageClass 需要做哪幾件事情。首先需要將應用的副本數縮減爲 0,然後創建一個新的 PVC,將舊 PV 的數據複製到新 PV,然後讓應用使用新的 PV,並將副本擴展到原來的數量,最後再將舊 PV 刪除。在這整個過程中還要防止刪除 PVC 時 Kubernetes 將 PV 也刪除了。

當然,有些 CSI 驅動或者存儲後端可能會有更便利的數據遷移技巧,但是本文提供的是一種更加通用的方案,不管後端是什麼存儲都可以。

KubeSphere 3.3.0 開啓所有組件之後使用的持久卷聲明(PVC)如下:

本文就以 Elasticsearch 爲例,演示如何將 Elasticsearch 的存儲從本地存儲替換爲分佈式存儲。

備份 PVC 和 PV

首先第一步就是備份 PVC 和 PV,萬一後面操作失敗了,還有反悔的餘地。

$ kubectl -n kubesphere-logging-system get pvc
NAME                                     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS     AGE
data-elasticsearch-logging-data-0        Bound    pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9   20Gi       RWO            local-hostpath   28h
data-elasticsearch-logging-data-1        Bound    pvc-0851350a-270e-4d4d-af8d-081132c1775b   20Gi       RWO            local-hostpath   28h
data-elasticsearch-logging-discovery-0   Bound    pvc-8f32fc97-3d6e-471a-8121-655991d945a8   4Gi        RWO            local-hostpath   28h

$ kubectl -n kubesphere-logging-system get pv pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9 -o yaml > pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9.yaml
$ kubectl -n kubesphere-logging-system get pv pvc-0851350a-270e-4d4d-af8d-081132c1775b -o yaml > pvc-0851350a-270e-4d4d-af8d-081132c1775b.yaml

$ kubectl -n kubesphere-logging-system get pvc data-elasticsearch-logging-data-0 -o yaml > data-elasticsearch-logging-data-0.yaml
$ kubectl -n kubesphere-logging-system get pvc data-elasticsearch-logging-data-1 -o yaml > data-elasticsearch-logging-data-1.yaml

複製數據

不管 PV 的 accessModes 是 ReadWriteOnce 還是 ReadWriteMany,在複製數據之前都要將應用的副本數量縮減爲 0,因爲 ReadWriteOne 模式同時只允許掛載一個 Pod,新 Pod 無法掛載,而 ReadWriteMany 模式如果不將副本數量縮減爲 0,在複製數據時可能會有新的數據寫入。所以無論如何,都要將副本數量縮爲 0 。

$ kubectl -n kubesphere-logging-system get sts
NAME                              READY   AGE
elasticsearch-logging-data        2/2     28h
elasticsearch-logging-discovery   1/1     28h

$ kubectl -n kubesphere-logging-system scale sts elasticsearch-logging-data --replicas=0

$ kubectl -n kubesphere-logging-system get sts
NAME                              READY   AGE
elasticsearch-logging-data        0/0     28h
elasticsearch-logging-discovery   1/1     28h

創建一個新的 PVC 叫 new-data-elasticsearch-logging-data-0,容量和 data-elasticsearch-logging-data-0 一樣,並將 storageClassName 指定爲新的 StorageClass。

創建一個 Deployment,將新 PV 和舊 PV 都掛載進去,然後再將舊 PV 的數據拷貝到新 PV。

在『工作負載』界面點擊『創建』,將下面的 YAML 粘貼進去即可。

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: kubesphere-logging-system
  labels:
    app: datacopy
  name: datacopy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: datacopy
  template:
    metadata:
      labels:
        app: datacopy
    spec:
      containers:
        - name: datacopy
          image: ubuntu
          command:
            - 'sleep'
          args:
            - infinity
          volumeMounts:
            - name: old-pv
              readOnly: false
              mountPath: /mnt/old
            - name: new-pv
              readOnly: false
              mountPath: /mnt/new
      volumes:
        - name: old-pv
          persistentVolumeClaim:
            claimName: data-elasticsearch-logging-data-0
        - name: new-pv
          persistentVolumeClaim:
            claimName: new-data-elasticsearch-logging-data-0

這個 Deployment 將新 PV 和舊 PV 都掛載進去了,稍後我們會將舊 PV 的數據拷貝到新 PV。

Pod 啓動成功後,點擊容器的終端圖標進入容器的終端。

在容器中先驗證舊 PV 的掛載點是否包含應用數據,新 PV 的掛載點是否是空的,之後再執行命令 (cd /mnt/old; tar -cf - .) | (cd /mnt/new; tar -xpf -),以確保所有數據的所有權和權限被繼承。

執行完成後,驗證新 PV 的掛載點是否包含舊 PV 的數據,以及所有權和權限是否被正確繼承。

到這裏複製數據的任務就完成了,現在我們需要將 datacopy 的副本數量縮爲 0。

遷移 PVC

遷移存儲的理想狀態是使用舊的 PVC,並將其指向新的 PV,這樣工作負載的 YAML 配置清單就不需要做任何改變。但 PVC 和 PV 之間的綁定關係是不可更改的,要想讓它們解綁,必須先刪除舊的 PVC,再創建同名的 PVC,並將舊的 PV 與它綁定。

需要注意的是,默認情況下 PV 的回收策略是 Delete,一旦刪除 PVC,與之綁定的 PV 和 PV 裏的數據都會被刪除。這是我們不希望看到的,所以我們需要修改回收策略,以便刪除 PVC 時 PV 能夠保留下來。

事實上可以通過 StorageClass 來設置全局的回收策略(reclaimPolicy),如果不設置,默認就是 Delete。可以通過命令 kubectl describe pv <pv-name> 來查看 PV 的回收策略(reclaimPolicy):

$ kubectl describe pv pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9
Name:              pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9
Labels:            openebs.io/cas-type=local-hostpath
Annotations:       pv.kubernetes.io/provisioned-by: openebs.io/local
Finalizers:        [kubernetes.io/pv-protection]
StorageClass:      local-hostpath
Status:            Bound
Claim:             kubesphere-logging-system/data-elasticsearch-logging-data-0
Reclaim Policy:    Delete
...

$ kubectl describe pv pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
Name:              pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
Labels:            <none>
Annotations:       pv.kubernetes.io/provisioned-by: disk.csi.qingcloud.com
Finalizers:        [kubernetes.io/pv-protection external-attacher/disk-csi-qingcloud-com]
StorageClass:      csi-qingcloud
Status:            Bound
Claim:             kubesphere-logging-system/new-data-elasticsearch-logging-data-0
Reclaim Policy:    Delete
...

我們可以通過 patch 命令將新舊 PV 的回收策略設置爲 Retain

$ kubectl patch pv pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9 -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
persistentvolume/pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9 patched

$ kubectl patch pv pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
persistentvolume/pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9 patched

⚠️注意:該命令對 PV 的穩定性和可用性沒有任何影響,可以隨時執行。

現在可以將新舊 PVC 全部刪除,PV 不會受到任何影響。

$ kubectl -n kubesphere-logging-system delete pvc data-elasticsearch-logging-data-0 new-data-elasticsearch-logging-data-0
persistentvolumeclaim "data-elasticsearch-logging-data-0" deleted
persistentvolumeclaim "new-data-elasticsearch-logging-data-0" deleted

在創建最終的 PVC 之前,我們必須要確保新創建的 PVC 能夠被綁定到新的 PV 上。通過以下命令可以看到新 PV 目前處於釋放狀態,不能被新 PVC 綁定:

$ kubectl describe pv pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
Name:              pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
Labels:            <none>
Annotations:       pv.kubernetes.io/provisioned-by: disk.csi.qingcloud.com
Finalizers:        [kubernetes.io/pv-protection external-attacher/disk-csi-qingcloud-com]
StorageClass:      csi-qingcloud
Status:            Released
Claim:             kubesphere-logging-system/new-data-elasticsearch-logging-data-0
Reclaim Policy:    Retain
Access Modes:      RWO
VolumeMode:        Filesystem
Capacity:          20Gi
...

這是因爲 PV 在 spec.claimRef 中仍然引用了已經被刪除的 PVC:

$ kubectl get pv pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e -o yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  ...
  name: pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
  ...
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 20Gi
  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    name: new-data-elasticsearch-logging-data-0
    namespace: kubesphere-logging-system
    resourceVersion: "657019"
    uid: f4e96f69-b3be-4afe-bb52-1e8e728ca55e
  ...
  persistentVolumeReclaimPolicy: Retain
  storageClassName: csi-qingcloud
  volumeMode: Filesystem

爲了解決這個問題,可以直接通過命令 kubectl edit pv <pv-name> 編輯 PV,將 claimRef 的內容全部刪除。然後再查看 PV 已經處於可用狀態(Available):

$ kubectl describe pv pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
Name:              pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
Labels:            <none>
Annotations:       pv.kubernetes.io/provisioned-by: disk.csi.qingcloud.com
Finalizers:        [kubernetes.io/pv-protection external-attacher/disk-csi-qingcloud-com]
StorageClass:      csi-qingcloud
Status:            Available
Claim:
Reclaim Policy:    Retain
Access Modes:      RWO
VolumeMode:        Filesystem
Capacity:          20Gi

最終我們需要創建與舊 PVC 同名的新 PVC,而且要儘可能保證與舊 PVC 的參數相同:

  • 新 PVC 的名字和舊 PVC 的名字相同;
  • spec.volumeName 指向新 PV;
  • 新 PVC 的 metadata.annotationsmetadata.labels 和舊 PVC 保存相同,因爲這些值可能會影響到應用部署(比如 Helm chart 等)。

最終 PVC 內容如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  labels:
    app: elasticsearch
    component: data
    release: elasticsearch-logging
    role: data
  name: data-elasticsearch-logging-data-0
  namespace: kubesphere-logging-system
spec:
  storageClassName: csi-qingcloud
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  volumeMode: Filesystem
  volumeName: pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e

在『存儲卷聲明』頁面點擊『創建』:

選擇『編輯 YAML』,將上面的 YAML 內容複製粘貼進去,然後點擊『創建』:

最終可以看到新的 PVC 和 PV 全部都是 Bound 狀態:

$ kubectl -n kubesphere-logging-system get pvc data-elasticsearch-logging-data-0
NAME                                STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
data-elasticsearch-logging-data-0   Bound    pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e   20Gi       RWO            csi-qingcloud   64s

$ kubectl get pv pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                         STORAGECLASS    REASON   AGE
pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e   20Gi       RWO            Retain           Bound    kubesphere-logging-system/data-elasticsearch-logging-data-0   csi-qingcloud            11h

再來一遍

到目前爲止,我們只遷移了 data-elasticsearch-logging-data-0 的數據,對於 data-elasticsearch-logging-data-1,按照上面的步驟再重複一遍就行了,記得將 datacopy 中的 PVC 改爲 data-elasticsearch-logging-data-1new-data-elasticsearch-logging-data-0,其他地方的配置內容也要修改爲新的。

恢復工作負載

現在所有的存儲都遷移完成,PVC 名稱保持不變,PV 使用的是新的存儲。

$ kubectl get pv -A|grep elasticsearch-logging-data
pvc-0851350a-270e-4d4d-af8d-081132c1775b   20Gi       RWO            Retain           Released   kubesphere-logging-system/data-elasticsearch-logging-data-1        local-hostpath            40h
pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9   20Gi       RWO            Retain           Released   kubesphere-logging-system/data-elasticsearch-logging-data-0        local-hostpath            40h
pvc-d0acd2e7-ee1d-47cf-8506-69147fe25563   20Gi       RWO            Retain           Bound      kubesphere-logging-system/data-elasticsearch-logging-data-1        csi-qingcloud             9m53s
pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e   20Gi       RWO            Retain           Bound      kubesphere-logging-system/data-elasticsearch-logging-data-0        csi-qingcloud             11h

$ kubectl -n kubesphere-logging-system get pvc|grep elasticsearch-logging-data
data-elasticsearch-logging-data-0        Bound    pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e   20Gi       RWO            csi-qingcloud    27m
data-elasticsearch-logging-data-1        Bound    pvc-d0acd2e7-ee1d-47cf-8506-69147fe25563   20Gi       RWO            csi-qingcloud    3m49s

將工作負載的副本恢復到之前的數量:

$ kubectl -n kubesphere-logging-system scale sts elasticsearch-logging-data --replicas=2
statefulset.apps/elasticsearch-logging-data scaled

$ kubectl -n kubesphere-logging-system get pod -l app=elasticsearch,component=data
NAME                           READY   STATUS    RESTARTS   AGE
elasticsearch-logging-data-0   1/1     Running   0          4m12s
elasticsearch-logging-data-1   1/1     Running   0          3m42s

完美!

最後還有一點收尾工作,我們需要將所有新 PV 的回收策略重新設置爲 Delete

$ kubectl patch pv pvc-d0acd2e7-ee1d-47cf-8506-69147fe25563 -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
persistentvolume/pvc-d0acd2e7-ee1d-47cf-8506-69147fe25563 patched

$ kubectl patch pv pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
persistentvolume/pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e patched

$ kubectl get pv -A|grep elasticsearch-logging-data
pvc-0851350a-270e-4d4d-af8d-081132c1775b   20Gi       RWO            Retain           Released   kubesphere-logging-system/data-elasticsearch-logging-data-1        local-hostpath            40h
pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9   20Gi       RWO            Retain           Released   kubesphere-logging-system/data-elasticsearch-logging-data-0        local-hostpath            40h
pvc-d0acd2e7-ee1d-47cf-8506-69147fe25563   20Gi       RWO            Delete           Bound      kubesphere-logging-system/data-elasticsearch-logging-data-1        csi-qingcloud             15m
pvc-f4e96f69-b3be-4afe-bb52-1e8e728ca55e   20Gi       RWO            Delete           Bound      kubesphere-logging-system/data-elasticsearch-logging-data-0        csi-qingcloud             11h

最後的最後,就可以將舊 PV 全部刪除了:

$ kubectl delete pv pvc-0851350a-270e-4d4d-af8d-081132c1775b
persistentvolume "pvc-0851350a-270e-4d4d-af8d-081132c1775b" deleted

$ kubectl delete pv pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9
persistentvolume "pvc-9aed3d1b-09a6-4fe3-8adc-9195a2bbb2b9" deleted

更簡單的方案

上面的方案雖然完美解決了問題,但步驟比較繁瑣,有沒有更簡潔的方法呢?

可以試試青雲推出的雲原生備份容災 SaaS 服務,無需部署、維護本地備份基礎架構,即可輕鬆完成多雲異構環境下數據的自由遷移,從而實現多地、按需的數據保護與應用的高可用。而且價格比較親民,對白嫖黨友好,提供了 100GB 的免費存儲,遷移幾個 PV 完全夠用了。

使用起來非常簡單,先註冊賬戶,然後導入 Kubernetes 集羣。如果選擇通過代理連接 Kubernetes 集羣,需要執行紅色方框內命令在 Kubernetes 集羣中安裝代理。

然後新建託管倉庫。

接下來直接創建備份計劃,選擇直接複製

備份成功之後,將集羣中的 PVC 和 PV 刪除,並將工作負載的副本數縮減爲 0。最後創建恢復計劃,注意將源存儲類型名稱爲 local-hostpath 的目標存儲類型名稱設置爲你想遷移的存儲,這樣恢復後的 PV 使用的就是新的 StorageClass。

完了。

總結

本文介紹瞭如何將 Kubernetes 集羣中現有 PV 的數據遷移到新的 PV,並創建同名的 PVC 來指向新的 PV,這樣就完成了應用的數據遷移而不需要對應用的配置清單做任何更改。最後還介紹瞭如何通過雲原生備份容災 SaaS 服務來簡化遷移過程。

本文由博客一文多發平臺 OpenWrite 發佈!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章