1
2
3
4
5
作者:李晓辉

微信联系:lxh_chat

联系邮箱: 939958092@qq.com
角色主机名IP地址操作系统版本
K8sk8s-master192.168.8.3Ubuntu 24.041.33.0
Cephceph.xiaohui.cn192.168.8.201Rocky 9.4Squid

随着容器化技术的普及,越来越多的企业和开发者选择将应用程序和服务迁移到容器环境中。容器带来了灵活性和便捷性,但也使得数据存储和管理变得更加复杂。传统的存储方式已经不再适用于这些动态、快速变化的环境。为了确保容器中的应用能够持久化数据,开发者需要借助高效、可扩展的持久化存储解决方案。

在众多的持久化存储解决方案中,Ceph以其高度的灵活性和扩展性脱颖而出。无论是对象存储、块存储,还是文件存储,Ceph都能够提供全面支持。它不仅能满足大规模分布式存储的需求,还能与Kubernetes等容器编排平台无缝集成,成为容器化应用的理想选择。

为了让Kubernetes环境中的容器能够使用Ceph存储,Ceph CSI(容器存储接口)插件提供了一种简单而有效的方式。通过这个插件,Kubernetes集群中的应用可以轻松地将Ceph存储挂载为持久化卷,从而确保数据的安全和持久性,不受容器生命周期的影响。

今天我们就来聊聊,怎么把 Ceph 存储和 Kubernetes 搭配起来,利用 Ceph CSI 插件给你的容器应用提供超强的持久化存储支持。准备好了吗?我们一起从头开始搞起!

创建Ceph池

创建 kubernetes 池,并初始化 RBD 以支持 Ceph CSI。

1
2
[root@ceph ~]# ceph osd pool create kubernetes
pool 'kubernetes' created
1
rbd pool init kubernetes

设置 Ceph 客户端身份验证

创建一个新的 Ceph 客户端身份,并生成客户端密钥。

1
ceph auth get-or-create client.kubernetes mon 'profile rbd' osd 'profile rbd pool=kubernetes' mgr 'profile rbd pool=kubernetes' > /etc/ceph/ceph.client.kubernetes.keyring

生成 ceph-csi ConfigMap

生成 ceph-csi-config ConfigMap,包含 Ceph 集群的 fsid 和 monitor 地址。

先查一下fsid和mon主机的IP和端口

1
2
3
4
5
6
7
8
9
[root@ceph ~]# ceph mon dump
epoch 1
fsid 2d97dbbc-2fd1-11f0-ac32-000c29ac059a
last_changed 2025-05-13T08:08:29.634469+0000
created 2025-05-13T08:08:29.634469+0000
min_mon_release 19 (squid)
election_strategy: 1
0: [v2:192.168.8.201:3300/0,v1:192.168.8.201:6789/0] mon.ceph
dumped monmap epoch 1

生成一个类似于以下示例的 csi-config-map.yaml 文件,将fsid 替换为 “clusterID”,将 monitor 地址替换为 “monitors”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat <<EOF > csi-config-map.yaml
---
apiVersion: v1
kind: ConfigMap
data:
config.json: |-
[
{
"clusterID": "2d97dbbc-2fd1-11f0-ac32-000c29ac059a",
"monitors": [
"192.168.8.201:3300"
]
}
]
metadata:
name: ceph-csi-config
EOF
1
kubectl apply -f csi-config-map.yaml

另外,如果未启用 KMS,创建一个空的 KMS ConfigMap。如果你没有启用 KMS(即不需要加密功能),你仍然需要创建这个 ConfigMap,但是你可以将其内容保持为空。

1
2
3
4
5
6
7
8
9
10
cat <<EOF > csi-kms-config-map.yaml
---
apiVersion: v1
kind: ConfigMap
data:
config.json: |-
{}
metadata:
name: ceph-csi-encryption-kms-config
EOF

生成后,将新的 ConfigMap 对象存储在 Kubernetes 中:

1
kubectl apply -f csi-kms-config-map.yaml

ceph.conf 配置(包含认证设置)和密钥(client.kubernetes)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat <<EOF > ceph-config-map.yaml
---
apiVersion: v1
kind: ConfigMap
data:
ceph.conf: |
[global]
auth_cluster_required = cephx
auth_service_required = cephx
auth_client_required = cephx
keyring: |
metadata:
name: ceph-config
EOF

生成后,将新的 ConfigMap 对象存储在 Kubernetes 中:

1
kubectl apply -f ceph-config-map.yaml
  1. 生成 ceph-csi cephx 密钥

为 Ceph-CSI 创建一个 Secret,以便与 Ceph 集群进行通信,别忘了替换你的Ceph用户名和keyring。

1
2
3
4
5
6
7
8
9
10
11
cat <<EOF > csi-rbd-secret.yaml
---
apiVersion: v1
kind: Secret
metadata:
name: csi-rbd-secret
namespace: default
stringData:
userID: kubernetes
userKey: AQCxBCNo1sLgAxAABRtRfZafN44/43Q5DLyXnQ==
EOF

生成后,将新的 Secret 对象存储在 Kubernetes 中:

1
kubectl apply -f csi-rbd-secret.yaml
  1. 配置 ceph-csi 插件

创建必要的 ServiceAccount 和 RBAC 权限:

1
2
kubectl apply -f https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-provisioner-rbac.yaml
kubectl apply -f https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-nodeplugin-rbac.yaml
1
2
3
4
5
6
7
8
serviceaccount/rbd-csi-provisioner created
clusterrole.rbac.authorization.k8s.io/rbd-external-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/rbd-csi-provisioner-role created
role.rbac.authorization.k8s.io/rbd-external-provisioner-cfg created
rolebinding.rbac.authorization.k8s.io/rbd-csi-provisioner-role-cfg created
serviceaccount/rbd-csi-nodeplugin created
clusterrole.rbac.authorization.k8s.io/rbd-csi-nodeplugin created
clusterrolebinding.rbac.authorization.k8s.io/rbd-csi-nodeplugin created

下载并应用 Ceph-CSI 的插件 YAML 文件:

1
2
kubectl apply -f https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin-provisioner.yaml
kubectl apply -f https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin.yaml

创建 StorageClass

定义 StorageClass,并将其与 Ceph 的 kubernetes 池映射,别忘了改fsid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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: 2d97dbbc-2fd1-11f0-ac32-000c29ac059a
pool: kubernetes
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
csi.storage.k8s.io/controller-expand-secret-namespace: default
csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
csi.storage.k8s.io/node-stage-secret-namespace: default
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions:
- discard
EOF
1
kubectl apply -f csi-rbd-sc.yaml

创建 PersistentVolumeClaim

PersistentVolumeClaim 就是用户向 Kubernetes 提出的一个存储资源请求。你可以把它理解为你向 Kubernetes 申请存储空间的一种方式。接着,PersistentVolumeClaim 会关联到一个 Pod 资源,Kubernetes 会帮你找到合适的 PersistentVolume,而这个卷就会由 Ceph 的块存储来提供支持。

这里有个小细节:你可以通过 volumeMode 来决定是挂载一个文件系统卷(默认方式),还是直接挂载一个原始的块设备卷。这样就能根据你的需求选择适合的存储方式了。

说到 ceph-csi,如果你把 volumeMode 设置为 Filesystem,那么就能支持两种访问模式:ReadWriteOnceReadOnlyMany。如果设置为 Block,那么就能支持更多访问模式:ReadWriteOnceReadWriteManyReadOnlyMany

举个例子,如果你想要创建一个基于块存储的 PersistentVolumeClaim,并利用之前创建的 ceph-csiStorageClass,那么你可以用下面的 YAML 配置文件来向 csi-rbd-sc 请求原始块存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF > raw-block-pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: raw-block-pvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Block
resources:
requests:
storage: 1Gi
storageClassName: csi-rbd-sc
EOF
1
kubectl apply -f raw-block-pvc.yaml

从下面来看,可以看到自动创建了pv

1
2
3
4
5
6
7
root@k8s-master:~# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-71864057-bfb0-4145-a758-cc3690673a34 1Gi RWO Delete Bound default/raw-block-pvc csi-rbd-sc <unset> 33s

root@k8s-master:~# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
raw-block-pvc Bound pvc-71864057-bfb0-4145-a758-cc3690673a34 1Gi RWO csi-rbd-sc <unset> 35s

将上述 PersistentVolumeClaim 作为原始块设备绑定到 Pod 资源的演示和示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat <<EOF > raw-block-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: raw-block-volume
spec:
containers:
- name: fc-container
image: centos
command: ["/bin/sh", "-c"]
args: ["tail -f /dev/null"]
volumeDevices:
- name: data
devicePath: /dev/xvda
volumes:
- name: data
persistentVolumeClaim:
claimName: raw-block-pvc
EOF
1
kubectl apply -f raw-block-pod.yaml

从pod的详情看,我们果然有一个/dev/xvda

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
root@k8s-master:~# kubectl get -f raw-block-pod.yaml
NAME READY STATUS RESTARTS AGE
raw-block-volume 1/1 Running 0 8s
root@k8s-master:~# kubectl describe -f raw-block-pod.yaml
Name: raw-block-volume
Namespace: default
Containers:
fc-container:
Devices:
/dev/xvda from data
Volumes:
data:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: raw-block-pvc
ReadOnly: false
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 89s default-scheduler Successfully assigned default/raw-block-volume to k8s-worker1
Normal SuccessfulAttachVolume 89s attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-71864057-bfb0-4145-a758-cc3690673a34"
Normal SuccessfulMountVolume 84s kubelet MapVolume.MapPodDevice succeeded for volume "pvc-71864057-bfb0-4145-a758-cc3690673a34" globalMapPath "/var/lib/kubelet/plugins/kubernetes.io/csi/volumeDevices/pvc-71864057-bfb0-4145-a758-cc3690673a34/dev"
Normal SuccessfulMountVolume 84s kubelet MapVolume.MapPodDevice succeeded for volume "pvc-71864057-bfb0-4145-a758-cc3690673a34" volumeMapPath "/var/lib/kubelet/pods/1a4be481-f6e4-4121-aebd-9b542efd0024/volumeDevices/kubernetes.io~csi"
Normal Pulling 84s kubelet Pulling image "centos"
Normal Pulled 83s kubelet Successfully pulled image "centos" in 794ms (794ms including waiting). Image size: 231268856 bytes.
Normal Created 83s kubelet Created container: fc-container
Normal Started 83s kubelet Started container fc-container

进入到pod中,看看是否有1GB的设备

1
2
3
4
5
6
7
8
9
10
root@k8s-master:~# kubectl exec -it raw-block-volume -- /bin/bash
[root@raw-block-volume /]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 1G 0 loop
sda 8:0 0 500G 0 disk
|-sda1 8:1 1M 0 part
|-sda2 8:2 0 2G 0 part
`-sda3 8:3 0 498G 0 part
sr0 11:0 1 1024M 0 rom
rbd0 251:0 0 1G 0 disk

如果你想要创建一个基于文件系统的 PersistentVolumeClaim,并且使用之前提到的 ceph-csiStorageClass,你可以使用下面的 YAML 配置文件。这样就能从 csi-rbd-sc StorageClass 请求一个由 RBD 映像支持的挂载文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF > pvc.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rbd-pvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 1Gi
storageClassName: csi-rbd-sc
EOF
1
kubectl apply -f pvc.yaml

ok,pvc成功绑定

1
2
3
root@k8s-master:~# kubectl get -f pvc.yaml
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
rbd-pvc Bound pvc-690042cc-652d-45a8-9e40-ff96a3f0bfdd 1Gi RWO csi-rbd-sc <unset> 4s

下面是一个示例,展示了如何将之前创建的 PersistentVolumeClaim 作为挂载的文件系统绑定到 Pod 资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat <<EOF > pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: csi-rbd-demo-pod
spec:
containers:
- name: web-server
image: nginx
volumeMounts:
- name: mypvc
mountPath: /var/lib/www/html
volumes:
- name: mypvc
persistentVolumeClaim:
claimName: rbd-pvc
readOnly: false
EOF
1
kubectl apply -f pod.yaml

pod成功运行

1
2
3
root@k8s-master:~# kubectl get -f pod.yaml
NAME READY STATUS RESTARTS AGE
csi-rbd-demo-pod 1/1 Running 0 12s

从pod详情可,我们已经成功挂载了mypvc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@k8s-master:~# kubectl describe -f pod.yaml
Name: csi-rbd-demo-pod
Namespace: default
Containers:
web-server:
Mounts:
/var/lib/www/html from mypvc (rw)
Volumes:
mypvc:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: rbd-pvc
ReadOnly: false
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 34s default-scheduler Successfully assigned default/csi-rbd-demo-pod to k8s-worker1
Normal SuccessfulAttachVolume 34s attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-690042cc-652d-45a8-9e40-ff96a3f0bfdd"
Normal Pulling 25s kubelet Pulling image "nginx"
Normal Pulled 24s kubelet Successfully pulled image "nginx" in 930ms (930ms including waiting). Image size: 187706879 bytes.
Normal Created 24s kubelet Created container: web-server
Normal Started 24s kubelet Started container web-server

Ceph数据池有新数据了,我们成功将ceph和k8s集成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@ceph ~]# rados -p kubernetes ls
rbd_id.csi-vol-f665f273-908b-4bb0-b31c-4b5683a41ece
rbd_header.854f3b01601
rbd_header.854f5b8f22de
rbd_data.854f3b01601.0000000000000004
rbd_data.854f3b01601.00000000000000a0
rbd_directory
rbd_info
rbd_data.854f3b01601.00000000000000e0
csi.volume.c-908b-4bb0-b31c-4b5683a41ece
rbd_data.854f3b01601.0000000000000000
rbd_id.csi-vol-544502c8-2a7e-4a6d-9bb2-90382fb73f07
csi.volume.544502c8-2a7e-4a6d-9bb2-90382fb73f07
rbd_data.854f3b01601.0000000000000020
rbd_data.854f3b01601.0000000000000060
csi.volumes.default
rbd_trash
rbd_data.854f3b01601.0000000000000080