3.11. Ceph 与 Longhorn 存储实践
前几章的存储还停留在 emptyDir、hostPath 与 NFS:要么数据随 Pod 消失,要么被钉死在一台机器上。真正有状态的服务需要的是「一块能在节点间漂移的块设备」或「一个多节点可写的共享文件系统」。本章用 Ceph 的 RBD/CephFS 讲清分布式存储的接法,再用 Longhorn 演示如何在集群内部自己造出带副本的块存储。
- 层级:
L3 · 平台
- 难度:
进阶
- 预计:
100 分钟
- 前置:
PV/PVC、StorageClass 与 CSI 基础
3.11.1. 你将学到
Ceph 的 RBD 与 CephFS 在 Kubernetes 中各自解决什么问题,字段怎么落到 PV 上。
为什么原书的 in-tree
rbd/cephfs清单在 v1.31 上跑不起来,CSI 写法长什么样。Longhorn 的 manager、engine、replica 分工,以及副本、快照、动态供给的完整链路。
Ceph、Longhorn、OpenEBS、NFS CSI 的选型边界。
3.11.2. 为什么需要它
存储方案的分水岭是「数据放在哪」。hostPath 与 localPV 把数据绑在节点上,Pod 漂移就看不到;NFS 把数据集中到一台服务器,性能与可用性都押在那一台上。Ceph 用 CRUSH 把数据分散到整个存储集群,同时提供块(RBD)、文件(CephFS)、对象(RGW)三种接口;Longhorn 则反过来,把每个节点的本地磁盘聚合成带副本的块存储,不依赖外部存储集群。原书这一章是存储卷的总览,本站把基础卷类型放在存储与持久化,本章只处理分布式存储的两条主线;OpenEBS 与 NFS CSI 在存储扩展中。
3.11.3. 核心概念
3.11.3.1. Ceph 的三种接口
RBD 是块设备,挂进容器后是一个格式化成 xfs/ext4 的磁盘,只支持 ReadWriteOnce;CephFS 是 POSIX 文件系统,多个节点可以同时挂载,支持 ReadWriteMany;RGW 提供 S3 兼容的对象接口,用在备份与数据湖。三者的元数据都由 monitor 集群维护,数据落在 OSD 上。
PV 里的关键字段对应关系是:monitors 指向 monitor 地址,pool 是存储池,image 是 RBD 块设备名,user + keyring 是认证凭据,fsType 决定挂载时格式化成什么文件系统。CephFS 多一个 path,指定挂载文件系统里的哪个子目录。
3.11.3.2. in-tree 已移除:v1.31 必须走 CSI
原书的 pv-rbd-demo.yaml、volumes-rbd-demo.yaml、volumes-cephfs-demo.yaml、storageclass-rbd-demo.yaml 全部使用 in-tree 插件(rbd、cephfs、kubernetes.io/rbd)。这两个插件在 Kubernetes v1.31 被移除,rbd 与 cephfs 卷类型随之失效,Pod 会卡在挂载阶段。
替代方案是 ceph-csi:controller 侧提供 rbd.csi.ceph.com 与 cephfs.csi.ceph.com 两个 provisioner,node 侧以 DaemonSet 常驻。认证不再依赖节点上的 keyring 文件,而是通过 Secret 传递,clusterID 用 Ceph 集群的 fsid。
原书文件 |
内容 |
v1.31 可用性 |
|---|---|---|
|
in-tree RBD PV |
不可用,改 CSI PV |
|
Pod 内联 |
不可用 |
|
Pod 内联 |
不可用 |
|
|
不可用,改 |
|
Longhorn 动态 PVC 与其 Pod |
可用 |
|
v1.0.1 部署清单 |
CRD 为 |
3.11.3.3. Longhorn 架构
Longhorn 把每个节点的本地磁盘切成副本,卷由「一个 engine + N 个 replica」组成,engine 通过 iSCSI 把块设备暴露给节点,kubelet 再把它挂进 Pod。
图:PVC 触发 provisioner 建 Volume CR,manager 在节点上调度 engine 与 replica,engine 把块设备暴露给 Pod 所在节点。
longhorn-manager 是 DaemonSet,负责调度与生命周期;每个节点还有 instance-manager,真正跑 engine 与 replica 进程;longhorn-ui 只读展示与运维操作;longhorn-driver-deployer 负责安装 CSI 组件。副本数是 StorageClass 参数,longhorn 用 1(实验环境省空间),lh3replicas 用 3。
3.11.3.4. 副本、快照与备份
副本保证「节点坏了数据还在」:3 副本时允许 1 个节点离线,卷进入 degraded 状态并在新节点重建副本。快照保证「误删能回滚」:快照是卷在某一时刻的只读视图,存在同一个卷的副本里,节点整体损坏时快照也没了。备份才解决「集群级灾难」:先把快照推到外部 backup target(S3 或 NFS),恢复时从 backup 重建。三者对应三个对象:VolumeSnapshotClass 声明用哪个驱动做快照,VolumeSnapshot 引用 PVC 发起快照,Backup/backup target 负责离群存储。Longhorn 还支持 recurringJobs,在 StorageClass 里用 JSON 声明定时快照与备份,不必依赖外部 CronJob。
3.11.3.5. 动态供给与对比
动态供给的链路是:PVC 指定 StorageClass → provisioner 调 CreateVolume → Longhorn 建 Volume CR → manager 调度 replica → engine 就绪后 PV 绑定 → Pod 调度到节点时 attach 并挂载。WaitForFirstConsumer 对 Longhorn 不是必须的,因为副本可以落在任意节点,但本地盘方案(OpenEBS localpv)必须用它。
维度 |
Longhorn |
Ceph RBD/CephFS |
OpenEBS |
NFS CSI |
|---|---|---|---|---|
数据位置 |
节点本地盘 + 副本 |
独立存储集群 |
节点本地 |
集中式 NFS |
复制 |
卷级副本 |
CRUSH + 副本/纠删码 |
依引擎 |
无 |
RWX |
通过 share-manager |
CephFS 原生 |
弱 |
原生 |
运维成本 |
低,集群内自洽 |
高,需专职存储运维 |
中 |
低 |
3.11.4. 动手实验
实验一:用 ceph-csi 挂载 RBD 静态卷
前置:Ceph 集群可用;pool kube 中已有 2GiB 的 pv-test 镜像;用户 kube 对 pool 有读写权限;ceph-csi 的 rbd.csi.ceph.com 已部署,ceph-csi-config 中登记了 clusterID 与 monitors。
3.11.4.1. 步骤 1 · 建 Secret 与 PV
apiVersion: v1
kind: Secret
metadata: {name: ceph-rbd-secret, namespace: default}
stringData:
userID: kube
userKey: AQB3xWljAAAAABAA...==
---
apiVersion: v1
kind: PersistentVolume
metadata: {name: pv-rbd-demo}
spec:
capacity: {storage: 2Gi}
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
csi:
driver: rbd.csi.ceph.com
volumeHandle: kube/pv-test
volumeAttributes: {clusterID: 4c0f..., pool: kube, staticVolume: "true"}
nodeStageSecretRef: {name: ceph-rbd-secret, namespace: default}
备注
静态 PV 的三个关键字段
volumeHandle 是 <pool>/<image>;staticVolume: "true" 表示复用已有镜像;nodeStageSecretRef 取代节点 keyring,Secret 需与 PV 同命名空间或显式写 namespace。
3.11.4.2. 步骤 2 · 建 PVC 与 Pod
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: pvc-rbd-demo}
spec:
accessModes: [ReadWriteOnce]
storageClassName: ""
volumeName: pv-rbd-demo
resources: {requests: {storage: 2Gi}}
---
apiVersion: v1
kind: Pod
metadata: {name: volumes-rbd-demo}
spec:
containers:
- name: redis
image: redis:alpine
volumeMounts: [{mountPath: /data, name: redis-rbd-vol}]
volumes:
- name: redis-rbd-vol
persistentVolumeClaim: {claimName: pvc-rbd-demo}
storageClassName: "" 是静态绑定的关键:留空表示「不参与动态供给」,直接按 volumeName 绑定。写 storageClassName 又写错名字,PVC 会一直 Pending。
3.11.4.3. 步骤 3 · 验证挂载与访问模式
kubectl apply -f ceph-rbd-pv.yaml -f ceph-rbd-claim.yaml
kubectl get pv,pvc
kubectl exec volumes-rbd-demo -- sh -c 'df -hT /data; echo ok > /data/probe.txt'
kubectl get pv 应显示 Bound,df -T 应显示 xfs 或 ext4。RWO 表示同一时刻只能被一个节点挂载:把 Pod 调度到另一节点会卡在 Multi-Attach error,这正是块设备的边界。
实验二:Longhorn 部署、动态 PVC 与快照
前置:至少 3 个节点,每节点有独立磁盘;所有节点安装 open-iscsi 并启动 iscsid。若沿用原书 longhorn.yaml(v1.0.1),它的 CRD 是 apiextensions/v1beta1,在 v1.31 上无法 apply,必须改用 Helm。
3.11.4.4. 步骤 4 · 安装 Longhorn
helm repo add longhorn https://charts.longhorn.io && helm repo update
helm install longhorn longhorn/longhorn -n longhorn-system --create-namespace \
--set defaultSettings.defaultReplicaCount=3
kubectl -n longhorn-system get pod && kubectl get sc
Chart 会创建 longhorn StorageClass(numberOfReplicas 默认 3)与 longhorn VolumeSnapshotClass。原书把默认副本改成 1 并用单独的 lh3replicas 提供 3 副本,本质是把「实验省空间」和「生产要冗余」分开,你可以直接改 SC 参数复现。
3.11.4.5. 步骤 5 · 动态 PVC、快照与恢复
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: pvc-dyn-longhorn-demo}
spec:
storageClassName: longhorn
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 2Gi}}
---
apiVersion: v1
kind: Pod
metadata: {name: volumes-pvc-longhorn-demo}
spec:
containers:
- name: redis
image: redis:alpine
volumeMounts: [{mountPath: /data, name: redis-data-vol}]
volumes:
- name: redis-data-vol
persistentVolumeClaim: {claimName: pvc-dyn-longhorn-demo}
kubectl apply -f longhorn-pvc-pod.yaml
kubectl -n longhorn-system get lhv,lhr
kubectl exec volumes-pvc-longhorn-demo -- sh -c 'echo v1 > /data/version.txt'
kubectl delete pod volumes-pvc-longhorn-demo && kubectl apply -f longhorn-pvc-pod.yaml
kubectl exec volumes-pvc-longhorn-demo -- cat /data/version.txt
lhv 是 Volume、lhr 是 Replica,应看到 1 个 Volume 与 3 个 Replica(分布在不同节点)。删 Pod 再重建后文件仍在,说明数据落在 PV 而不是容器层。发起快照:kubectl apply -f longhorn-snapshot.yaml,其中 volumeSnapshotClassName: longhorn、source.persistentVolumeClaimName: pvc-dyn-longhorn-demo;再从快照恢复一个新 PVC,把 dataSource 指向它。
3.11.4.6. 验证清单
kubectl get pv中pv-rbd-demo为Bound,df -T显示预期文件系统。Longhorn 的 Volume 有 3 个 Replica 且分布在 3 个节点,状态
healthy。删除并重建 Pod 后,PVC 中的数据仍在。
VolumeSnapshot的readyToUse为true,从快照恢复的 PVC 能挂载。
3.11.5. 常见问题与排错
现象 |
可能原因 |
处理方式 |
|---|---|---|
PVC 一直 |
StorageClass 不存在,或 provisioner Pod 未就绪 |
|
Pod |
image features 与内核不匹配,或 pool 权限不足 |
核对 |
Longhorn Volume |
某节点离线、磁盘满或 replica 重建中 |
查 |
Longhorn Pod 起不来,日志提示 iSCSI |
节点未装 |
|
3.11.6. 进阶挑战
用 ceph-csi 的
cephfs.csi.ceph.com建一个 RWX StorageClass,让两个副本同时写同一目录,观察文件锁与一致性。给 Longhorn 配 MinIO 作为 backup target,用
recurringJobs做「每天快照、每周备份」,再演练从 backup 恢复。对比 Longhorn 3 副本与 OpenEBS Mayastor 在随机写下的延迟与写放大,给出选型结论。
3.11.7. 小结
RBD 是块设备、CephFS 是共享文件系统,PV 字段与 CSI 的
volumeAttributes一一对应;in-tree 的rbd/cephfs在 v1.31 已移除,生产必须用 ceph-csi,静态绑定靠storageClassName: ""加volumeName。Longhorn 用 manager + engine + replica 在集群内部造块存储;选型看数据放在哪:有 Ceph 用 Ceph,没有外部存储要副本用 Longhorn,只要共享配置用 NFS CSI。