---
# System prepended metadata

title: Harvester 使用第三方 storage(Rook Ceph RBD)
tags: [工具]

---

# Harvester 使用第三方 storage(Rook Ceph RBD)

## 部署 Rook Ceph RBD Server 可以[參考](https://hackmd.io/@wu-andy/ryUHrzVfbl)

## Ceph Server 創建 Pool

* 在 Ceph Server 建立 Ceph Pool
```
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd pool create harvester

# Use the rbd tool to initialize the pool
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- rbd pool init harvester

# 設定 pool 最大容量為 100GB
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd pool set-quota harvester max_bytes $((100 * 1024 * 1024 * 1024))
```
```
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd pool get-quota harvester
quotas for pool 'harvester':
  max objects: N/A
  max bytes  : 100 GiB  (current num bytes: 19 bytes)
```

* 在 Ceph 系統中建立一個專門的使用者帳號（hx），並賦予它存取特定硬碟池 (Pool) 的權限
```
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph auth get-or-create client.hx mon 'profile rbd' osd 'profile rbd pool=harvester' mgr 'profile rbd pool=harvester'
[client.hx]
        key = AQBF2jdpkyB5CxAAA5lX96UfP3Y1MGZzQHKaPw==  # 這個就是待會要填的 userkey
```

```
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph auth get-key client.hx
AQBF2jdpkyB5CxAAA5lX96UfP3Y1MGZzQHKaPw==
```

![image](https://hackmd.io/_uploads/By-dc8SzZl.png)


* 獲取 Ceph monitor 和 fsid 資訊
```
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph mon dump
epoch 3
fsid b295be2e-5166-43dc-8b2f-2adcc9cd457b             # 此行就是 clusterID
last_changed 2025-12-09T06:00:34.717245+0000
created 2025-12-09T06:00:13.104271+0000
min_mon_release 19 (squid)
election_strategy: 1
0: [v2:172.20.1.51:3300/0,v1:172.20.1.51:6789/0] mon.a
1: [v2:172.20.1.52:3300/0,v1:172.20.1.52:6789/0] mon.b
2: [v2:172.20.1.50:3300/0,v1:172.20.1.50:6789/0] mon.c
```

## harvester 安裝 Ceph-CSI RBD plugins

```
$ kubectl get no -owide
NAME   STATUS   ROLES                       AGE    VERSION          INTERNAL-IP   EXTERNAL-IP   OS-IMAGE           KERNEL-VERSION                  CONTAINER-RUNTIME
hx-1   Ready    control-plane,etcd,master   7d5h   v1.33.5+rke2r1   172.20.2.11   <none>        Harvester v1.6.1   5.14.21-150500.55.121-default   containerd://2.1.4-k3s2
hx-2   Ready    <none>                      6d6h   v1.33.5+rke2r1   172.20.2.12   <none>        Harvester v1.6.1   5.14.21-150500.55.121-default   containerd://2.1.4-k3s2
hx-3   Ready    <none>                      21m    v1.33.5+rke2r1   172.20.2.13   <none>        Harvester v1.6.1   5.14.21-150500.55.121-default   containerd://2.1.4-k3s2
```
* 在其他環境打包，並上傳至 harvester
```
$ git clone https://github.com/ceph/ceph-csi.git

$ tar -czvf ceph-csi.tar.gz ceph-csi/

$ scp -O ceph-csi.tar.gz rancher@172.20.2.11:~
```


* 在 harvester 上解壓縮，建立並切換至 `csi-ceph` namespace
```
$ tar -zxvf ceph-csi.tar.gz

$ kubectl create ns csi-ceph

$ kubectl config set-context --current --namespace=csi-ceph
```

* 設定 Ceph-csi configmap

```
$ cd ~/ceph-csi/deploy/rbd/kubernetes

$ cat <<EOF > csi-config-map.yaml
---
apiVersion: v1
kind: ConfigMap
data:
  config.json: |-
    [
      {
        "clusterID": "b295be2e-5166-43dc-8b2f-2adcc9cd457b",
        "monitors": [
          "172.20.1.51:6789",
          "172.20.1.52:6789",
          "172.20.1.53:6789"
        ]
      }
    ]
metadata:
  name: ceph-csi-config
EOF
```
> 須設定 clusterID 和 monitors 的 IP Address
* 設定 csidriver 的 pod 不要 Mount host 主機的 `/etc/selinux` 到 pods 裡面
```
$ sed -i 's|seLinuxMount: true|seLinuxMount: false|g' csidriver.yaml
$ sed -i '/- mountPath: \/etc\/selinux/,+2d' csi-rbdplugin.yaml
$ sed -i '/- name: etc-selinux/,+2d' csi-rbdplugin.yaml
```
* 將所有 Yaml 中定義的物件都更換為 csi-ceph Namespace
```
$ sed -i 's|namespace: default|namespace: csi-ceph|g' *.yaml
```
* 設定 csi-rbdplugin-provisioner 和 csi-rbdplugin 的 Pod 能夠在 Contorlplane Node 上執行
```
$ sed -i '36i\      tolerations:\n        - operator: Exists' csi-rbdplugin-provisioner.yaml
$ sed -i '24i\      tolerations:\n        - operator: Exists' csi-rbdplugin.yaml
```
* 產生 CEPH-CSI cephx secret

```
$ cat <<EOF > ~/ceph-csi/examples/rbd/secret.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: csi-rbd-secret
  namespace: csi-ceph                                # change
stringData:
  userID: hx                                 # change
  userKey: AQBF2jdpkyB5CxAAA5lX96UfP3Y1MGZzQHKaPw==  # change

  # Encryption passphrase
  encryptionPassphrase: test_passphrase
EOF
```
> 須設定 namespace、userID 和 userKey 的值
* 建立 CEPH-CSI cephx secret
```
$ kubectl apply -f ~/ceph-csi/examples/rbd/secret.yaml
```
* 給予 csi-ceph 這個 Namespace 最高權限
```
$ kubectl label ns csi-ceph pod-security.kubernetes.io/enforce=privileged
```
> csi-rbdplugin 和 csi-rbdplugin-provisioner 的 Pod 會需要 privileged 的權限。

* 最新版本的 ceph-csi 還需要另一個 ConfigMap 物件來定義 Ceph 的設定資訊，以便新增至 CSI Container 內的 ceph.conf 檔案中

```
$ kubectl apply -f ~/ceph-csi/deploy/ceph-conf.yaml
```
* 開始部屬 ceph-csi
```
$ cd ~/ceph-csi/examples/rbd

$ ./plugin-deploy.sh ~/ceph-csi/deploy/rbd/kubernetes

## 移除 vault
$ kubectl delete -f ../kms/vault/vault.yaml
```

* 檢視 ceph-csi 部屬狀態

```
$ kubectl get all
NAME                                             READY   STATUS    RESTARTS      AGE
pod/csi-rbdplugin-lt295                          3/3     Running   0             28m
pod/csi-rbdplugin-provisioner-57ff7fc887-j8gcj   7/7     Running   0             29m
pod/csi-rbdplugin-provisioner-57ff7fc887-s7rd4   7/7     Running   0             29m
pod/csi-rbdplugin-provisioner-57ff7fc887-vvdc4   7/7     Running   0             29m
pod/csi-rbdplugin-vrgpr                          3/3     Running   0             16m
pod/csi-rbdplugin-x7kjh                          3/3     Running   1 (15m ago)   16m

NAME                                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/csi-metrics-rbdplugin       ClusterIP   10.96.233.216   <none>        8080/TCP   38m
service/csi-rbdplugin-provisioner   ClusterIP   10.96.72.154    <none>        8080/TCP   38m

NAME                           DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/csi-rbdplugin   3         3         3       3            3           <none>          38m

NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/csi-rbdplugin-provisioner   3/3     3            3           38m

NAME                                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/csi-rbdplugin-provisioner-57ff7fc887   3         3         3       38m
```

* 設定 StorageClass Yaml 檔
```
$ cat <<EOF > csi-rbd-sc.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: csi-rbd-sc
provisioner: rbd.csi.ceph.com
parameters:
   clusterID: b295be2e-5166-43dc-8b2f-2adcc9cd457b      # change
   pool: harvester                                     # change
   imageFeatures: layering
   csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
   csi.storage.k8s.io/provisioner-secret-namespace: csi-ceph
   csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
   csi.storage.k8s.io/controller-expand-secret-namespace: csi-ceph
   csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
   csi.storage.k8s.io/node-stage-secret-namespace: csi-ceph
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions:
   - discard
EOF
```
> 須設定 clusterID 和 pool 的值
* 建立 StorageClass
```
$ kubectl apply -f csi-rbd-sc.yaml
```
* 檢視 StorageClass
```
$ kubectl get sc csi-rbd-sc
NAME         PROVISIONER        RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
csi-rbd-sc   rbd.csi.ceph.com   Delete          Immediate           true                   11s
```


* 設定 VolumeSnapshotClass Yaml 檔
```
$ cat <<EOF > snapshotclass.yaml
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-rbdplugin-snapclass
driver: rbd.csi.ceph.com
parameters:
  clusterID: b295be2e-5166-43dc-8b2f-2adcc9cd457b
  csi.storage.k8s.io/snapshotter-secret-name: csi-rbd-secret
  csi.storage.k8s.io/snapshotter-secret-namespace: csi-ceph
deletionPolicy: Delete
EOF
```
> 須設定 clusterID 和 pool 的值


* 建立 SnapshotClass
```
$ kubectl apply -f snapshotclass.yaml
```
* 檢視 SnapshotClass
```
$ kubectl get VolumeSnapshotClass
NAME                      DRIVER               DELETIONPOLICY   AGE
csi-rbdplugin-snapclass   rbd.csi.ceph.com     Delete           30s
longhorn                  driver.longhorn.io   Delete           7d5h
longhorn-snapshot         driver.longhorn.io   Delete           7d5h
```

* 產生 `volumegroupsnapshot` CRD，如果沒有這個 CRD 會導致 volume snapshot 失敗
```
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/refs/heads/master/client/config/crd/groupsnapshot.storage.k8s.io_volumegroupsnapshotclasses.yaml

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/refs/heads/master/client/config/crd/groupsnapshot.storage.k8s.io_volumegroupsnapshotcontents.yaml

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/refs/heads/master/client/config/crd/groupsnapshot.storage.k8s.io_volumegroupsnapshots.yaml
```


* 切回 `default` namespace
```
$ kubectl config set-context --current --namespace=default
```


## harvester 設定 csi-driver-config

![image](https://hackmd.io/_uploads/SkSe-DBfbe.png)


## 驗證

### harvester 建立 vm 使用 Rook Ceph RBD

* 第一顆硬碟掛載 ISO

![image](https://hackmd.io/_uploads/B11hrwHz-l.png)


* 第二顆硬碟用來安裝 OS，使用 Rook Ceph RBD 儲存

![image](https://hackmd.io/_uploads/B1x18wrGbe.png)

* 安裝好後可以正常開機

![image](https://hackmd.io/_uploads/SJHziwSzbx.png)


### 測試 volume snapshot 功能

![image](https://hackmd.io/_uploads/B1lYW_BfWx.png)

![image](https://hackmd.io/_uploads/B1Jq-dBfbg.png)

* 確認 snapshot 狀態正常

![image](https://hackmd.io/_uploads/SJQISF8z-g.png)

* 在 Volume Snapshots 可以看到 snpshot

![image](https://hackmd.io/_uploads/H1Pj-OHfbl.png)


* 測試 restore

![image](https://hackmd.io/_uploads/SJiDLYUG-x.png)

* restore 時要命名 volume ，他會再產生一個 volume

![image](https://hackmd.io/_uploads/BkBFLtIfbl.png)

* 新產生的 volume

![image](https://hackmd.io/_uploads/Skjp8YIzbx.png)

* 將 vm 先關機

![image](https://hackmd.io/_uploads/Bk7JYK8M-x.png)

* 將 vm 原有的 volume 移除，然後再點選 `Add Existing Volume` 選擇剛剛 restore 的 volume

![image](https://hackmd.io/_uploads/H1V6FtLzbx.png)

* 重新開機後，並成功將資料回復成 snapshot 當時的內容

![image](https://hackmd.io/_uploads/BkNx2YIfbg.png)


## 參考
https://docs.harvesterhci.io/v1.6/advanced/csidriver/

https://github.com/kubernetes-csi/external-snapshotter/tree/master/client/config/crd