rook-ceph 部署与运维

本文记录 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 分为三大块:

  1. 对象存储:object
  2. 块设备存储:block
  3. 文件系统服务:file

Ceph 核心组件

一套 Ceph 集群由下面这些守护进程组成,排查问题时基本都是在和它们打交道。

OSD

Object Storage Device,主要功能包括存储数据,处理数据的复制、恢复、回补、平衡数据分布,并将一些相关数据提供给 Ceph Monitor

MON

monitorCeph 的监控器,主要功能是维护整个集群健康状态、提供一致性的决策,包含了 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 的核心,主要由 OSDMonitor 两部分构成。

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 上运行,它由两部分组成:

  1. Operator:由一些 CRD 和一个 All in one 镜像构成,包含启动和监控存储系统的所有功能。
  2. Cluster:负责创建 CRD 对象,指定相关参数,包括 ceph 镜像、元数据持久化位置、磁盘位置、dashboard 等等。

部署 rook-ceph

官网地址 https://rook.io/ 。部署顺序是固定的:crds.yaml + common.yamloperator.yamlcluster.yamltoolbox.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.bak
wget 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 里的少数字段:dataDirHostPathnetwork.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"
# grpc 端口指标
ROOK_CSI_ENABLE_GRPC_METRICS: "true"

# 开启设备自动发现。注意 rook 读这类设置走 GetOperatorSetting(configmap, env, default):
# ConfigMap 里有这个键时优先于下面 Deployment 的 env,所以两处别配成相反的值。
# 它只影响 rook-discover DaemonSet;如果像下面那样显式列了 storage.nodes.devices,其实并不需要它。
ROOK_ENABLE_DISCOVERY_DAEMON: "true"

其余是按需调整的部分,默认值大多可以不动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 启用 cephfs
ROOK_CSI_ENABLE_CEPHFS: "true"
# 开启内核驱动替换 ceph-fuse
CSI_FORCE_CEPHFS_KERNEL_CLIENT: "true"
# 可以设置 NODE_AFFINITY 来指定 csi 部署的节点
# 我把 plugin 和 provisioner 分开了,具体调度方式看集群资源
CSI_PROVISIONER_NODE_AFFINITY: "app.rook.role=csi-provisioner"
CSI_PLUGIN_NODE_AFFINITY: "app.rook.plugin=csi"
# 修改 metrics 端口,可以不改,我因为集群网络是 host,为了避免端口冲突
# Configure CSI CSI Ceph FS grpc and liveness metrics port
CSI_CEPHFS_GRPC_METRICS_PORT: "9491"
CSI_CEPHFS_LIVENESS_METRICS_PORT: "9481"
# Configure CSI RBD grpc and liveness metrics port
CSI_RBD_GRPC_METRICS_PORT: "9490"
CSI_RBD_LIVENESS_METRICS_PORT: "9480"

以及 operator Deployment 容器部分的镜像与环境变量:

1
2
3
4
5
6
7
8
9
10
# 修改 rook 镜像,加速部署时间
image: registry.aliyuncs.com/google_containers/rook/ceph:v1.5.1
env:
# 限制 discover agent 调度到哪些节点(不是"指定哪些节点做存储"——
# 决定哪些节点起 OSD 的是 CephCluster 的 storage.useAllNodes / storage.nodes / placement.osd)
- 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:
# ceph 版本说明
# v13 is mimic, v14 is nautilus, and v15 is octopus.
cephVersion:
# 修改 ceph 镜像,加速部署时间
image: registry.aliyuncs.com/google_containers/ceph/ceph:v15.2.5
# 是否允许不支持的 ceph 版本
allowUnsupported: false
# 指定 rook 数据在节点的保存路径
dataDirHostPath: /var/lib/rook
# 升级时如果检查失败是否继续
skipUpgradeChecks: false
# 从 1.5 开始,mon 的数量必须是奇数
mon:
count: 3 # 必须奇数,原因见本节末尾
# 是否允许在单个节点上部署多个 mon pod
allowMultiplePerNode: false
mgr:
modules:
- name: pg_autoscaler
enabled: true
# 添加 rook
- name: rook
enabled: true
# 开启 dashboard,禁用 ssl,指定端口是 7000,也可以用默认的 https 配置,这里是为了 ingress 配置省事
dashboard:
urlPrefix: /test/ceph
enabled: true
port: 7000
ssl: false
# 开启 prometheusRule,可以在 ceph 集群起来后再修改
# kubectl edit cephclusters.ceph.rook.io -n rook-ceph
monitoring:
enabled: false
# 部署 PrometheusRule 的命名空间,默认此 CR 所在命名空间
rulesNamespace: rook-ceph
# 这里开了 host 网络:少一层封装、性能更好,代价是 mon/osd 直接暴露在主机网段上,生产要靠防火墙收口。
# 注意这个字段建集群之后极难变更,一开始就要定好。
network:
provider: host
# 开启 crash collector,在每个运行了 Ceph 守护进程的节点上创建 crash collector pod
crashCollector:
disable: false

# ...(中间字段省略)
storage: # cluster level storage configuration and selection
useAllNodes: false
useAllDevices: false
# ...(中间字段省略)
nodes:
- name: "192.168.2.231"
devices: # specific devices to use for storage can be specified for each node
- name: "sdb"
- name: "sdc"
- name: "192.168.2.232"
devices: # specific devices to use for storage can be specified for each node
- name: "sdb"
- name: "192.168.2.233"
devices: # specific devices to use for storage can be specified for each node
- 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        # 看每个池当前/建议的 pg_num
ceph osd pool set <pool> pg_autoscale_mode off # 要手工控制才关掉

需要注意 PG 数量变化会触发数据重平衡,所以给一个离目标值不太远的起始值能省掉一轮大规模 backfill。经验公式是 OSD 数 × 100 / size,再向上取到 2 的幂。

1
kubectl apply -f cluster.yaml

部署 toolbox

操作系统上直接使用 ceph 命令需要额外安装 ceph 的包,某些操作系统还需要编译安装,所以更方便的做法是用 rook-ceph-tools 这个工具箱 pod 来操作 ceph。默认清单见 https://github.com/rook/rook/blob/master/deploy/examples/toolbox.yaml ,一般要做这几处修改:

  • Deployment 改成 DaemonSet,让每个节点都有一个 toolbox;不改也可以,那就固定在某个节点上操作。
  • 镜像换成与集群版本对应的 ceph 镜像。
  • securityContext 里给到 privileged: truerunAsUser: 0,否则 rbd mapmount 这类操作没有权限。
  • 额外挂载 /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 # namespace:cluster
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"

# create a ceph config file in its default location so ceph/rados tools can be used
# without specifying any arguments
write_endpoints() {
endpoints=$(cat ${MON_CONFIG})

# filter out the mon names
# external cluster can have numbers or hyphens in mon names, handling them in regex
# shellcheck disable=SC2001
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 the endpoints config file and update if the mon endpoints ever change
watch_endpoints() {
# get the timestamp for the target of the soft link
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
}

# read the secret from an env var (for backward compatibility), or from the secret file
ceph_secret=${ROOK_CEPH_SECRET}
if [[ "$ceph_secret" == "" ]]; then
ceph_secret=$(cat /var/lib/rook-ceph-mon/secret.keyring)
fi

# create the keyring file
cat <<EOF > ${KEYRING_FILE}
[${ROOK_CEPH_USERNAME}]
key = ${ceph_secret}
EOF

# write the initial config file
write_endpoints

# continuously update the mon endpoints if they fail over
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
# if hostNetwork: false, the "rbd map" command hangs, see https://github.com/rook/rook/issues/2021
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 # namespace:cluster
labels:
app: rook-ceph-mgr
rook_cluster: rook-ceph # namespace:cluster
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.txt
ceph dashboard ac-user-set-password admin -i /tmp/pass.txt

登录后如图所示:

ceph-dashboard

集群就绪之后,块存储、共享文件存储、对象存储三种用法见 rook-ceph 存储使用方法

用 toolbox 排查与运维

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 detailceph osd treeceph 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 内核上只能留 layeringrbd mapRBD 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 # 初次使用需要格式化,复用已有的 image 不要做这一步
[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

用完按相反顺序卸载,先 umountrbd 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

# Detect the mon endpoints and the user secret for the connection
[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}')

# Mount the filesystem
[root@test-62 /]# mount -t ceph -o mds_namespace=myfs,name=admin,secret=$my_secret $mon_endpoints:/ /tmp/registry
# 展开后等价于
# mount -t ceph -o mds_namespace=myfs,name=admin,secret=<REDACTED_CEPH_KEY> 172.20.19.62:6789,172.20.19.61:6789,172.20.19.63:6789:/ /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 /]# cat /etc/ceph/ceph.conf
[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 /]# cat /etc/ceph/keyring
[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 osd crush reweight osd.2 0.0

# 反复确认,直到所有 PG 都是 active+clean 才往下走
ceph -s
ceph osd safe-to-destroy osd.2

kubectl delete deploy -n rook-ceph rook-ceph-osd-2

# purge 一步顶掉 crush remove + auth del + osd rm
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 全部 Runningceph status 为健康状态,再声明好命名空间变量:

1
2
3
4
5
6
7
8
# Parameterize the environment
export ROOK_SYSTEM_NAMESPACE="rook-ceph"
export ROOK_NAMESPACE="rook-ceph"

kubectl -n $ROOK_NAMESPACE get pods # pod 都是 running 状态

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 # ceph 集群健康

小版本升级

以 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/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

# 观察升级
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}'

# 查看 ceph 集群是否正常
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
# cat /var/lib/rook/rook-ceph/client.admin.keyring

[client.admin] # 使用的用户 client.admin
key = <REDACTED_CEPH_KEY> # client.admin 的 keyring
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
# cat /var/lib/rook/rook-ceph/rook-ceph.config

[global]
fsid = 8ca801df-f763-4682-b23c-2c6f4b71b392 # 集群 id
mon initial members = a b c # mon 个数,为 3
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 编码:

1
2
# echo -n "<REDACTED_CEPH_KEY>" | base64 -i -
<REDACTED_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
# kubectl get configmaps -n rook-ceph rook-ceph-mon-endpoints -o yaml > rook-ceph-mon-endpoints.yaml # 修改后文件如下

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" # 我有 3 个 mon,这里就是 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
# kubectl get secrets -n rook-ceph rook-ceph-mon -o yaml > rook-ceph-mon.yaml # 修改后如下

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.yamltoolbox.yaml 等后续文件:

1
2
3
4
kubectl create -f crds.yaml -f common.yaml -f operator.yaml

# 等 operator 正常运行,再恢复原来的配置信息
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 还活着的时候删掉cephclustercephblockpoolcephfilesystem 以及 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

# 关键一步:删 CephCluster CR,并确认它真的消失了
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/*  # 按 cluster.yaml 配置修改
DISK="/dev/vdc"
# Zap the disk to a fresh, usable state (zap-all is important, b/c MBR has to be clean)
# You will have to run this step for all disks.
sgdisk --zap-all $DISK
# hdd 用以下命令
dd if=/dev/zero of="$DISK" bs=1M count=100 oflag=direct,dsync
# ssd 用以下命令
# blkdiscard $DISK

# These steps only have to be run once on each node
# If rook sets up osds using ceph-volume, teardown leaves some devices mapped that lock the disks.
# 15 以后的版本默认不需要执行下面的命令
ls /dev/mapper/ceph-* | xargs -I% -- dmsetup remove %
# ceph-volume setup can leave ceph-<UUID> directories in /dev (unnecessary clutter)
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 editfinalizers: [] 改掉:

1
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -n rook-ceph

常见问题

下面几个是部署阶段最容易踩到的,日志关键字都在 rook-ceph-operatorrook-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",磁盘上有上一次部署的残留,按上文「清理主机磁盘」重新格式化这块盘即可。