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 可用性

pv-rbd-demo.yaml

in-tree RBD PV

不可用,改 CSI PV

volumes-rbd-demo.yaml

Pod 内联 rbd

不可用

volumes-cephfs-demo.yaml

Pod 内联 cephfs

不可用

storageclass-rbd-demo.yaml

kubernetes.io/rbd

不可用,改 rbd.csi.ceph.com

pvc-dyn-longhorn-demo.yaml / volumes-pvc-longhorn-demo.yaml

Longhorn 动态 PVC 与其 Pod

可用

longhorn.yaml

v1.0.1 部署清单

CRD 为 apiextensions/v1beta1,不可 apply

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

ceph-rbd-pv.yaml
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

ceph-rbd-claim.yaml
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、快照与恢复

longhorn-pvc-pod.yaml
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 一直 Pending

StorageClass 不存在,或 provisioner Pod 未就绪

kubectl describe pvc 看事件;确认 ceph-csi/longhorn 的 controller

Pod ContainerCreating,事件里有 rbd: map failed

image features 与内核不匹配,或 pool 权限不足

核对 imageFeatures: layering;用 rbd map 在节点手工验证

Longhorn Volume degraded

某节点离线、磁盘满或 replica 重建中

查 lhr 状态与节点磁盘;恢复后自动重建

Longhorn Pod 起不来,日志提示 iSCSI

节点未装 open-iscsi 或 iscsid 未启动

systemctl enable --now iscsid,装 iscsi-initiator-utils

3.11.6. 进阶挑战

  1. 用 ceph-csi 的 cephfs.csi.ceph.com 建一个 RWX StorageClass,让两个副本同时写同一目录,观察文件锁与一致性。

  2. 给 Longhorn 配 MinIO 作为 backup target,用 recurringJobs 做「每天快照、每周备份」,再演练从 backup 恢复。

  3. 对比 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。

3.11.8. 延伸阅读