【Kubernetes从入门到精通】第40篇:NFS和Ceph——自建分布式存储的那些年

【Kubernetes从入门到精通】第40篇:NFS和Ceph——自建分布式存储的那些年

上一篇【第39篇】本地持久化存储——Local PV和hostPath的正确使用姿势
下一篇【第41篇】ConfigMap和Secret的持久化——subPath挂载vs自动更新


摘要

如果你跑在AWS上,存储不是问题——EBS随叫随到。但如果你是在自建机房里搭K8s,怎么办?买不起SAN,用不起云盘,难道只能hostPath?

两条路:NFS(NAS的简化版,简单粗暴,搭个NFS服务器就能用)和Ceph(企业级分布式存储,复杂但强大,AWS的EBS底层其实就是类似的东西)。NFS适合小团队——一台服务器导出目录,所有Node挂载,配合nfs-subdir-external-provisioner就能实现动态PV供应。Ceph适合有追求的团队——MON集群保证高可用、OSD管理磁盘、PG自动分布数据、自带容错和副本。

本文带你从NFS的搭建讲到Ceph的架构,最后介绍Rook这个开源项目——它让Ceph能在K8s里一键部署,运维成本降到几乎为零。


一、NFS——最朴素的分布式存储

1.1 NFS是什么

NFS(Network File System)就是一个"网络优盘"——一台服务器把自己的某个目录通过网络共享出去,其他机器挂载后就能读写。简单、成熟、几乎所有操作系统都支持。

【NFS 架构——"一台服务器,所有人共用"】 ┌───────────────────────────────────────────────────────────┐ │ NFS Server (192.168.1.100) │ │ │ │ /exports/ │ │ ├── k8s-volumes/ ← 给K8s用的存储池 │ │ │ ├── pvc-abc123/ ← 动态创建的PVC子目录 │ │ │ ├── pvc-def456/ │ │ │ └── pvc-ghi789/ │ │ └── backups/ ← 备份目录 │ └───────────────────────┬───────────────────────────────────┘ │ NFS Protocol (TCP/UDP 2049) ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Node-1 │ │ Node-2 │ │ Node-3 │ │ │ │ │ │ │ │ Pod-A │ │ Pod-B │ │ Pod-C │ │ /data───│─│─/data───│─│─/data │ │ │ │ │ │ │ │ 都看到 │ │ 同一份 │ │ 数据! │ └─────────┘ └─────────┘ └─────────┘

1.2 搭建NFS Server

# 在存储服务器上(CentOS/Ubuntu通用)# Step 1: 安装NFS服务sudoaptupdate&&sudoaptinstall-ynfs-kernel-server# Ubuntu# sudo yum install -y nfs-utils # CentOS# Step 2: 创建共享目录sudomkdir-p/exports/k8s-volumessudochownnobody:nogroup /exports/k8s-volumessudochmod777/exports/k8s-volumes# Step 3: 配置导出sudotee-a/etc/exports<<EOF /exports/k8s-volumes *(rw,sync,no_subtree_check,no_root_squash) EOF# 参数说明:# rw - 读写权限# sync - 同步写入(数据安全,性能稍低)# no_subtree_check - 不检查父目录权限(性能优化)# no_root_squash - 允许root权限(容器内root可写)# Step 4: 应用配置并启动sudoexportfs-rasudosystemctlenable--nownfs-kernel-server# Step 5: 防火墙放行(如果有)sudoufw allow from10.0.0.0/8 to any port nfs# 验证:在另一个Node上测试挂载sudomount-tnfs192.168.1.100:/exports/k8s-volumes /mnt/testls/mnt/testsudoumount/mnt/test

要点:NFS的配置简单到令人发指——就5步,15分钟搞定。但别被简单蒙蔽了——这个NFS Server是单点故障。它挂了,所有依赖的Pod就跪了(处于I/O阻塞状态)。生产环境建议用商业NAS设备(Synology/QNAP)或者至少配个NFS高可用(DRBD + Pacemaker,但配置复杂度会让你想回去装Ceph)。

1.3 在K8s中原生使用NFS Volume

# 最原始的方式——直接把Pod绑定到NFSapiVersion:v1kind:Podmetadata:name:nfs-direct-podspec:volumes:-name:nfs-storagenfs:server:192.168.1.100path:/exports/k8s-volumes/my-appcontainers:-name:appimage:nginx:alpinevolumeMounts:-name:nfs-storagemountPath:/usr/share/nginx/html
# 稍微好一点——用PV/PVC包装NFSapiVersion:v1kind:PersistentVolumemetadata:name:nfs-pvspec:capacity:storage:100GiaccessModes:-ReadWriteMany# NFS支持RWX!多Pod同时读写-ReadWriteOnce-ReadOnlyManynfs:server:192.168.1.100path:/exports/k8s-volumes/pv001---apiVersion:v1kind:PersistentVolumeClaimmetadata:name:nfs-pvcspec:accessModes:-ReadWriteManyresources:requests:storage:50Gi

1.4 nfs-subdir-external-provisioner——NFS动态供应

手动创建NFS PV太傻了。nfs-subdir-external-provisioner让你享受StorageClass级别的动态供应体验:

【nfs-subdir-external-provisioner——NFS版动态供应】 PVC: "我要10G NFS存储" │ ▼ StorageClass: nfs-client │ ▼ ┌────────────────────────────────────────────────────┐ │ nfs-subdir-external-provisioner │ │ ─────────────────────────────────────────────── │ │ 1. 收到PVC请求 │ │ 2. 在NFS上创建子目录: │ │ /exports/k8s-volumes/<namespace>-<pvcname>-<uuid>/│ │ 3. 创建PV(NFS类型,path指向新建子目录) │ │ 4. 绑定PVC到PV │ │ 5. 删除PVC时自动清理子目录 │ └────────────────────────────────────────────────────┘
# 部署 nfs-subdir-external-provisioner(Helm方式)helm repoaddnfs-subdir-external-provisioner\https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helminstallnfs-provisioner\nfs-subdir-external-provisioner/nfs-subdir-external-provisioner\--setnfs.server=192.168.1.100\--setnfs.path=/exports/k8s-volumes\--setstorageClass.name=nfs-client\--setstorageClass.defaultClass=true\--setstorageClass.reclaimPolicy=Delete\--setstorageClass.archiveOnDelete=false
# 部署完成后,创建StorageClass(Helm已自动创建,这是手动方式参考)apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:nfs-clientannotations:storageclass.kubernetes.io/is-default-class:"true"provisioner:k8s-sigs.io/nfs-subdir-external-provisionerparameters:archiveOnDelete:"false"# PVC删除后是否归档数据pathPattern:"${.PVC.namespace}-${.PVC.name}"# 子目录命名规则
# 现在你只需要写PVC——剩下的全自动!kubectl apply-f-<<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-app-data spec: storageClassName: nfs-client # 使用NFS StorageClass accessModes: - ReadWriteMany resources: requests: storage: 10Gi EOF# 几秒后,NFS上自动创建了 default-my-app-data-xxxxx/ 目录# PV自动创建、自动绑定!

要点nfs-subdir-external-provisioner是自建K8s存储的"入门首选"——用最小的成本(一台NFS服务器 + 一个Helm chart)实现动态PV供应。但它不是银弹:(1) NFS单点故障风险要自己承担;(2) 所有IO走网络,延迟比本地高出不少;(3) 不适合高并发随机IO场景(数据库)。


二、Ceph——分布式存储的"瑞士军刀"

2.1 Ceph是什么——不是一块硬盘,而是一套系统

如果NFS是"一个房东把房子分租出去",那Ceph就是"整个小区的物业管理系统"——自动管理所有房子、自动分配合适的租户、自动修复坏掉的房子。

【Ceph 核心架构——三大组件协同工作】 ┌─────────────────────────────────────────────────────────────┐ │ Ceph 集群 │ │ │ │ ┌─────────────────┐ │ │ │ MON (Monitor) │ 监视器集群(奇数台,3/5/7) │ │ │ ───────────────│ │ │ │ • 维护集群地图 │ "谁在集群里、谁不在、数据在哪" │ │ │ • 选举领导者 │ │ │ │ • 仲裁决策 │ MON ≠ 存储数据,MON = 管家 │ │ └────────┬────────┘ │ │ │ │ │ ┌────────┴──────────────────────────────────────────┐ │ │ │ OSD (Object Storage Daemon) │ │ │ │ ──────────────────────────────────────────── │ │ │ │ • 每个磁盘一个OSD进程 │ │ │ │ • 实际存储数据 │ │ │ │ • 自动副本、自动恢复 │ │ │ │ • 默认3副本(你可以配) │ │ │ │ │ │ │ │ Node-1 Node-2 Node-3 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐│ │ │ │ │ OSD.0 │ │ OSD.1 │ │ OSD.2 ││ │ │ │ │ /dev/sdb │ │ /dev/sdb │ │ /dev/sdb ││ │ │ │ │ OSD.3 │ │ OSD.4 │ │ OSD.5 ││ │ │ │ │ /dev/sdc │ │ /dev/sdc │ │ /dev/sdc ││ │ │ │ └──────────┘ └──────────┘ └──────────┘│ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────┐ │ │ │ MDS (Metadata │ 元数据服务器(仅CephFS需要) │ │ │ Server) │ │ │ │ ───────────────│ "文件A在哪些OSD上、文件B多大、 │ │ │ • 文件系统元数据│ 谁最近修改了文件C" │ │ │ • 目录结构 │ │ │ └─────────────────┘ │ └─────────────────────────────────────────────────────────────┘

2.2 Ceph的核心概念——PG和CRUSH算法

Ceph真正的魔力在Placement Group(PG)和CRUSH算法:

【数据在Ceph中如何分布——PG和CRUSH】 你的文件 → 切成多个Object(默认4MB一个) Object → 哈希 → 分配到某个PG → PG通过CRUSH算法 → 确定存在哪些OSD上 ──────────────────────────────────────────────────────── ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Object-1 │ │ Object-2 │ │ Object-3 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ hash │ hash │ hash ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ PG-42 │ │ PG-17 │ │ PG-99 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ CRUSH("PG-42") │ CRUSH("PG-17") │ CRUSH("PG-99") ▼ ▼ ▼ ┌─────────────────────────────────────────────┐ │ PG-42 → OSD.0 (主), OSD.3 (副本1), OSD.7 (副本2)│ │ PG-17 → OSD.5 (主), OSD.1 (副本1), OSD.2 (副本2)│ │ PG-99 → OSD.4 (主), OSD.6 (副本1), OSD.0 (副本2)│ └─────────────────────────────────────────────┘ CRUSH算法的威力: - 不需要中心化的元数据表(不像HDFS的NameNode) - 任何客户端都知道数据在哪(计算PG→OSD映射) - OSD挂了,CRUSH自动重新计算→数据自动迁移到健康的OSD - 完全去中心化——没有单点瓶颈

要点:CRUSH是Ceph的"智能魔方"——你给它一个PG编号,它就能算出这个PG的数据存在哪些OSD上。不需要查表、不需要中心节点、任何客户端都是平等的。这就是Ceph能做到"无限扩展、去中心化"的根本原因。NFS要查服务器地址,Ceph直接算——这就是架构上的降维打击。

2.3 CephFS vs RBD——选哪个?

Ceph对外提供三种存储接口,K8s里常用的是CephFS和RBD:

【Ceph 的三种存储接口】 ┌────────────────────────────────────────────────┐ │ Ceph 存储集群 │ │ │ │ RADOS (Reliable Autonomic Distributed Object │ │ Store) - 底层对象存储 │ └──────────┬──────────┬──────────┬───────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────┐ ┌──────────────┐ │ CephFS │ │ RBD │ │ RADOSGW │ │ (文件系统) │ │ (块设备) │ │ (对象存储) │ │ │ │ │ │ │ │ 像NFS一样用 │ │ 像硬盘 │ │ 像S3一样用 │ │ 多Pod共享 │ │ 单Pod独占 │ │ HTTP API │ │ 需要MDS │ │ 不需要MDS │ │ 不需要 │ └──────────────┘ └──────────┘ └──────────────┘
对比维度CephFSRBD
类型分布式文件系统块设备
多Pod挂载✅ RWX(多读多写)❌ 仅RWO(单Pod读写)
MDS需要?✅ 需要MDS服务❌ 不需要
适用场景共享文件存储、多Pod读写数据库、单Pod独占存储
性能文件系统开销,略低于RBD裸块设备,性能更高
一致性强一致性强一致性
K8s支持✅ CSI Driver✅ CSI Driver
# CephFS PVC(多Pod共享)apiVersion:v1kind:PersistentVolumeClaimmetadata:name:cephfs-sharedspec:accessModes:-ReadWriteMany# CephFS 支持 RWX!resources:requests:storage:100GistorageClassName:cephfs---# RBD PVC(单Pod独占,数据库专属)apiVersion:v1kind:PersistentVolumeClaimmetadata:name:rbd-db-dataspec:accessModes:-ReadWriteOnce# RBD 只支持 RWOresources:requests:storage:200GistorageClassName:rbd-ssd

要点:选CephFS还是RBD,本质上是选"我要多Pod共享还是单Pod独占"。多Pod需要看同一份文件 → CephFS。数据库要最高性能、独占块设备 → RBD。两个可以在同一个Ceph集群里共存,只是用的接口不同。


三、Rook——让Ceph在K8s里一键部署

3.1 Rook解决了什么

传统Ceph部署有多痛苦?大概需要:

  • 手动配5-10台服务器
  • 每台装Ceph包、配置OSD、加MON
  • ceph-deploy/cephadm 学半天
  • 出了问题只能上Ceph社区求助

Rook一句话:把Ceph变成K8s的一个Operator——用K8s的CRD定义Ceph集群,Rook Operator自动把Ceph的各个组件(MON、OSD、MDS、RGW)变成Pod运行在K8s上。

【Rook 架构——Ceph变成K8s原生应用】 ┌──────────────────────────────────────────────────────────┐ │ Kubernetes │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Rook Operator │ │ │ │ (Deployment, 监控CephCluster CR, 管理Ceph组件) │ │ │ └──────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌────────────┼────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │MON Pod │ │MON Pod │ │MON Pod │ (3个MON) │ │ │Node-1 │ │Node-2 │ │Node-3 │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │OSD Pod │ │OSD Pod │ │OSD Pod │ │ │ │OSD.0 │ │OSD.1 │ │OSD.2 │ (N个OSD) │ │ │/dev/sdb │ │/dev/sdb │ │/dev/sdb │ │ │ │Node-1 │ │Node-2 │ │Node-3 │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │MDS Pod │ │MDS Pod │ (CephFS元数据,高可用) │ │ │Node-1 │ │Node-2 │ │ │ └──────────┘ └──────────┘ │ └──────────────────────────────────────────────────────────┘

3.2 Rook 一键部署 Ceph

# Step 1: 部署Rook Operator(Helm方式)helm repoaddrook-release https://charts.rook.io/release helminstall--create-namespace--namespacerook-ceph rook-ceph\rook-release/rook-ceph# Step 2: 创建Ceph集群(就这一个CRD!)kubectl apply-f-<<EOF apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: rook-ceph namespace: rook-ceph spec: cephVersion: image: quay.io/ceph/ceph:v17.2.7 # Quincy版本 dataDirHostPath: /var/lib/rook # 每个Node上的数据目录 mon: count: 3 # 3个MON(高可用) allowMultiplePerNode: false storage: useAllNodes: true useAllDevices: true # 使用所有未格式化的磁盘 # deviceFilter: "^sd[b-c]" # 或者指定特定磁盘 dashboard: enabled: true ssl: true # 可选:指定特定Node和磁盘 # nodes: # - name: node-1 # devices: # - name: /dev/nvme0n1 # - name: node-2 # devices: # - name: /dev/nvme0n1 EOF# Step 3: 等待集群就绪kubectl-nrook-ceph get cephcluster# NAME DATADIRHOSTPATH MONCOUNT AGE PHASE MESSAGE# rook-ceph /var/lib/rook 3 10m Ready Cluster created successfully# 查看Ceph状态kubectl-nrook-cephexec-itdeploy/rook-ceph-tools -- ceph status# cluster:# id: a1b2c3d4-...# health: HEALTH_OK# services:# mon: 3 daemons# mgr: a(active)# osd: 6 osds: 6 up, 6 in# data:# pools: 1 pools, 1 pgs# objects: 0 objects# usage: 6.0 GiB used / 600 GiB avail# Step 4: 创建StorageClass(Rook自动创建,手动参考)# CephFS StorageClass ——支持RWXapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: rook-cephfs provisioner: rook-ceph.cephfs.csi.ceph.com parameters: clusterID: rook-ceph fsName: myfs pool: myfs-data0 csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph csi.storage.k8s.io/controller-expand-secret-name: rook-csi-cephfs-provisioner csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph reclaimPolicy: Delete allowVolumeExpansion:true---# RBD StorageClass ——高性能块存储apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: rook-ceph-block provisioner: rook-ceph.rbd.csi.ceph.com parameters: clusterID: rook-ceph pool: replicapool imageFormat:"2"imageFeatures: layering csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph reclaimPolicy: Delete allowVolumeExpansion:true
# 使用CephFS(多Pod共享文件)kubectl apply-f-<<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata: name: shared-data spec: storageClassName: rook-cephfs accessModes: - ReadWriteMany # CephFS 支持! resources: requests: storage: 50Gi EOF# 使用RBD(数据库高性能)kubectl apply-f-<<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-data spec: storageClassName: rook-ceph-block accessModes: - ReadWriteOnce resources: requests: storage: 200Gi EOF

要点:Rook让Ceph从"运维噩梦"变成"K8s原生体验"——以前搭Ceph要几周、你搭完还不敢改配置(怕搞崩),现在一条kubectl apply命令就能搭、扩容缩容改配置都是改CRD。Rook Operator自动处理Ceph的生命周期管理。这可以说是自建K8s存储的最优方案了。


四、NFS vs Ceph vs Rook+Ceph——终极选型指南

【自建K8s存储方案选择决策树】 你的场景是什么? │ ├── 小团队(<10人),预算有限,能接受单点故障? │ └── NFS + nfs-subdir-external-provisioner │ 优点:15分钟搞定,运维成本低 │ 缺点:NFS服务器是单点 │ ├── 中型团队,对可用性有要求,有专业运维? │ └── Ceph (手动部署) │ 优点:高可用、自动容错、性能好 │ 缺点:运维成本高,需要懂Ceph │ └── 有K8s经验,想一劳永逸? └── Rook + Ceph 优点:K8s原生、自动运维、一键部署 缺点:需要K8s经验,资源占用稍高
方案复杂度可靠性性能运维成本推荐场景
NFS⭐ 极低⭐⭐ 单点⭐⭐ 中等⭐ 极低开发/测试、小团队生产
Ceph手动⭐⭐⭐⭐ 高⭐⭐⭐⭐⭐ 极高⭐⭐⭐⭐ 高⭐⭐⭐⭐ 高大公司有专业运维
Rook+Ceph⭐⭐⭐ 中等⭐⭐⭐⭐⭐ 极高⭐⭐⭐⭐ 高⭐⭐ 低最佳平衡方案
NFS Provisioner⭐⭐ 低⭐⭐ 单点⭐⭐ 中等⭐⭐ 低快速上手过渡

本篇小结

自建机房的K8s存储,两条技术路线各有千秋:

NFS路线(入门):一台服务器+一个Helm chart搞定。nfs-subdir-external-provisioner让你享受动态供应,但NFS本身是单点故障——服务器挂了全跪。适合非关键业务或作为临时方案。

Ceph路线(进阶):MON集群保高可用、OSD管理磁盘、CRUSH算法智能分布数据。CephFS(多Pod共享)和RBD(单Pod高性能)覆盖全部场景。传统部署复杂,但Rook让一切变得简单——Ceph变成K8s上的原生应用,一键部署、自动运维。

我的建议:先上NFS Provisioner快速跑起来,然后花时间学Rook+Ceph做长期方案。别一上来就搞手动Ceph——你会被MON选举、OSD故障、PG状态这些概念折磨死。

下一篇聊ConfigMap和Secret的持久化——这两种K8s原生对象虽然叫"持久化",但它们的存储方式和使用姿势有很多坑。


上一篇【第39篇】本地持久化存储——Local PV和hostPath的正确使用姿势
下一篇【第41篇】ConfigMap和Secret的持久化——subPath挂载vs自动更新