# Rook & Ceph 简介

原文：[The Ultimate Rook and Ceph Survival Guide](https://www.cloudops.com/2019/05/the-ultimate-rook-and-ceph-survival-guide/)

在容器世界中，无状态是一个核心原则，然而我们始终需要保存数据，并提供给他人进行访问。所以就需要一个方案用于保持数据，以备重启之需。

在 Kubernetes 中，PVC 是管理有状态应用的一个推荐方案。有了 PVC 的帮助，Pod 可以申请并连接到存储卷，这些存储卷在 Pod 生命周期结束之后，还能独立存在。

PVC 在存储方面让开发和运维的职责得以分离。运维人员负责供应存储，而开发人员则可以在不知后端细节的情况下，申请使用这些存储卷。

PVC 由一系列组件构成：

[PVC](https://kubernetes.io/docs/concepts/storage/volumes/#persistentvolumeclaim)：是 Pod 对存储的请求。PVC 会被 Pod 动态加载成为一个存储卷。

[PV](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)，可以由运维手工分配，也可以使用 `StorageClass` 动态分配。PV 受 Kubernetes 管理，但并不与特定的 Pod 直接绑定。

[StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/)：由管理员创建，可以用来动态的创建存储卷和 PV。

物理存储：实际连接和加载的存储卷。

分布式存储系统是一个有效的解决有状态工作负载高可用问题的方案。Ceph 就是一个分布式存储系统，近年来其影响主键扩大。Rook 是一个编排器，能够支持包括 Ceph 在内的多种存储方案。Rook 简化了 Ceph 在 Kubernetes 集群中的部署过程。

在生产环境中使用 Rook + Ceph 组合的用户正在日益增加，尤其是自建数据中心的用户，[CENGN](https://www.cengn.ca/)、Gini、GPR 等很多组织都在进行评估。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1746353338466/6c6374a7-703d-43d5-b52b-d5c8313dbfa5.png align="center")

## Ceph 是什么

Ceph 是一个分布式存储系统，具备大规模、高性能、无单点失败的特点。Ceph 是一个软件定义的系统，也就是说他可以运行在任何符合其要求的硬件之上。

Ceph 包括多个组件：

**Ceph Monitors(MON)**：负责生成集群票选机制。所有的集群节点都会向 Mon 进行汇报，并在每次状态变更时进行共享信息。

**Ceph Object Store Devices(OSD)**：负责在本地文件系统保存对象，并通过网络提供访问。通常 OSD 守护进程会绑定在集群的一个物理盘上，Ceph 客户端直接和 OSD 打交道。

**Ceph Manager(MGR)**：提供额外的监控和界面给外部的监管系统使用。

**Reliable Autonomic Distributed Object Stores**：Ceph 存储集群的核心。这一层用于为存储数据提供一致性保障，执行数据复制、故障检测以及恢复等任务。

为了在 Ceph 上进行读写，客户端首先要联系 MON，获取最新的集群地图，其中包含了集群拓扑以及数据存储位置的信息。Ceph 客户端使用集群地图来获知需要交互的 OSD，从而和特定 OSD 建立联系。

## Rook 是什么

Rook 是一个可以提供 Ceph 集群管理能力的 [Operator](https://coreos.com/blog/introducing-operators.html)。Rook 使用 CRD 一个控制器来对 Ceph 之类的资源进行部署和管理。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1746353306075/80296572-eb0b-4416-a40b-e0fb027baafc.png align="center")

Rook 包含多个组件：

**Rook Operator**：Rook 的核心组件，Rook Operator 是一个简单的容器，自动启动存储集群，并监控存储守护进程，来确保存储集群的健康。

**Rook Agent**：在每个存储节点上运行，并配置一个 FlexVolume 插件，和 Kubernetes 的存储卷控制框架进行集成。Agent 处理所有的存储操作，例如挂接网络存储设备、在主机上加载存储卷以及格式化文件系统等。

**Rook Discovers**：检测挂接到存储节点上的存储设备。

Rook 还会用 Kubernetes Pod 的形式，部署 Ceph 的 MON、OSD 以及 MGR 守护进程。

Rook Operator 让用户可以通过 CRD 的是用来创建和管理存储集群。每种资源都定义了自己的 CRD.

**Rook** [**Cluster**](https://rook.github.io/docs/rook/master/cluster-crd.html)：提供了对存储机群的配置能力，用来提供块存储、对象存储以及共享文件系统。每个集群都有多个 Pool。

[**Pool**](https://rook.github.io/docs/rook/master/pool-crd.html)：为块存储提供支持。Pool 也是给文件和对象存储提供内部支持。

[**Object Store**](https://rook.github.io/docs/rook/master/object-store-crd.html)：用 S3 兼容接口开放存储服务。

[**File System**](https://rook.github.io/docs/rook/master/filesystem-crd.html)：为多个 Kubernetes Pod 提供共享存储。

## 在 Kubernetes 上部署 Rook

下面我们会在 Kubernetes 上分步骤部署 Rook，并在同一集群中作为客户端来使用其存储服务。Ceph 需要额外的驱动来保存数据，因此建议提供一组独立的存储节点。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1746353315308/e2dca868-491e-4653-ac49-ff7144f8629b.png align="center")

### 准备工作

* Helm
    
* Kubernetes（启用 RBAC）
    

### 磁盘配置

这里假设在存储节点上配有未格式化的磁盘。为了提供最佳性能，你可能需要在单独的设备上启用 WAL（本文就不深入讨论了）。

### 配置 FlexVolume（如果需要）

Rook agent 使用 [FlexVolume](https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md) 来和 Kubernetes 进行集成，从而进行存储操作。为了达成这一目标，Rook agent 要在每个节点部署 Rook FlexVolume。

在一些情况下，FlexVolume 的缺省目录是只读的，例如 Rancher 和 CoreOS。在这种情况下就需要配置 Kubelet 使用不同的可写入的目录了。

如果使用的是 Rancher Kubernetes Engine（RKE），可以用下面的方式配置 kubelet，然后使用 `rke up` 应用配置。

```yaml
kubelet:
  image: ""
  extra_args:
    volume-plugin-dir: /usr/libexec/kubernetes/kubelet-plugins/volume/exec
  extra_binds:
    - /usr/libexec/kubernetes/kubelet-plugins/volume/exec:/usr/libexec/kubernetes/kubelet-plugins/volume/exec
```

### 安装 Rook Operator

在 Helm 中加入 `Rook Charts`。

helm repo add rook-stable https://charts.rook.io/stable

安装 Rook Operator（当前版本为 v0.9.3）

helm install --name rook --namespace rook-ceph-system rook-stable/rook-ceph

$ kubectl get po -n rook-ceph-system -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE rook-ceph-agent-gwl8s 1/1 Running 0 35d 10.212.144.51 coo-r1-k8s-worker-st-01 rook-ceph-agent-lqkjl 1/1 Running 0 35d 10.212.144.52 coo-r1-k8s-worker-st-02 rook-ceph-agent-x66sw 1/1 Running 0 35d 10.212.144.53 coo-r1-k8s-worker-st-03 rook-ceph-operator-7d44ddfdcb-q5chh 1/1 Running 0 35d 10.244.8.3 coo-r1-k8s-worker-st-03 rook-discover-fmqrd 1/1 Running 0 35d 10.244.7.5 coo-r1-k8s-worker-st-01 rook-discover-jlsv9 1/1 Running 0 35d 10.244.6.3 coo-r1-k8s-worker-st-02 rook-discover-vt7mk 1/1 Running 0 35d 10.244.8.4 coo-r1-k8s-worker-st-03

Helm 会部署 Rook 的相关 Pod（rook-operator、rook-discover 以及 rook-agent），以及相关的 CRD。Discover Pod 会运行一个发现脚本，来查找挂接到 Kubernetes 存储节点上的本地存储设备。

请注意，`rook-ceph-system` 中的所有 Pod 都应该是 `Running` 或者 `Completed` 状态，不应存在 `restarts` 或 `error` 的情况。

### 创建受 Root 管理的 Ceph 集群

下一步就是创建 Ceph 集群。在 [`rook` 源码](https://github.com/rook/rook)中找到 `cluster/examples/kubernetes/ceph/cluster.yaml`，进行查看和修改。集群 CRD 中定义了存储集群的内容。下面的命令就能够启动一个 Rook 集群：

kubectl create -f cluster.yaml

要确认我们的 Rook 集群的工作状况，可以检查一下 `rook-ceph` 命名空间中的 Pod 运行情况：

$ kubectl get po -n rook-ceph -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE rook-ceph-mgr-a-bf78cdcb8-q4gpz 1/1 Running 0 35d 10.244.7.7 coo-r1-k8s-worker-st-01 rook-ceph-mon-a-755d985488-72kzh 1/1 Running 0 35d 10.244.7.6 coo-r1-k8s-worker-st-01 rook-ceph-mon-b-845c97f94b-h5jlp 1/1 Running 0 35d 10.244.6.4 coo-r1-k8s-worker-st-02 rook-ceph-mon-c-68b495d97d-m524q 1/1 Running 0 35d 10.244.8.7 coo-r1-k8s-worker-st-03 rook-ceph-osd-0-56b7b86b5b-kz882 1/1 Running 0 35d 10.244.7.9 coo-r1-k8s-worker-st-01 rook-ceph-osd-1-6d9558b6bd-xlkzf 1/1 Running 0 35d 10.244.6.6 coo-r1-k8s-worker-st-02 rook-ceph-osd-2-56bf4b6c64-2p9rp 1/1 Running 0 35d 10.244.8.9 coo-r1-k8s-worker-st-03 rook-ceph-osd-3-86ccf5d69f-xzjmz 1/1 Running 0 35d 10.244.7.10 coo-r1-k8s-worker-st-01 rook-ceph-osd-4-6f469fc877-bt799 1/1 Running 0 35d 10.244.6.7 coo-r1-k8s-worker-st-02 rook-ceph-osd-5-6549cdf949-qbvnh 1/1 Running 0 35d 10.244.7.11 coo-r1-k8s-worker-st-01 rook-ceph-osd-6-7f56d8cf95-qgd8p 1/1 Running 0 35d 10.244.6.8 coo-r1-k8s-worker-st-02 rook-ceph-osd-7-55b6c5c8df-dnp4p 1/1 Running 0 35d 10.244.8.11 coo-r1-k8s-worker-st-03 rook-ceph-osd-8-d6df7694-w2psw 1/1 Running 0 35d 10.244.8.10 coo-r1-k8s-worker-st-03 rook-ceph-osd-prepare-coo-r1-k8s-worker-st-01-zbs6m 0/2 Completed 0 35d 10.244.7.8 coo-r1-k8s-worker-st-01 rook-ceph-osd-prepare-coo-r1-k8s-worker-st-02-sr2dm 0/2 Completed 0 35d 10.244.6.5 coo-r1-k8s-worker-st-02 rook-ceph-osd-prepare-coo-r1-k8s-worker-st-03-zzqmq 0/2 Completed 0 35d 10.244.8.8 coo-r1-k8s-worker-st-03 rook-ceph-tools-cb5655595-vq4vj 1/1 Running 0 35d 10.212.144.53 coo-r1-k8s-worker-st-03

可以看到 `mon`、`osd-prepare` 已经部署。`rook-ceph-osd-prepare` 格式化了磁盘，准备了 OSD，并把 `osd` Pod 加入了集群。

Rook 还提供了一个 `toolkit` 容器，其中包含了全套的 Ceph 客户端，用于测试和排错，运行下列命令即可安装：

kubectl create -f toolkit.yaml

进入工具 Pod，就可以执行下面的内容了。

### 集群配置

设置 Ceph 组件的资源：

```yaml
mgr:
    limits:
        cpu: "500m"
        memory: "1024Mi"
    requests:
        cpu: "500m"
        memory: "1024Mi"
mon:
    limits:
        cpu: "1"
        memory: "1024Mi"
    requests:
        cpu: "500m"
        memory: "1024Mi"
osd:
    limits:
        cpu: "1"
        memory: "2048Mi"
    requests:
        cpu: "500m"
        memory: "1024Mi"
```

建议尽量为所有存储节点分配同样的 CPU、内存和磁盘。这样就可以使用 `deviceFilter` 了：

```yaml
storage:
  useAllNodes: true
  useAllDevices: false
  deviceFilter: sd[a-z]
```

这里使用正则表达式 `/dev/sd[a-z]` 进行设备匹配。

### 创建 Ceph 副本池以及 Kubernetes StorageClass

可以用 CRD 来定义 Pool。Rook 提供了两种机制来维持 OSD：

**副本**：缺省选项，每个对象都会根据 `spec.replicated.size` 在多个磁盘上进行复制。建议非生产环境至少 2 个副本，生产环境至少 3 个。

**Erasure Code**：是一种较为节约的方式。EC 把数据拆分 n 段（`spec.erasureCoded.dataChunks`），再加入 k 个代码段（`spec.erasureCoded.codingChunks`），用分布的方式把 n+k 段数据保存在磁盘上。这种情况下 Ceph 能够隔离 `k` 个 OSD 的损失。

```yaml
# pool with replication enabled
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicated-metadata-pool
  namespace: rook-ceph
spec:
  replicated:
    size: 2
---
# pool with EC enabled
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: ec-data-pool
  namespace: rook-ceph
spec:
  erasureCoded:
    dataChunks: 2
    codingChunks: 1
```

本文中使用副本的方式来保证数据冗余。

Kubernetes 环境里，StorageClass 是动态存储配置的核心。下面的例子定义了一个 Ceph 块存储的 StorageClass：

```yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: ceph.rook.io/block
parameters:
  # The replicated pool as the `blockPool` parameter
  blockPool: replicated-metadata-pool
  # The erasure coded pool must be set as the `dataBlockPool` parameter below.
  dataBlockPool: ec-data-pool
  clusterNamespace: rook-ceph
```

使用 `kubectl` 提交 `storageclass.yaml` 以及 `cluster.yaml`，就完成了 Ceph 副本和 StorageClass 的创建。

## 测试

使用上面创建的 `StorageClass`，新建一个 PVC，就可以完成测试了：

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  storageClassName: rook-ceph-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
```

检查新建的 PVC，看状态是不是会变成 `Bounded`：

$ kubectl get pvc NAMESPACE NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE default mysql-pv-claim Bound pvc-f1af6df6-474a-11e9-8360-02006e76001e 8Gi RWO rook-ceph-block 1m

## Ceph 常用命令

在工具箱 Pod 中，可以使用命令和 Ceph 集群进行交互。下面是一个例子。

### 查看集群状态

$ ceph status cluster: id: 62e69dc1-efb5-42d9-a7bc-1ea6cfbd467f health: HEALTH\_OK

services: mon: 3 daemons, quorum c,a,b mgr: a(active) osd: 9 osds: 9 up, 9 in

data: pools: 1 pools, 100 pgs objects: 236 objects, 406 MiB usage: 10 GiB used, 1.7 TiB / 1.8 TiB avail pgs: 100 active+clean

io: client: 38 KiB/s wr, 0 op/s rd, 3 op/s wr

### 健康情况

用来查看是否有物理损坏。

$ ceph health detail HEALTH\_OK

### 所有 OSD 的状态

$ ceph osd status +----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+ | id | host | used | avail | wr ops | wr data | rd ops | rd data | state | +----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+ | 0 | coo-r1-k8s-worker-st-01 | 1149M | 198G | 0 | 13.6k | 0 | 0 | exists,up | | 1 | coo-r1-k8s-worker-st-02 | 1157M | 198G | 0 | 0 | 0 | 0 | exists,up | | 2 | coo-r1-k8s-worker-st-03 | 1143M | 198G | 0 | 0 | 0 | 0 | exists,up | | 3 | coo-r1-k8s-worker-st-01 | 1128M | 198G | 0 | 0 | 0 | 0 | exists,up | | 4 | coo-r1-k8s-worker-st-02 | 1180M | 198G | 4 | 37.6k | 0 | 0 | exists,up | | 5 | coo-r1-k8s-worker-st-01 | 1169M | 198G | 1 | 12.0k | 0 | 0 | exists,up | | 6 | coo-r1-k8s-worker-st-02 | 1109M | 198G | 0 | 0 | 0 | 0 | exists,up | | 7 | coo-r1-k8s-worker-st-03 | 1160M | 198G | 0 | 1638 | 0 | 0 | exists,up | | 8 | coo-r1-k8s-worker-st-03 | 1143M | 198G | 0 | 3276 | 0 | 0 | exists,up | +----+-------------------------+-------+-------+--------+---------+--------+---------+-----------+

### Ceph Pool 详情

$ ceph osd pool ls detail pool 1 'replicapool' replicated size 3 min\_size 1 crush\_rule 1 object\_hash rjenkins pg\_num 100 pgp\_num 100 last\_change 37 flags hashpspool,selfmanaged\_snaps stripe\_width 0 application rbd removed\_snaps \[1~3\]

### 显示 Pool 和总体用量

$ rados df POOL\_NAME USED OBJECTS CLONES COPIES MISSING\_ON\_PRIMARY UNFOUND DEGRADED RD\_OPS RD WR\_OPS WR replicapool 1.4 GiB 575 0 1150 0 0 0 1224 2.4 MiB 1698291 84 GiB

total\_objects 575 total\_used 5.6 GiB total\_avail 294 GiB total\_space 300 GiB

## 重新安装

### 包括 RKE 的完全重新部署

> 下面的步骤会擦除数据，不建议在生产集群上使用。

Rook 有很多数据保存在本地存储节点，重新部署比较困难。如果使用的是 RKE，在 Worker 和 Master 节点上清除数据，然后重新安装 RKE。

删除所有 Docker、Rook 和 RKE 及其相关组件：

\# remove rke docker and everything sudo apt -y purge docker-ce  
&& sudo apt -y autoremove  
&& sudo rm -rf /var/lib/docker  
&& sudo rm -rf /opt/\*  
&& sudo rm -rf /var/lib/rook  
&& sudo rm -rf /var/lib/etcd  
&& sudo rm -rf /var/lib/cni  
&& sudo rm -rf /var/lib/containerd  
&& sudo rm -rf /var/lib/calico  
&& sudo rm -rf /var/lib/kubelet  
&& sudo rm -rf /var/lib/rancher

如果是一个运行中的服务器，完成这一命令之后建议重新启动，然后重复执行一次，最后再重新安装（包括 Docker）。

还需要格式化 Rook/Ceph 使用的磁盘。

$ sudo fdisk /dev/sdb Welcome to fdisk (util-linux 2.29.2). Changes will remain in memory only, until you decide to write them. Be careful before using the write command.

Device /dev/sdb already contains a LVM2\_member signature. The signature will be removed by a write command.

Device does not contain a recognized partition table. Created a new DOS disklabel with disk identifier 0xa24124a7.

Command (m for help):

简单的输入 `w`，然后回车，就会覆盖分区表，让磁盘恢复可用。这一动作完成之后也建议重新启动。

### 只重装 Rook

删除 `rook-ceph` 以及 `rook-ceph-system` 命名空间会造成很大麻烦。要关停 Ceph 集群：

* 删除 Rook 集群的相关资源（Pool、StorageClass、PVC 等等）
    
    kubectl delete -n rook-ceph cephblockpool replicapool kubectl delete storageclass rook-ceph-block
    
* 删除 cluster CRD 和 Helm release
    
    kubectl -n rook-ceph patch clusters.ceph.rook.io rook-ceph -p ‘{“metadata”:{“finalizers”: \[\]}}’ –type=merge kubectl -n rook-ceph delete cephcluster rook-ceph helm delete –purge rook kubectl delete namespace rook-ceph
    
* 连接到每个节点上删除 `/var/lib/rook` 或者 `dataDirHostPath` 指定的路径。
    

如果出现任何问题，可以参考 [Trouble shooting 页面](https://github.com/rook/rook/blob/master/Documentation/ceph-teardown.md#troubleshooting)

## 检测

### Physical Group 修复

Ceph 偶尔会报告 Physical Group 需要修复，可以在工具箱 Pod 中完成：

$ ceph health detail HEALTH\_ERR 1 pgs inconsistent; 2 scrub errors pg 0.6 is active+clean+inconsistent, acting \[0,1,2\] 2 scrub errors

上面的输出说明需要进行修复，执行下列命令：

ceph pg repair 0.6

这个命令会启动一个修复过程，几分钟之后，会恢复到 `HEALTH_OK` 状态。

### 修改副本数量

如果副本数量设置有误，可以在运行中的副本池中修改设置，在工具箱 Pod 中执行：

ceph osd pool set replicapool size 3 ceph osd pool set replicapool min\_size 3

### 修改 PG 数量

要修改副本池中的 PG 数，可以使用：

ceph osd pool set replicapool pg\_num 256 ceph osd pool set replicapool pgp\_num 256

## 参考

* https://github.com/rook/rook/blob/master/Documentation/ceph-toolbox.md
    
* http://docs.ceph.com/docs/mimic/rados/troubleshooting/troubleshooting-pg/
    
* http://docs.ceph.com/docs/jewel/rados/operations/placement-groups/#set-the-number-of-placement-groups
    
* http://accelazh.github.io/ceph/Ceph-Performance-Tuning-Checklist
    
* https://github.com/rook/rook/blob/master/Documentation/advanced-configuration.md
    
* https://github.com/rook/rook/blob/master/Documentation/common-issues.md
    
* http://docs.ceph.com/docs/giant/rados/troubleshooting/troubleshooting-osd/
