本文记录 rook-ceph 的整体架构、在 Kubernetes 上的部署过程,以及依靠 rook-ceph-tools 完成的日常运维操作。集群跑起来之后,块存储、共享文件存储、对象存储的具体用法见 rook-ceph 存储使用方法 。
⚠️ 注:下文命令与清单是 rook v1.5.9、ceph v15.2.x 时期的记录。rook 自 v1.7 起把示例清单目录从 cluster/examples/kubernetes/ceph 调整为 deploy/examples,CRD 与 CSI 镜像也随版本变化,套用前请对照自己所用版本的官方文档。
Ceph 与 Rook 简介 先分清两者的分工:Ceph 提供存储能力,Rook 负责把 Ceph 编排到 Kubernetes 上。
Ceph 简介 Ceph 是一个可靠、自动重均衡、自动恢复的分布式存储系统。按使用场景可以把 Ceph 分为三大块:
对象存储:object
块设备存储:block
文件系统服务:file
Ceph 核心组件 一套 Ceph 集群由下面这些守护进程组成,排查问题时基本都是在和它们打交道。
OSD Object Storage Device,主要功能包括存储数据,处理数据的复制、恢复、回补、平衡数据分布,并将一些相关数据提供给 Ceph Monitor。
MON 即 monitor,Ceph 的监控器,主要功能是维护整个集群健康状态、提供一致性的决策,包含了 Monitor map(即集群 map)。monitor 不存储用户数据 ,但它负责维护并持久化整个 cluster map(monmap/osdmap/crushmap/mdsmap/fsmap)和 fsid,这些落在 mon 自己的 rocksdb store 里(rook 下就是 dataDirHostPath),并通过 Paxos 在多个 mon 之间达成一致。它是 rook-ceph 里两个有状态服务之一——后面”集群修复”那一节做的正是从 /var/lib/rook/rook-ceph/* 里把 mon 的这些数据恢复出来。
MGR Ceph Manager 守护进程(ceph-mgr)负责跟踪运行时指标和 Ceph 集群的当前状态,包括存储利用率、当前性能指标和系统负载。
MDS Ceph Metadata Server,保存的是 Ceph 文件系统(File System)的元数据(metadata)。它不是必须安装的组件,只有需要使用 CephFS 的时候才会用到。
RGW 即 radosgw,提供对象存储服务的网关,只有使用对象存储时才会部署。
RADOS 与上层访问接口 底层是 RADOS,往上通过 librados 派生出块、文件、对象三类访问接口。
RADOS 自身是一个完整的分布式对象存储系统,具有可靠、智能、分布式等特性。Ceph 的高可靠、高可拓展、高性能、高自动化都是由这一层来提供的,用户数据最终也都是通过这一层来存储的。RADOS 可以说就是 Ceph 的核心,主要由 OSD 和 Monitor 两部分构成。
librados 它是一个库,允许应用程序通过它与 RADOS 系统交互,支持 C、C++、Python 等多种编程语言。
RBD、RGW、CephFS 则可以归为上层应用接口,都建立在 librados 之上。
RBD RBD 通过 Linux 内核客户端和 QEMU/KVM 驱动来提供一个分布式的块设备,可以理解为像 Linux 的 LVM 一样,从 Ceph 集群中划分出一块磁盘,用户可以直接在上面做文件系统和挂载目录。
CephFS 通过 Linux 内核客户端和 fuse 来提供一个兼容 POSIX 的文件系统。当一些 linux 系统不支持 mount 命令,或者需要更高级的操作时,会用到 ceph-fuse。
RADOSGW RADOSGW 是一套基于 RESTFUL 协议的网关,只有当使用对象存储时才会用到。
Rook 及其组成 Rook 是一个自管理的分布式存储编排系统,可以为 Kubernetes 提供便利的存储解决方案。Rook 本身并不提供存储,而是在 Kubernetes 和存储系统之间提供适配层,简化存储系统的部署与维护工作。除 Ceph 之外,rook 还支持 CockroachDB、Cassandra、EdgeFS、Minio、NFS 等存储系统。
用在 Ceph 上时,Rook 使用 Kubernetes 原语让 Ceph 存储系统能够在 Kubernetes 上运行,它由两部分组成:
Operator:由一些 CRD 和一个 All in one 镜像构成,包含启动和监控存储系统的所有功能。
Cluster:负责创建 CRD 对象,指定相关参数,包括 ceph 镜像、元数据持久化位置、磁盘位置、dashboard 等等。
部署 rook-ceph 官网地址 https://rook.io/ 。部署顺序是固定的:crds.yaml + common.yaml → operator.yaml → cluster.yaml → toolbox.yaml。
获取部署清单 切换到写作时使用的 v1.5.9 分支:
1 2 git clone --single-branch --branch v1.5.9 https://github.com/rook/rook.git cd rook/cluster/examples/kubernetes/ceph
这个目录下就是全部示例清单:
1 2 3 4 5 6 7 8 9 10 11 [root@k8s01 ceph]# ls ceph-client.yaml crds.yaml filesystem.yaml object-multisite.yaml pre-k8s-1.16 cluster-external-management.yaml create-external-cluster-resources.py flex object-openshift.yaml rbdmirror.yaml cluster-external.yaml create-external-cluster-resources.sh import-external-cluster.sh object-test.yaml rgw-external.yaml cluster-on-pvc.yaml csi monitoring object-user.yaml scc.yaml cluster-stretched.yaml dashboard-external-https.yaml nfs-test.yaml object.yaml storageclass-bucket-delete.yaml cluster-test.yaml dashboard-external-http.yaml nfs.yaml operator-openshift.yaml storageclass-bucket-retain.yaml cluster-with-drive-groups.yaml dashboard-ingress-https.yaml object-bucket-claim-delete.yaml operator.yaml test-data cluster.yaml dashboard-loadbalancer.yaml object-bucket-claim-retain.yaml osd-purge.yaml toolbox-job.yaml common-external.yaml direct-mount.yaml object-ec.yaml pool-ec.yaml toolbox.yaml common.yaml filesystem-ec.yaml object-external.yaml pool-test.yaml
默认的 crds.yaml 面向较新的 Kubernetes;低版本集群(k8s 1.15 及以下)需要换用 pre-k8s-1.16 目录下的那份:
1 2 mv crds.yaml crds.yaml.bakwget https://raw.githubusercontent.com/rook/rook/release-1.5/cluster/examples/kubernetes/ceph/pre-k8s-1.16/crds.yaml
部署 CRD 与公共资源 1 kubectl create -f crds.yaml -f common.yaml
部署 operator rook-ceph-operator-config 这个 ConfigMap 设计上就是可以随时改的:operator 会监听它,CSI 相关的设置改完会重建 CSI 的 DaemonSet/Deployment 来生效,跟 CephCluster 里的数据无关,rook 也没有”operator 配置变了就删集群”这种逻辑。真正需要一开始就定好、事后极难变更的是 CephCluster CR 里的少数字段:dataDirHostPath、network.provider、以及 mon 的 allowMultiplePerNode。
国内环境主要是替换镜像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ROOK_CSI_CEPH_IMAGE: "quay.io/cephcsi/cephcsi:v3.6.2" ROOK_CSI_REGISTRAR_IMAGE: "registry.aliyuncs.com/google_containers/csi-node-driver-registrar:v2.5.1" ROOK_CSI_RESIZER_IMAGE: "registry.aliyuncs.com/google_containers/csi-resizer:v1.4.0" ROOK_CSI_PROVISIONER_IMAGE: "registry.aliyuncs.com/google_containers/csi-provisioner:v3.1.0" ROOK_CSI_SNAPSHOTTER_IMAGE: "registry.aliyuncs.com/google_containers/csi-snapshotter:v6.0.1" ROOK_CSI_ATTACHER_IMAGE: "registry.aliyuncs.com/google_containers/csi-attacher:v3.4.0" ROOK_CSI_NFS_IMAGE: "registry.aliyuncs.com/google_containers/nfsplugin:v4.0.0" ROOK_CSI_ENABLE_GRPC_METRICS: "true" ROOK_ENABLE_DISCOVERY_DAEMON: "true"
其余是按需调整的部分,默认值大多可以不动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ROOK_CSI_ENABLE_CEPHFS: "true" CSI_FORCE_CEPHFS_KERNEL_CLIENT: "true" CSI_PROVISIONER_NODE_AFFINITY: "app.rook.role=csi-provisioner" CSI_PLUGIN_NODE_AFFINITY: "app.rook.plugin=csi" CSI_CEPHFS_GRPC_METRICS_PORT: "9491" CSI_CEPHFS_LIVENESS_METRICS_PORT: "9481" CSI_RBD_GRPC_METRICS_PORT: "9490" CSI_RBD_LIVENESS_METRICS_PORT: "9480"
以及 operator Deployment 容器部分的镜像与环境变量:
1 2 3 4 5 6 7 8 9 10 image: registry.aliyuncs.com/google_containers/rook/ceph:v1.5.1 env: - name: DISCOVER_AGENT_NODE_AFFINITY value: "app.rook=storage" - name: ROOK_ENABLE_DISCOVERY_DAEMON value: "true"
改完后应用:
1 2 3 [root@k8s01 ceph]# kubectl apply -f operator.yaml configmap/rook-ceph-operator-config created deployment.apps/rook-ceph-operator created
部署 cluster cluster.yaml 是集群的主体描述,节选出需要关注的字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: rook-ceph namespace: rook-ceph spec: cephVersion: image: registry.aliyuncs.com/google_containers/ceph/ceph:v15.2.5 allowUnsupported: false dataDirHostPath: /var/lib/rook skipUpgradeChecks: false mon: count: 3 allowMultiplePerNode: false mgr: modules: - name: pg_autoscaler enabled: true - name: rook enabled: true dashboard: urlPrefix: /test/ceph enabled: true port: 7000 ssl: false monitoring: enabled: false rulesNamespace: rook-ceph network: provider: host crashCollector: disable: false storage: useAllNodes: false useAllDevices: false nodes: - name: "192.168.2.231" devices: - name: "sdb" - name: "sdc" - name: "192.168.2.232" devices: - name: "sdb" - name: "192.168.2.233" devices: - name: "sdb"
这些字段之间的约束关系 逐字段的注释解决”这个键是什么意思”,但套到自己集群上时真正卡人的是字段之间的约束 。
size: 3 是总副本数,不是”额外三份”。 配 3 就是全集群一共 3 份数据(1 个主 + 2 个副本),可用容量是裸容量的 1/3。这一点看着显然,但配成 size: 2 想”省一点”的人不少——两副本在一个副本损坏时没有第三方可以仲裁谁是正确数据,Ceph 社区明确不建议用于生产。
failureDomain 决定了节点数的下限。 它的语义是”这 size 份副本必须落在不同的 XXX 上”:
failureDomain
size
需要的最小规模
不满足时的状态
host
3
3 个节点
少于 3 个节点 → PG 永远 undersized+degraded,写入可能被拒
host
2
2 个节点
能跑,但不建议(见上)
osd
3
3 个 OSD(可以在同一台机器上)
单机测试环境用;失去主机级容错 ,机器一挂全丢
所以”3 节点 + host + size: 3“是刚好够用的最小生产配置,一台机器下线期间集群处于 degraded 但仍可服务;2 节点想跑 3 副本只能把域降到 osd,那就丧失了跨主机冗余的意义。
requireSafeReplicaSize 拦的是什么。 它默认 true,作用是拒绝创建 size: 1 的池——单副本没有任何冗余,任一 OSD 故障即数据丢失。测试环境确实要单副本时才显式关掉它,关掉之前想清楚这个池里的数据能不能丢。
mon count 为什么必须是奇数。 MON 靠 Paxos 达成一致,quorum(法定人数)是 ⌊n/2⌋+1:
mon 数
quorum
能容忍几个挂掉
1
1
0
3
2
1
4
3
1 ← 和 3 个一样
5
3
2
6
4
2 ← 和 5 个一样
偶数不会提升容错能力,只是多养一个进程、多一份网络开销和一个额外的故障源,所以 rook 直接在校验里拦掉了偶数。3 个够绝大多数场景,节点很多、要求更高可用时上 5 个。
pg_num 与 pg_autoscaler。 开了自动伸缩(较新版本默认开)之后就别手工算 pg_num 了——它会根据池里实际数据量和 OSD 数量动态调整。手工创建池时给的 pg_num 只是个起始值 ,autoscaler 会接管后续变化:
1 2 ceph osd pool autoscale-status ceph osd pool set <pool> pg_autoscale_mode off
需要注意 PG 数量变化会触发数据重平衡,所以给一个离目标值不太远的起始值能省掉一轮大规模 backfill。经验公式是 OSD 数 × 100 / size,再向上取到 2 的幂。
1 kubectl apply -f cluster.yaml
操作系统上直接使用 ceph 命令需要额外安装 ceph 的包,某些操作系统还需要编译安装,所以更方便的做法是用 rook-ceph-tools 这个工具箱 pod 来操作 ceph。默认清单见 https://github.com/rook/rook/blob/master/deploy/examples/toolbox.yaml ,一般要做这几处修改:
把 Deployment 改成 DaemonSet,让每个节点都有一个 toolbox;不改也可以,那就固定在某个节点上操作。
镜像换成与集群版本对应的 ceph 镜像。
securityContext 里给到 privileged: true 和 runAsUser: 0,否则 rbd map、mount 这类操作没有权限。
额外挂载 /dev、/sys/bus、/lib/modules,使用 rbd 设备和 mount 命令需要它们。
hostNetwork: true,否则 rbd map 命令会挂住,见 rook issue 2021。
加上 tolerations,节点 unreachable 时 pod 能尽快重建。
改完后的完整清单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 apiVersion: apps/v1 kind: DaemonSet metadata: name: rook-ceph-tools namespace: rook-ceph labels: app: rook-ceph-tools spec: selector: matchLabels: app: rook-ceph-tools template: metadata: labels: app: rook-ceph-tools spec: dnsPolicy: ClusterFirstWithHostNet containers: - name: rook-ceph-tools image: imwl/ceph:v17.2.6 command: - /bin/bash - -c - | # Replicate the script from toolbox.sh inline so the ceph image # can be run directly, instead of requiring the rook toolbox CEPH_CONFIG="/etc/ceph/ceph.conf" MON_CONFIG="/etc/rook/mon-endpoints" KEYRING_FILE="/etc/ceph/keyring" write_endpoints() { endpoints=$(cat $ {MON_CONFIG }) mon_endpoints=$(echo "${endpoints}" | sed 's/[a-z0-9_-]\+=//g' ) DATE=$(date) echo "$DATE writing mon endpoints to ${CEPH_CONFIG}: ${endpoints}" cat <<EOF > $ {CEPH_CONFIG } [global ] mon_host = $ {mon_endpoints } [client.admin ] keyring = $ {KEYRING_FILE } EOF } watch_endpoints() { real_path=$(realpath $ {MON_CONFIG }) initial_time=$(stat -c %Z "${real_path}" ) while true ; do real_path=$(realpath $ {MON_CONFIG }) latest_time=$(stat -c %Z "${real_path}" ) if [[ "${latest_time}" != "${initial_time}" ]]; then write_endpoints initial_time=$ {latest_time } fi sleep 10 done } ceph_secret=${ROOK_CEPH_SECRET} if [[ "$ceph_secret" == "" ]]; then ceph_secret=$(cat /var/lib/rook-ceph-mon/secret.keyring) fi cat <<EOF > ${KEYRING_FILE} [$ {ROOK_CEPH_USERNAME }] key = ${ceph_secret} EOF write_endpoints watch_endpoints imagePullPolicy: IfNotPresent tty: true securityContext: privileged: true readOnlyRootFilesystem: false runAsUser: 0 runAsGroup: 0 env: - name: ROOK_CEPH_USERNAME valueFrom: secretKeyRef: name: rook-ceph-mon key: ceph-username volumeMounts: - mountPath: /etc/ceph name: ceph-config - name: mon-endpoint-volume mountPath: /etc/rook - name: ceph-admin-secret mountPath: /var/lib/rook-ceph-mon readOnly: true - mountPath: /dev name: dev - mountPath: /sys/bus name: sysbus - mountPath: /lib/modules name: libmodules hostNetwork: true volumes: - name: ceph-admin-secret secret: secretName: rook-ceph-mon optional: false items: - key: ceph-secret path: secret.keyring - name: mon-endpoint-volume configMap: name: rook-ceph-mon-endpoints items: - key: data path: mon-endpoints - name: ceph-config emptyDir: {} - name: dev hostPath: path: /dev - name: sysbus hostPath: path: /sys/bus - name: libmodules hostPath: path: /lib/modules tolerations: - key: "node.kubernetes.io/unreachable" operator: "Exists" effect: "NoExecute" tolerationSeconds: 5
应用后就可以直接借它检查集群健康状况了:
1 2 3 4 [root@k8s01 ceph]# kubectl apply -f toolbox.yaml [root@k8s01 ceph]# kubectl exec -it $(kubectl get pod -n rook-ceph -l app=rook-ceph-tools -o=jsonpath='{.items[0].metadata.name}' ) -n rook-ceph -- ceph health HEALTH_OK
开启 dashboard cluster.yaml 里已经把 dashboard 打开并监听 7000,再执行 dashboard-external-http.yaml 把它以 NodePort 暴露出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: v1 kind: Service metadata: name: rook-ceph-mgr-dashboard-external-http namespace: rook-ceph labels: app: rook-ceph-mgr rook_cluster: rook-ceph spec: ports: - name: dashboard port: 7000 protocol: TCP targetPort: 7000 selector: app: rook-ceph-mgr rook_cluster: rook-ceph sessionAffinity: None type: NodePort
执行完后可以看到 rook-ceph-mgr-dashboard-external-http 已经映射到节点端口 32070:
1 2 3 4 5 6 7 8 9 10 [root@k8s01 ceph]# kubectl get svc -n rook-ceph NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE csi-cephfsplugin-metrics ClusterIP 10.105.91.163 <none> 8080/TCP,8081/TCP 159m csi-rbdplugin-metrics ClusterIP 10.104.68.21 <none> 8080/TCP,8081/TCP 159m rook-ceph-mgr ClusterIP 10.108.132.13 <none> 9283/TCP 157m rook-ceph-mgr-dashboard ClusterIP 10.101.67.94 <none> 7000/TCP 157m rook-ceph-mgr-dashboard-external-http NodePort 10.68.18.182 <none> 7000:32070/TCP 160m rook-ceph-mon-a ClusterIP 10.96.153.68 <none> 6789/TCP,3300/TCP 159m rook-ceph-mon-b ClusterIP 10.109.178.82 <none> 6789/TCP,3300/TCP 158m rook-ceph-mon-c ClusterIP 10.104.162.214 <none> 6789/TCP,3300/TCP 157m
登录账户是 admin,初始密码在 secret 里:
1 2 [root@k8s01 ceph]# kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}" | base64 --decode && echo <REDACTED_PASSWORD>
也可以进入 rook-ceph-tools 容器直接改密码:
1 2 echo '<CHANGE_ME>' > /tmp/pass.txtceph dashboard ac-user-set-password admin -i /tmp/pass.txt
登录后如图所示:
集群就绪之后,块存储、共享文件存储、对象存储三种用法见 rook-ceph 存储使用方法 。
rook-ceph-tools 里已经准备好 ceph.conf 与 keyring,所有 ceph 原生命令都可以直接执行,是日常排查的第一落点。
查看集群状态 日常排查的入口就是进 toolbox 执行 ceph -s:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@test-61 ~]# kubectl exec -it -n rook-ceph rook-ceph-tools-84d9889d64-wlm6x -- bash [root@test-62 /]# ceph -s cluster: id : 546a216f-2c8e-4a9d-acf4-3041857a127a health: HEALTH_OK services: mon: 3 daemons, quorum a,b,c (age 2w) mgr: a(active, since 4h), standbys: b mds: 1/1 daemons up, 1 hot standby osd: 3 osds: 3 up (since 11d), 3 in (since 2w) data: volumes: 1/1 healthy pools: 4 pools, 81 pgs objects: 1.68k objects, 5.0 GiB usage: 18 GiB used, 882 GiB / 900 GiB avail pgs: 81 active+clean io: client: 1.2 KiB/s rd, 2.0 KiB/s wr, 2 op/s rd, 0 op/s wr
ceph health detail、ceph osd tree、ceph osd pool ls 等命令同样在这里执行,pool、rbd、cephfs 的完整命令参考见 rook-ceph 存储使用方法 中的原生使用部分。
操作 rbd 设备 toolbox 里挂了 /dev、/lib/modules 且是 hostNetwork,所以可以直接 rbd map 出块设备来验证存储是否可用。注意 krbd(内核态 rbd)只实现了 image feature 的一个子集,而且内核越老支持越少 :layering 3.10+、exclusive-lock 4.9+、data-pool 4.11+,而 object-map/fast-diff/deep-flatten/journaling 是 librbd 独有的,任何内核都不支持。所以在 CentOS 7 这种 3.10 内核上只能留 layering;rbd map 报 RBD image feature set mismatch 时,把内核不支持的特性关掉,或者改用用户态的 rbd-nbd:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [root@test-62 /]# rbd create replicapool/test --size 10 [root@test-62 /]# rbd info replicapool/test rbd image 'test' : size 10 MiB in 3 objects order 22 (4 MiB objects) snapshot_count: 0 id : 249d8df433415b block_name_prefix: rbd_data.249d8df433415b format: 2 features: layering, exclusive-lock, object-map, fast-diff, deep-flatten op_features: flags: create_timestamp: Mon Aug 14 02:36:22 2023 access_timestamp: Mon Aug 14 02:36:22 2023 modify_timestamp: Mon Aug 14 02:36:22 2023 [root@test-62 /]# rbd feature disable replicapool/test fast-diff deep-flatten object-map exclusive-lock [root@test-62 /]# rbd map replicapool/test /dev/rbd2 [root@test-62 /]# mkfs.ext4 -m0 /dev/rbd2 [root@test-62 /]# mkdir /tmp/rook-volume [root@test-62 /]# mount /dev/rbd2 /tmp/rook-volume [root@test-62 /]# df -h | grep rbd /dev/rbd2 8.7M 172K 8.4M 2% /tmp/rook-volume [root@test-62 /]# lsblk | grep rbd rbd0 251:0 0 8G 0 disk rbd1 251:16 0 8G 0 disk rbd2 251:32 0 10M 0 disk /tmp/rook-volume
用完按相反顺序卸载,先 umount 再 rbd unmap,否则设备会被占用:
1 2 3 4 5 [root@test-62 /]# umount /tmp/rook-volume [root@test-62 /]# rbd unmap /dev/rbd2 [root@test-62 /]# lsblk | grep rbd rbd0 251:0 0 8G 0 disk rbd1 251:16 0 8G 0 disk
挂载 CephFS 文件系统需要先创建(见 rook-ceph 存储使用方法 里的 filesystem.yaml),toolbox 内的 /etc/ceph/ceph.conf 和 /etc/ceph/keyring 是容器启动时自动写好的,直接取用即可:
1 2 3 4 5 6 7 8 9 10 11 12 [root@test-62 /]# mkdir /tmp/registry [root@test-62 /]# mon_endpoints=$(grep mon_host /etc/ceph/ceph.conf | awk '{print $3}' ) [root@test-62 /]# my_secret=$(grep key /etc/ceph/keyring | awk '{print $3}' ) [root@test-62 /]# mount -t ceph -o mds_namespace=myfs,name=admin,secret=$my_secret $mon_endpoints :/ /tmp/registry [root@test-62 /]# umount /tmp/registry
在宿主机上使用 ceph 命令 有些场景(比如把 CephFS 挂到主机目录)必须在节点上直接执行 ceph 命令,这时要装 ceph-common,且客户端版本要和集群、操作系统对应。下面示例主机是 CentOS Stream 9,装的是 ceph 17.2.5 的客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cat > /etc/yum.repos.d/ceph.repo <<-EOF [ceph] name=ceph baseurl=http://mirrors.aliyun.com/ceph/rpm-17.2.5/el9/x86_64/ gpgcheck=0 priority=1 [ceph-noarch] name=cephnoarch baseurl=http://mirrors.aliyun.com/ceph/rpm-17.2.5/el9/noarch/ gpgcheck=0 priority=1 [ceph-source] name=Ceph source packages baseurl=http://mirrors.aliyun.com/ceph/rpm-17.2.5/el9/SRPMS gpgcheck=0 priority=1 EOF
el9 上直接安装会缺一批依赖:
1 2 3 4 5 6 7 8 9 10 [root@k8s-254 ~]# yum install -y ceph-common Error: Problem: conflicting requests - nothing provides liboath.so.0()(64bit) needed by ceph-common-2:17.2.5-0.el9.x86_64 - nothing provides liboath.so.0(LIBOATH_1.2.0)(64bit) needed by ceph-common-2:17.2.5-0.el9.x86_64 - nothing provides libtcmalloc.so.4()(64bit) needed by ceph-common-2:17.2.5-0.el9.x86_64 - nothing provides liboath.so.0(LIBOATH_1.10.0)(64bit) needed by ceph-common-2:17.2.5-0.el9.x86_64 - nothing provides libthrift-0.14.0.so()(64bit) needed by ceph-common-2:17.2.5-0.el9.x86_64 (try to add '--skip-broken' to skip uninstallable packages or '--nobest' to use not only best candidate packages)
缺的包可以在 https://centos.pkgs.org/ 上查,先下载补齐再装 ceph-common:
1 2 3 4 wget https://dl.fedoraproject.org/pub/epel/9/Everything/x86_64/Packages/l/liboath-2.6.7-2.el9.x86_64.rpm wget https://dl.fedoraproject.org/pub/epel/9/Everything/x86_64/Packages/g/gperftools-libs-2.9.1-2.el9.x86_64.rpm wget https://dl.fedoraproject.org/pub/epel/9/Everything/x86_64/Packages/l/libunwind-1.6.2-1.el9.x86_64.rpm wget https://dl.fedoraproject.org/pub/epel/9/Everything/x86_64/Packages/t/thrift-0.14.0-7.el9.x86_64.rpm
装完之后命令还不能用,因为没有配置文件:
1 2 3 4 5 6 [root@k8s01 ~]# ceph -s 2021-03-29 13:29:43.244 7f4cbd2cd700 -1 Errors while parsing config file! 2021-03-29 13:29:43.244 7f4cbd2cd700 -1 parse_file: cannot open /etc/ceph/ceph.conf: (2) No such file or directory 2021-03-29 13:29:43.244 7f4cbd2cd700 -1 parse_file: cannot open /root/.ceph/ceph.conf: (2) No such file or directory 2021-03-29 13:29:43.244 7f4cbd2cd700 -1 parse_file: cannot open ceph.conf: (2) No such file or directory Error initializing cluster client: ObjectNotFound('error calling conf_read_file',)
配置文件直接抄 toolbox pod 里的那两份,内容形如:
1 2 3 4 5 6 7 8 9 10 [root@test-249 /] [global] mon_host = 172.20 .40.107 :6790 ,172.20 .40.173 :6790 ,172.20 .40.249 :6790 [client.admin] keyring = /etc/ceph/keyring[root@test-249 /] [client.admin] key = <REDACTED_CEPH_KEY>
在能访问集群的机器上可以一条命令拷过来:
1 2 3 kubectl exec -it $(kubectl get pod -n rook-ceph -l app=rook-ceph-tools -o=jsonpath='{.items[0].metadata.name}' ) -n rook-ceph -- cat /etc/ceph/ceph.conf | tee /etc/ceph/ceph.conf kubectl exec -it $(kubectl get pod -n rook-ceph -l app=rook-ceph-tools -o=jsonpath='{.items[0].metadata.name}' ) -n rook-ceph -- cat /etc/ceph/keyring | tee /etc/ceph/keyring
否则就手工写入,然后验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@test-173 ~]# vi /etc/ceph/ceph.conf [root@test-173 ~]# vi /etc/ceph/keyring [root@test-173 ~]# ceph -s cluster: id: 3d90ed67-7f00-47ae-ae3f-dbdd837c607c health: HEALTH_OK services: mon: 3 daemons, quorum a,b,d mgr: a(active) mds: myfs-1/1/1 up {0=myfs-a=up:active}, 1 up:standby-replay osd: 4 osds: 3 up, 3 in data: pools: 3 pools, 300 pgs objects: 170.7 k objects, 79 GiB usage: 240 GiB used, 1.2 TiB / 1.4 TiB avail pgs: 300 active+clean io: client: 5.8 KiB/s rd, 11 KiB/s wr, 2 op/s rd, 2 op/s wr
处理告警 ceph -s 里出现 1 mgr modules have recently crashed 这类告警时,先用 ceph crash ls-new 看是哪个组件,确认问题已经处理之后再归档,集群状态就会回到 HEALTH_OK:
1 2 3 4 [root@imwl-03 ~]# ceph crash ls-new ID ENTITY NEW 2023-03-29T04:33:49.298550Z_31fd79c2-e230-4ee1-b340-e9a7920a6e38 mgr.b * [root@imwl-03 ~]# ceph crash archive-all
删除 osd 换盘 换盘的关键是等数据真的迁完再动手 。ceph osd out(或 crush reweight 0)只是发起 backfill,视数据量要几分钟到几小时;这中间就把 OSD 删掉的话,PG 会直接进入 degraded/recovery,靠剩下的副本硬撑——那恰恰是先 reweight 想避免的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ceph osd tree ceph osd out 2 ceph -s ceph osd safe-to-destroy osd.2 kubectl delete deploy -n rook-ceph rook-ceph-osd-2 ceph osd purge 2 --yes-i-really-mean-it DISK="/dev/vdc" sgdisk --zap-all $DISK dd if =/dev/zero of="$DISK " bs=1M count=100 oflag=direct,dsync
升级 升级前先确认 pod 全部 Running、ceph status 为健康状态,再声明好命名空间变量:
1 2 3 4 5 6 7 8 export ROOK_SYSTEM_NAMESPACE="rook-ceph" export ROOK_NAMESPACE="rook-ceph" kubectl -n $ROOK_NAMESPACE get pods TOOLS_POD=$(kubectl -n $ROOK_NAMESPACE get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}' ) kubectl -n $ROOK_NAMESPACE exec -it $TOOLS_POD -- ceph status
小版本升级 以 1.5.0 升级到 1.5.1 为例,更新公共资源后替换 operator 镜像即可:
1 2 3 4 git clone --single-branch --branch v1.5.1 https://github.com/rook/rook.git cd $YOUR_ROOK_REPO /cluster/examples/kubernetes/ceph/kubectl apply -f common.yaml -f crds.yaml kubectl -n rook-ceph set image deploy/rook-ceph-operator rook-ceph-operator=rook/ceph:v1.5.1
跨版本升级 1.4.0 升级到 1.5.1 属于跨版本,步骤相同但要观察每个 deployment 的滚动情况:
1 2 3 4 5 6 7 8 9 10 11 git clone --single-branch --branch v1.5.1 https://github.com/rook/rook.git cd rook/cluster/examples/kubernetes/cephkubectl apply -f common.yaml -f crds.yaml kubectl -n rook-ceph set image deploy/rook-ceph-operator rook-ceph-operator=rook/ceph:v1.5.1 watch --exec kubectl -n $ROOK_NAMESPACE get deployments -l rook_cluster=$ROOK_NAMESPACE -o jsonpath='{range .items[*]}{.metadata.name}{" \treq/upd/avl: "}{.spec.replicas}{"/"}{.status.updatedReplicas}{"/"}{.status.readyReplicas}{" \trook-version="}{.metadata.labels.rook-version}{"\n"}{end}' kubectl -n $ROOK_NAMESPACE get deployment -l rook_cluster=$ROOK_NAMESPACE -o jsonpath='{range .items[*]}{"rook-version="}{.metadata.labels.rook-version}{"\n"}{end}' | sort | uniq
rook 升级完再升 ceph,改 CephCluster 里的镜像版本,由 operator 逐个替换守护进程:
1 2 3 4 5 6 7 8 9 NEW_CEPH_IMAGE='ceph/ceph:v15.2.5' CLUSTER_NAME=rook-ceph kubectl -n rook-ceph patch CephCluster rook-ceph --type =merge -p "{\"spec\": {\"cephVersion\": {\"image\": \"$NEW_CEPH_IMAGE \"}}}" watch --exec kubectl -n $ROOK_NAMESPACE get deployments -l rook_cluster=$ROOK_NAMESPACE -o jsonpath='{range .items[*]}{.metadata.name}{" \treq/upd/avl: "}{.spec.replicas}{"/"}{.status.updatedReplicas}{"/"}{.status.readyReplicas}{" \tceph-version="}{.metadata.labels.ceph-version}{"\n"}{end}' kubectl -n $ROOK_NAMESPACE get deployment -l rook_cluster=$ROOK_NAMESPACE -o jsonpath='{range .items[*]}{"ceph-version="}{.metadata.labels.ceph-version}{"\n"}{end}' | sort | uniq
集群修复 误删集群,但没有清空磁盘和 /var/lib/rook 下的文件时,是可以恢复的。rook-ceph 里只有 monitor 和 osd 服务是有状态的,恢复集群也就是恢复这两个服务。
如果有 etcd 备份,可以整体回滚 namespace,参考 k8s 遇到的问题 里误删 namespace 的处理,但这种方式影响面大,不推荐。更常规的做法是恢复 mon 信息后重装集群。
从主机文件恢复 mon 信息 账号和 keyring 在节点的 /var/lib/rook 下:
1 2 3 4 5 6 7 8 [client.admin] key = <REDACTED_CEPH_KEY> caps mds = "allow *" caps mon = "allow *" caps osd = "allow *" caps mgr = "allow *"
endpoints 信息在同目录的配置文件里,注意 mon host 默认记录的不是主机 IP,需要按 ls /var/lib/rook 看出本机是哪个 mon,再一一对应改成 HOSTIP:
1 2 3 4 5 6 7 8 9 [global] fsid = 8 ca801df-f763-4682 -b23c-2 c6f4b71b392 mon initial members = a b c mon host = [v2:192.168 .2.133 :3300 ,v1:192.168 .2.133 :6789 ],[v2:192.168 .2.132 :3300 ,v1:192.168 .2.132 :6789 ],[v2:192.168 .2.131 :3300 ,v1:192.168 .2.131 :6789 ] [client.admin] keyring = /var/lib/rook/rook-ceph/client.admin.keyring
上面的 key 填回 secret 前需要 base64 编码:
从 k8s 中导出配置 如果 rook-ceph 还能通过 k8s 操作,直接把两份资源导出来改,比从主机文件拼更省事。
rook-ceph-mon-endpoints 这个 configmap 记录了 mon 的地址映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 data: csi-cluster-config-json: '[{"clusterID":"rook-ceph","monitors":["192.168.2.133:6789","192.168.2.132:6789","192.168.2.131:6789"],"namespace":""}]' data: a=192.168.2.133:6789,b=192.168.2.132:6789,c=192.168.2.131:6789 mapping: '{"node":{"a":{"Name":"192.168.2.133","Hostname":"192.168.2.133","Address":"192.168.2.133"},"b":{"Name":"192.168.2.132","Hostname":"192.168.2.132","Address":"192.168.2.132"},"c":{"Name":"192.168.2.131","Hostname":"192.168.2.131","Address":"192.168.2.131"}}}' maxMonId: "2" kind: ConfigMap metadata: finalizers: - ceph.rook.io/disaster-protection name: rook-ceph-mon-endpoints namespace: rook-ceph ownerReferences: null
rook-ceph-mon 这个 secret 记录了 fsid 与两个 key:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 data: ceph-secret: "<REDACTED_BASE64>" ceph-username: Y2xpZW50LmFkbWlu fsid: OGNhODAxZGYtZjc2My00NjgyLWIyM2MtMmM2ZjRiNzFiMzky mon-secret: "<REDACTED_BASE64>" kind: Secret metadata: finalizers: - ceph.rook.io/disaster-protection name: rook-ceph-mon namespace: rook-ceph ownerReferences: null type: kubernetes.io/rook
重建集群 先建 operator,等它正常运行后把上面两份原有配置塞回去,再执行 cluster.yaml、toolbox.yaml 等后续文件:
1 2 3 4 kubectl create -f crds.yaml -f common.yaml -f operator.yaml kubectl create -f rook-ceph-mon.yaml -f rook-ceph-mon-endpoints.yaml
pod 全部起来后即恢复完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [root@imwl-02 ~]# kubectl get pod -n rook-ceph NAME READY STATUS RESTARTS AGE csi-cephfsplugin-nxjj5 2/2 Running 2 (63m ago) 82m csi-cephfsplugin-provisioner-868f98bbcb-9gfwn 5/5 Running 5 (63m ago) 82m csi-cephfsplugin-provisioner-868f98bbcb-pvgts 5/5 Running 5 (46m ago) 82m csi-cephfsplugin-wjc72 2/2 Running 2 (46m ago) 82m csi-cephfsplugin-zl9lk 2/2 Running 2 (45m ago) 82m csi-rbdplugin-5fvhr 2/2 Running 2 (63m ago) 82m csi-rbdplugin-gk45g 2/2 Running 2 (45m ago) 82m csi-rbdplugin-provisioner-79b79874d7-jghgm 5/5 Running 5 (63m ago) 82m csi-rbdplugin-provisioner-79b79874d7-qt4qm 5/5 Running 5 (46m ago) 82m csi-rbdplugin-w25hr 2/2 Running 2 (46m ago) 82m rook-ceph-crashcollector-192.168.2.131-584b844855-zq9mn 1/1 Running 0 32m rook-ceph-crashcollector-192.168.2.132-7ccb4697f4-z6grd 1/1 Running 0 32m rook-ceph-crashcollector-192.168.2.133-768cd898bc-fjqm7 1/1 Running 1 (46m ago) 81m rook-ceph-mds-myfs-a-6bf69975cb-jgh46 2/2 Running 0 32m rook-ceph-mds-myfs-b-74585c95bf-gk6dm 2/2 Running 0 32m rook-ceph-mgr-a-d64bc6795-l9n6r 3/3 Running 3 (45m ago) 82m rook-ceph-mgr-b-697b8869fb-wbjmv 3/3 Running 3 (46m ago) 82m rook-ceph-mon-a-684f5c6f6-sv9g6 2/2 Running 2 (46m ago) 82m rook-ceph-mon-b-85769cb8cb-n9r48 2/2 Running 2 (63m ago) 82m rook-ceph-mon-c-78fc799bd-nxm26 2/2 Running 2 (45m ago) 82m rook-ceph-operator-554d898d5-t5cf8 1/1 Running 2 (45m ago) 84m rook-ceph-osd-0-f766dd974-jlh22 2/2 Running 2 (45m ago) 81m rook-ceph-osd-1-684d5f6866-pg99x 2/2 Running 2 (63m ago) 81m rook-ceph-osd-2-64d6649f85-f8lmg 2/2 Running 2 (46m ago) 81m rook-ceph-osd-3-7676d96b5c-8bld7 2/2 Running 2 (45m ago) 81m rook-ceph-osd-4-b8b7777c-mk6rw 2/2 Running 2 (46m ago) 81m rook-ceph-osd-prepare-192.168.2.131-zh642 0/1 Completed 0 45m rook-ceph-osd-prepare-192.168.2.132-l74x9 0/1 Completed 0 45m rook-ceph-osd-prepare-192.168.2.133-nfcnj 0/1 Completed 0 45m rook-ceph-tools-8558bfc844-kxlkh 1/1 Running 1 (45m ago) 55m
原来挂载的数据也回来了:
1 2 3 4 [root@imwl-02 ~]# df -h |grep /mnt 192.168.2.133:6789,192.168.2.132:6789,192.168.2.131:6789:/ 471G 0 471G 0% /mnt/ceph [root@imwl-02 ~]# ls /mnt/ceph/ frontend logs pods upload
卸载 彻底卸载分两步:先删干净 k8s 里的资源,再把主机上的磁盘和残留目录清掉,否则下次部署会识别不到设备。
清理 k8s 资源 删除 ceph 集群前,请先清理使用了它的 pod 和 PVC,然后按建立时的相反顺序删除。
顺序上有一点必须守住:CephCluster CR 要在 operator 还活着的时候删掉 。cephcluster、cephblockpool、cephfilesystem 以及 rook-ceph-mon secret 上都带着 ceph.rook.io/disaster-protection 之类的 finalizer,只有 operator 在跑才会去摘除它们。先删 operator 再删 CRD 和 namespace,必然卡住——下面”namespace 卡在 Terminating”那一节,其实就是这个顺序错误的直接后果;手工强删 finalizer 绕过去,还会在磁盘上留下没清理的 OSD 数据。
1 2 3 4 5 6 7 8 9 10 11 12 kubectl delete -n rook-ceph cephfilesystem test kubectl delete -n rook-ceph cephblockpool replicapool kubectl delete storageclass rook-ceph-block rook-cephfs kubectl -n rook-ceph delete cephcluster rook-ceph kubectl -n rook-ceph get cephcluster kubectl delete -f toolbox.yaml kubectl delete -f operator.yaml kubectl delete -f common.yaml kubectl delete -f crds.yaml
清理主机磁盘 所有做过 osd 的主机都要清一遍,否则磁盘上的 ceph_bluestore 残留会让下次部署识别不到设备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 rm -rf /var/lib/rook/* DISK="/dev/vdc" sgdisk --zap-all $DISK dd if =/dev/zero of="$DISK " bs=1M count=100 oflag=direct,dsyncls /dev/mapper/ceph-* | xargs -I% -- dmsetup remove %rm -rf /dev/ceph-*
namespace 卡在 Terminating rook-ceph 这个 ns 一直是 Terminating,通常是还有带 finalizer 的自定义资源没删掉:
1 2 3 4 5 6 [root@test-173 ~]# kubectl get ns |grep Terminating rook-ceph Terminating 13h [root@test-249 ~]# kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n rook-ceph NAME ACTIVEMDS AGE cephfilesystem.ceph.rook.io/myfs 1 34m
把这些资源的 finalizers 清空即可,涉及的对象按实际存在的挑着执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 kubectl patch crds cephfilesystems.ceph.rook.io -p '{"metadata":{"finalizers": []}}' --type =merge kubectl patch -n rook-ceph cephfilesystem.ceph.rook.io myfs -p '{"metadata":{"finalizers": []}}' --type =merge kubectl patch -n rook-ceph secret rook-ceph-mon -p '{"metadata":{"finalizers": []}}' --type =merge kubectl patch -n rook-ceph configmap rook-ceph-mon-endpoints -p '{"metadata":{"finalizers": []}}' --type =merge kubectl patch -n rook-ceph cephcluster.ceph.rook.io rook-ceph -p '{"metadata":{"finalizers": []}}' --type =merge kubectl patch -n rook-ceph cephblockpool.ceph.rook.io/replicapool -p '{"metadata":{"finalizers": []}}' --type =merge kubectl patch -n rook-ceph cephobjectstore.ceph.rook.io/my-store -p '{"metadata":{"finalizers": []}}' --type =merge kubectl patch -n rook-ceph cephobjectstoreuser.ceph.rook.io/my-user -p '{"metadata":{"finalizers": []}}' --type =merge
处理完 ns 就消失了:
1 [root@test-249 ~]# kubectl get ns |grep Terminating
也可以先列出残留对象,再逐个 kubectl edit 把 finalizers: [] 改掉:
1 kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n rook-ceph
常见问题 下面几个是部署阶段最容易踩到的,日志关键字都在 rook-ceph-operator 和 rook-ceph-osd-prepare 里。
主机缺少 lvm2 operator 日志里报找不到 lvm 二进制,osd 无法创建,在对应主机上装 lvm2 即可:
1 2 3 4 2021-03-27 03:11:19.763338 D | exec: Running command: nsenter --mount=/rootfs/proc/1/ns/mnt -- /sbin/lvm --help 2021-03-27 03:11:19.764945 D | cephosd: failed to call nsenter. failed to execute nsenter. output: nsenter: failed to execute /sbin/lvm: No such file or directory: exit status 127 2021-03-27 03:11:19.764972 D | cephosd: failed to lookup binary path "/rootfs/sbin/lvm" on the host rootfs. stat /rootfs/sbin/lvm: no such file or directory binary "lvm" does not exist on the host, make sure lvm2 package is installed: binary "lvm" does not exist on the host
1 sudo yum install -y lvm2
servicemonitors 权限不足 开了 monitoring 但没建对应 RBAC,mgr 起不来:
1 2 W0908 03:00:16.166815 1 client_config.go:617] Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work. 2022-09-08 03:00:16.176356 E | ceph-cluster-controller: failed to reconcile CephCluster "rook-ceph/rook-ceph". failed to reconcile cluster "rook-ceph": failed to configure local ceph cluster: failed to create cluster: failed to start ceph mgr: failed to enable mgr services: failed to enable service monitor: service monitor could not be enabled: failed to retrieve servicemonitor. servicemonitors.monitoring.coreos.com "rook-ceph-mgr" is forbidden: User "system:serviceaccount:rook-ceph:rook-ceph-system" cannot get resource "servicemonitors" in API group "monitoring.coreos.com" in the namespace "rook-ceph"
1 kubectl apply -f monitoring/rbac.yaml
osd prepare 完成但没有 osd rook-ceph-osd-prepare 的 job 是 Completed,但 osd 没建出来,日志里能看到设备被跳过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2023-09-22 10:08:54.554152 I | cephosd: skipping device "sda1" with mountpoint "boot" 2023-09-22 10:08:54.554156 I | cephosd: skipping device "sda2" because it contains a filesystem "LVM2_member" 2023-09-22 10:08:54.554158 I | cephosd: skipping device "dm-0" with mountpoint "rootfs" 2023-09-22 10:08:54.554160 I | cephosd: skipping device "nvme0n1" because it contains a filesystem "ceph_bluestore" 2023-09-22 10:08:54.561332 I | cephosd: configuring osd devices: {"Entries":{}} 2023-09-22 10:08:54.561363 I | cephosd: no new devices to configure. returning devices already configured with ceph-volume. 2023-09-22 10:08:54.561509 D | exec: Running command: stdbuf -oL ceph-volume --log-path /tmp/ceph-log lvm list --format json 2023-09-22 10:08:54.856867 D | cephosd: {} 2023-09-22 10:08:54.856895 I | cephosd: 0 ceph-volume lvm osd devices configured on this node 2023-09-22 10:08:54.856912 D | exec: Running command: stdbuf -oL ceph-volume --log-path /tmp/ceph-log raw list --format json 2023-09-22 10:08:55.280026 D | cephosd: { "ae6b3a5c-2425-4433-b185-06ce0557ccdc": { "ceph_fsid": "676d13b0-9985-4a3c-a989-7b299aff885a", "device": "/dev/nvme0n1", "osd_id": 1, "osd_uuid": "ae6b3a5c-2425-4433-b185-06ce0557ccdc", "type": "bluestore" } }
关键是 skipping device "nvme0n1" because it contains a filesystem "ceph_bluestore",磁盘上有上一次部署的残留,按上文「清理主机磁盘」重新格式化这块盘即可。