☰
Kubernetes高可用NFS集群搭建与动态存储供应实战
2026/10/21 6:01:01 网站建设 项目流程

前一阵和一位朋友聊他们平台的存储架构,他说大部分业务都用CSI接的云盘,剩下的日志和文件服务全挂在一台NFS上。我问他如果这台NFS挂了怎么办,他说那就所有人等它起来。这句话让我想起很多规模不大但有Kubernetes高可用动态存储NFS集群需求的团队,通常都有同样的处境:PV越来越多,但存储后端还是单点。今天这篇文章就把我从零搭建NFS高可用主备集群、再接入Kubernetes动态存储供应的完整过程写下来,包括方案选型、每一步配置、故障切换演练,以及生产环境里踩过的几个比较隐蔽的坑。适合正在用NFS但没有高可用方案的运维和开发同学参考,也适合刚接触StorageClass想理解动态供应原理的K8s使用者。

1. 为什么单点NFS不够用:存储成为故障单点的真实代价

1.1 从一次业务挂起看单点风险

我之前在一套内部私有云上遇到过非常典型的情况:三台Kubernetes节点,十几套应用共享一个NFS。某天NFS所在虚拟机网络抖动,持续了大概几分钟,结果所有挂载了这个共享目录的业务容器全部进入等待状态,日志打不出来了,数据库连接池被占满,健康检查也陆续开始失败。

大家可能觉得NFS很“皮实”,挂载一次就能一直用。但其实NFS天然依赖网络会话,一旦服务端不可达,客户端进程并不会立刻报错,而是进入重试等待。默认的hard挂载方式下,进程会一直处于不可中断的等待状态,直到网络恢复或者手动干预。这一个细节,就让单点NFS的故障半径从“一台存储挂了”,扩散成了“所有使用该存储的业务全部不可用”。

最麻烦的是,这种故障在Kubernetes里表现得非常隐蔽。容器还在运行,kubectl get pod看不到Error或CrashLoopBackOff,只有进到容器里敲命令才会发现什么都是hang住的。排障时容易把时间浪费在应用层,而不是存储层。

1.2 NFS单点影响的三个维度

我用三个维度来评估单点NFS对平台的影响,这样思考会更清晰:

  • 控制面维度:Kubernetes的PersistentVolume和PersistentVolumeClaim如果底层依赖NFS,当NFS不可用时,PV的状态会异常,PVC无法正常绑定,新的工作负载调度不过来。就算Pod调度到了其他节点,数据卷也挂不上去。

  • 数据面维度:已经挂载了NFS的Pod,读写会直接阻塞。如果是ReadWriteMany场景,多个副本共享一份文件,故障影响范围会成倍扩大。日志服务、文件上传、对象存储网关、分布式数据库的wal目录,凡是用到了NFS的模块都会被波及。

  • 运维维度:单点NFS意味着所有恢复动作都是串行的。你得先修存储,再处理客户端,再处理应用。没有备用节点可以切换,也没有办法在业务不中断的情况下做数据校验、备份恢复和内核升级。很多团队把NFS故障定义为P0,但架构上又没有给这个P0事故留任何逃生通道。

1.3 判断你是否需要高可用NFS

不是所有场景都需要给NFS搭建高可用集群。我给一个比较实用的判断标准,满足两条以上就建议动手改造:

  • 平台上已经有超过20个PVC挂在同一个NFS上;
  • NFS存储里承载了日志、文件服务、CI产物等业务数据,而不是纯临时缓存;
  • 业务对存储故障的容忍时间低于30分钟;
  • 你希望在Kubernetes里实现StorageClass动态供应,而不是每个PV都靠手工预先创建;
  • 集群规模不大,不想引入Ceph这类重型分布式存储,但又不接受单点。

如果满足条件,下面这套方案基本就是最经济、最直接的一条路了。

2. 高可用NFS技术选型对比:主备、双活还是分布式文件系统

2.1 三种主流方案的实现原理

做NFS高可用,绕不开三个方向。第一个方向是“Block层镜像+虚拟IP漂移”,也就是用DRBD这类块复制工具把两台机器的数据盘做成实时镜像,再用Keepalived之类的手段对外提供一个虚拟IP。NFS服务跑在虚拟IP后面,哪台机器持有虚拟IP,哪台机器就是当前的主节点。第二种方向是“分布式文件系统”,比如CephFS、GlusterFS,由多个存储节点组成集群,客户端直接访问分布式集群提供的文件系统,单节点故障由分布式协议自动处理。第三种方向可以叫“硬件NAS双控制器”,很多成品NAS本身自带双控制器,故障后控制器自动切换,Kubernetes这边只需要把它当作普通NFS后端。

从原理上说,第一种方案最轻量,只有两台机器,数据通过DRBD在块设备层面实时复制,主节点故障后,备用节点接管虚拟IP并激活文件系统。第二种方案扩展性最好,文件数据打散到多个节点,天然不存在单点,但部署运维复杂度和资源消耗远高于双机方案。第三种方案体验最好,但硬件成本摆在那里,而且控制器切换也有自己的隐藏坑,比如切换后NFS连接需要重建。

2.2 关键取舍:一致性、切换时间与运维成本

我整理一个表格,把三种方案的关键指标放在一起看:

方案数据冗余方式故障切换方式切换耗时运维复杂度适用规模
DRBD+Keepalived双机块级实时镜像VIP漂移+主备服务切换5秒到30秒中单集群,PVC几十到几百
CephFS / GlusterFS多副本或纠删码客户端自动重连+元数据集群选主秒级高多集群,PB级数据
成品NAS双控制器硬件级磁盘镜像控制器故障切换秒级低视硬件规格而定

这三者里,CephFS的扩展性最好,但如果你只是给Kubernetes提供动态存储,Ceph的运维负担会显得特别重。OSD、Monitor、PG状态、网络带宽规划,每一项都需要花时间维护。成品NAS最省心,但很多团队的采购流程卡了几个月都批不下来。所以我个人在大多数项目里推荐DRBD+Keepalived双机方案,它能把高可用能力牢牢抓在自己手里,依赖的组件也少。

2.3 我最终选择的基线架构

我给朋友推荐的最终基线架构是这样的:两台物理机或虚机作为存储节点,每台节点配一块独立的本地数据盘。DRBD把两块数据盘做成主备镜像,对外暴露一个统一的块设备。Keepalived在这个块设备之上维护一个虚拟IP。NFS服务只在这个虚拟IP上监听,业务侧无论怎么挂载都只认这个IP。Kubernetes集群里的动态供应器指向虚拟IP和DRBD设备挂载出来的目录,PVC一申请,供应器就在目录里自动创建子目录并生成PV。

这套架构的优势在于:DRBD负责数据冗余,Keepalived负责IP故障转移,NFS负责文件协议转换,每层职责单一。生产环境下跑了一年多,切换过十几次,没有出现数据丢失的情况。后面所有配置都会围绕这套架构展开。

3. 核心原理拆解:Kubernetes动态存储供应是怎么工作的

3.1 静态供应与动态供应的区别

传统NFS使用者都熟悉静态供应流程:先手工在NFS服务端创建目录,然后手工到Kubernetes里创建PV对象,再创建PVC去绑定。问题是每次扩容都要重复一遍,目录多了以后命名混乱、回收困难,PV和真实目录之间的对应关系全靠人肉记忆。

动态供应解决了这个痛点。它把“创建目录”和“创建PV”这两件事自动化了。用户只需要写一个PVC,声明需要的容量大小和访问模式,平台就会通过一个叫做Provisioner的组件,自动在后端存储上创建出真实的存储空间,并生成对应的PV,然后完成PVC和PV的绑定。整个过程不需要运维预先手工准备任何目录。

3.2 StorageClass、Provisioner、PVC、PV的关系

这四个角色是理解Kubernetes存储动态供应最关键的概念。StorageClass是动态供应的“模板”,它定义了使用哪种Provisioner、回收策略是什么、是否允许扩容。Provisioner是真正干活的组件,它负责和后端存储交互,在NFS上创建目录、删除目录。PVC是用户发出的需求声明,表示我要一个多大容量、什么访问模式的存储。PV则是最终创建出来的存储资源对象,它描述的是Kubernetes集群里已经存在的、真实可用的存储。

用一句大白话概括:PVC是需求单,StorageClass是规则说明书,Provisioner是执行者,PV是交付物。用户写需求单,Provisioner按照规则说明书执行,最后把交付物绑定回需求单。

3.3 一次动态供应的完整申请链路

当用户在Kubernetes里提交一个PVC后,真正发生的事情是这样的:

  1. 用户创建PVC,PVC里指定了storageClassName。
  2. Kubernetes控制面发现这个PVC没有对应的PV,于是找到PVC指定的StorageClass。
  3. StorageClass里定义的Provisioner组件收到创建请求,触发器开始执行。
  4. Provisioner调用后端存储接口,在NFS共享根目录下创建出一个以“命名空间-PVC名称”命名的子目录。
  5. Provisioner创建一个PV对象,PV的nfs.path指向刚创建出来的那个子目录,nfs.server指向NFS虚拟IP。
  6. Kubernetes控制面检查到PV的容量、访问模式和PVC匹配,自动完成绑定。
  7. 之后任何Pod引用这个PVC,Kubelet都会通过PV里的NFS信息把对应子目录挂载进容器。

整个过程是自动化的,用户从提交PVC到Pod成功挂载,通常只需要几秒到几十秒。对这个链路理解越透,后面排查动态供应问题就会越顺手。很多人配置完Provisioner后PVC一直Pending,其实就是链路里某一环没打通,后面我会详细讲排查思路。

4. 实战搭建:NFS主备集群部署全程

4.1 环境规划与关键参数

我以一套双节点NFS高可用集群为例,所有配置都可以直接套用。环境如下:

作用主机名IP地址数据盘角色
存储节点一nfs-node1192.168.50.11/dev/sdb可主可备
存储节点二nfs-node2192.168.50.12/dev/sdb可主可备
NFS虚拟IP-192.168.50.100--

操作系统选择Debian系发行版,Kubernetes节点和NFS节点在同一个二层网络内。数据盘要求两块盘容量一致,建议使用独立的SSD或高性能云盘,不要和系统盘共用。节点之间需要开通TCP端口7788给DRBD使用,开通TCP/UDP 111和2049给NFS使用,以及Keepalived的VRRP组播需要放通。

在动手之前,先在两台节点上确认内核是否支持DRBD模块。Debian系可以直接安装drbd-utils,安装完成后用modprobe drbd加载内核模块,然后用lsmod | grep drbd确认模块状态。如果加载失败大概率是内核头文件和系统内核版本不匹配,需要先安装对应版本的linux-headers包。

4.2 DRBD数据镜像配置

DRBD的核心工作就是把一块数据盘从主节点实时复制到备节点。配置方式很直接,在两台节点上编辑/etc/drbd.d/r0.res,定义资源名为r0,并指定每台节点的设备路径和IP地址:

resource r0 { device /dev/drbd0; meta-disk internal; on nfs-node1 { disk /dev/sdb; address 192.168.50.11:7788; } on nfs-node2 { disk /dev/sdb; address 192.168.50.12:7788; } syncer { rate 200M; verify-alg sha1; } }

这里的syncer段用于控制同步速率,200M是比较稳妥的经验值。如果内网是万兆环境,可以调到500M以上,如果共享带宽建议调低。verify-alg配置了数据一致性校验算法,定期做verify时能发现镜像数据差异。

配置完成后在两台节点上分别执行:

drbdadm create-md r0 drbdadm up r0 drbdadm status

create-md是初始化DRBD元数据的过程,只需要执行一次。这一步如果提示磁盘上有文件系统,需要先确认数据盘确实没有历史数据,再用wipefs清理。初始化完成后,两台节点都会显示Secondary/Secondary,还没有人担任主节点。这时在一台节点上执行:

drbdadm primary r0 --force mkfs.ext4 /dev/drbd0 mkdir -p /data/nfs mount /dev/drbd0 /data/nfs

创建了文件系统并挂载后,DRBD会自动把整个文件系统数据同步到备节点。初始同步期间,业务可以使用主节点,但IO性能会受影响。drbdadm status里能看到SyncSource状态和同步百分比,等它到UpToDate/UpToDate才算完全就绪。

这里有一个很多人忽略的关键点:备节点不要执行任何mount /dev/drbd0操作。DRBD块设备在Secondary状态下不允许读写,强行挂载会报错。备节点的作用只是接收同步数据,文件系统在故障切换后才由备节点接管。

4.3 Keepalived VIP配置与切换脚本

DRBD只负责数据镜像,不负责对外提供稳定的服务地址。两台节点谁当主,对外IP就得跟着走。这里用Keepalived维护一个虚拟IP192.168.50.100,正常情况下由主节点持有,故障后自动漂移到备节点。

两台节点的主配置文件可以直接共享,全部设为state BACKUP并开启nopreempt,这样能避免主节点恢复后出现无意义的来回切换。配置如下:

vrrp_instance VI_NFS { state BACKUP nopreempt interface eth0 virtual_router_id 51 priority 100 advert_int 2 authentication { auth_type PASS auth_pass NFS_HA_PASS } virtual_ipaddress { 192.168.50.100/24 dev eth0 } notify_master "/usr/local/bin/nfs-master.sh" notify_backup "/usr/local/bin/nfs-backup.sh" }

notify_master和notify_backup是切换时调用的钩子脚本,这是整套方案能否真正自动化的关键。主节点成为VIP持有者时,脚本要做三件事:把DRBD提升为Primary、挂载DRBD设备到/data/nfs、启动NFS服务并导出共享目录。备节点脚本则反向操作:停止NFS服务、卸载文件系统、把DRBD降级为Secondary。

我给出一个简化版的主节点切换脚本,重点展示执行顺序:

#!/bin/bash drbdadm primary r0 mount /dev/drbd0 /data/nfs systemctl start nfs-kernel-server exportfs -ra

备份节点脚本:

#!/bin/bash exportfs -ua systemctl stop nfs-kernel-server umount /data/nfs drbdadm secondary r0

脚本执行顺序非常重要。如果先启动了NFS服务但DRBD还没升主,数据盘无法读取,导出目录就会变成不可用的空壳。反过来,先停NFS再卸载数据盘,可以确保正在读写的客户端连接先被切断,避免文件系统被异常卸载。生产环境建议在脚本里加set -e和一些日志输出,方便事后查看切换过程。

4.4 NFS服务与导出配置

两台节点都需要安装NFS服务端,配置好/etc/exports,但实际的导出动作只在主节点进行。导出目录建议指定为DRBD挂载点,而不是某个随意创建的路径:

/data/nfs 192.168.50.0/24(rw,no_root_squash,no_subtree_check,insecure,fsid=0)

参数说明:rw允许读写,no_root_squash让Kubernetes节点上以root运行的容器也可以正常访问NFS文件,no_subtree_check减少文件句柄校验的开销,insecure允许客户端从大于1024的端口发起NFS连接。fsid=0是NFSv4场景下比较稳妥的配置,避免不同主机导出路径的根文件句柄不一致。

这里要强调一个细节:NFS服务必须和VIP绑定在同一时间点启动。有些发行版的NFS服务启动后默认只监听本机配置的主IP地址,如果Keepalived已经把VIP设上去了,systemctl start nfs-kernel-server启动的服务会自然监听VIP地址。但如果是重启NFS服务导致VIP丢失,就会出现VIP漂移但NFS监听地址对不上的问题。解决思路是让Keepalived的notify脚本在启动NFS前确认VIP存在,并且NFS服务配置为监听0.0.0.0或预留地址,实际生产中我更倾向于直接配置/etc/default/nfs-kernel-server让服务绑定到虚拟IP。

4.5 故障切换演练与验证

配置完成后,优先做一次可控的故障切换演练,而不是等真的宕机了才验证。演练步骤建议分三步走。

第一步,正常状态下验证:在任意一台Kubernetes节点上挂载192.168.50.100:/data/nfs,写入测试文件,再读取确认。同时检查drbdadm status,确认主备状态一Primary一Secondary,ip addr确认VIP在预期节点上。

第二步,模拟主节点故障:直接shutdown -h now或reboot主节点。观察Keepalived是否在5秒到30秒内把VIP漂移到备节点,DRBD是否在合理时间内完成主备切换,NFS服务是否由notify脚本自动拉起。切完后立刻在客户端执行读文件和写文件操作,验证数据一致。

第三步,恢复原主节点:等原主节点启动后,DRBD会自动重新建立镜像同步。此时不要让Keepalived把VIP切回去,保持当前主节点继续服务。原主节点会以Secondary身份重新加入集群并同步数据,等同步完成后再考虑是否手动触发下一次切换。

演练中要特别关注两个指标:一是切换耗时,二是客户端恢复时间。NFS客户端对服务断电和VIP漂移的感知需要一定超时时间,如果客户端默认的timeo和retrans参数不合适,切换后业务要等几分钟才能恢复。合理的做法是给Kubernetes节点上的NFS挂载加上hard,timeo=600,retrans=2参数,这样切换时最多等几十秒就能重连。

5. 打通Kubernetes动态供应链路

5.1 部署NFS动态供应器

NFS后端就绪后,接下来在Kubernetes集群内部署动态供应器。这里选用社区维护的nfs-subdir-external-provisioner,它专门负责在既有NFS服务上创建带前缀或命名空间标识的子目录。

先创建ServiceAccount和RBAC权限。Provisioner需要监听PVC和StorageClass事件,以及创建和删除PV,所以权限要覆盖persistentvolumeclaims、persistentvolumes和storageclasses这几类资源。简化后的RBAC配置如下:

apiVersion: v1 kind: ServiceAccount metadata: name: nfs-provisioner-sa --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: nfs-provisioner-role rules: - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["list", "watch", "create", "update", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: nfs-provisioner-binding subjects: - kind: ServiceAccount name: nfs-provisioner-sa namespace: default roleRef: kind: ClusterRole name: nfs-provisioner-role apiGroup: rbac.authorization.k8s.io

然后是Deployment。核心环境变量有三个:PROVISIONER_NAME是Provisioner对外暴露的名字,必须与后续StorageClass里的provisioner字段完全一致;NFS_SERVER填VIP;NFS_PATH填DRBD挂载出来的NFS根目录。挂载方式直接用Kubernetes原生的nfs卷类型指向VIP和根路径:

apiVersion: apps/v1 kind: Deployment metadata: name: nfs-provisioner spec: replicas: 1 selector: matchLabels: app: nfs-provisioner template: metadata: labels: app: nfs-provisioner spec: serviceAccountName: nfs-provisioner-sa containers: - name: nfs-provisioner image: {your-registry}/sig-storage/nfs-subdir-external-provisioner:v4.0.2 env: - name: PROVISIONER_NAME value: example.com/nfs - name: NFS_SERVER value: 192.168.50.100 - name: NFS_PATH value: /data/nfs volumeMounts: - name: nfs-root mountPath: /persistentvolumes volumes: - name: nfs-root nfs: server: 192.168.50.100 path: /data/nfs

弹出一个疑问:Provisioner自己只有一个副本,如果它所在的节点挂了怎么办?注意Provisioner本身是控制面组件,不需要高可用双跑,Kubernetes会把它重新调度到其他节点。真正的高可用数据面是后端的NFS集群。

5.2 创建StorageClass并配置参数

Provisioner跑起来后,需要创建一个StorageClass把它暴露给业务。这里有个容易搞混的地方:provisioner字段的值必须和Deployment里的PROVISIONER_NAME一致,写错一个字PVC就永远Pending。

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-dynamic-sc provisioner: example.com/nfs reclaimPolicy: Retain volumeBindingMode: Immediate allowVolumeExpansion: true parameters: archiveOnDelete: "true" pathPattern: "${.PVC.namespace}-${.PVC.name}"

参数里值得解释的是archiveOnDelete和pathPattern。archiveOnDelete=true表示删除PVC时,NFS上的真实目录不会一起删除,而是改名归档,这对于误删PVC找回数据非常关键。如果设为false,PVC一删,底层目录直接清空,数据恢复基本无望。pathPattern用来定义创建子目录的命名规则,默认是“命名空间-PVC名称”,显式写出来更清晰。

reclaimPolicy建议设为Retain。动态供应的PV默认回收策略通常是Delete,如果PVC被删除,PV也会被删,Provisioner会在NFS上删除对应目录。实际生产中我建议改成Retain,这样即使PVC被不小心删掉,PV还在,底层目录还在,还有抢救的机会。

5.3 PVC创建与Pod挂载验证

StorageClass就绪后,创建一个PVC验证链路:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: web-data-pvc namespace: demo spec: accessModes: - ReadWriteMany storageClassName: nfs-dynamic-sc resources: requests: storage: 5Gi

提交后先看PVC状态:

kubectl get pvc -n demo kubectl describe pvc web-data-pvc -n demo

正常情况下PVC很快由Pending变为Bound。如果停在Pending,用describe看事件,常见原因有三个:StorageClass名字拼错、Provisioner的Deployment没有正常Running、NFS_PATH和实际导出目录不一致。

PVC绑定后,用一个临时Pod把PVC挂载进去测试写入:

apiVersion: v1 kind: Pod metadata: name: nfs-write-test namespace: demo spec: containers: - name: writer image: busybox:1.36 command: ["/bin/sh", "-c", "echo hello > /data/test.txt && sleep 3600"] volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName: web-data-pvc

Pod起来后,进入容器读取文件,再到NFS主节点上的/data/nfs/demo-web-data-pvc目录里检查文件是否真实落盘。如果两边都能看到同一份文件,整条动态供应链路就算完全打通了。

5.4 理解Provisioner的目录命名与回收策略

动态供应器创建目录后,NFS根目录下的目录名会根据pathPattern生成,比如demo-web-data-pvc。这里给大家提前打个预防针:一个集群用得越久,NFS根目录下的子目录就越乱。大量历史PVC、漂移残留、命名空间迁移遗留的目录堆在一起,如果没有归档规则很难维护。

建议从落地第一天就固化两件事:第一,pathPattern里带上命名空间,用“命名空间-PVC名称”命名,别用随机前缀或者不带namesapce的短名;第二,开启archiveOnDelete,PVC删除后目录会被重命名成带-archived-时间戳的形式,隔一段时间人工或者定时任务清理这些归档目录,既能保证误删可恢复,又不会让目录无限膨胀。

回收策略这块,我建议在测试环境先验证Delete和Retain两种策略的实际行为。很多团队在默认策略下用了一个月,直到某次误删PVC导致真实数据消失,才意识到NFS子目录一旦被Provisioner删除,是没有任何回收站可用的。提前把策略理解和配置成符合业务需求的形态,比事后恢复要轻松得多。

6. 生产环境踩坑记录与日常巡检建议

6.1 高频问题与根因对照

这套架构在生产环境跑了一段时间后,我积累了几个比较典型的问题。每个问题都值得拿出来单独说一下,因为它们在文档里很难看到完整体现。

问题现象根因解决方案
DRBD状态显示SplitBrain主备节点网络中断期间,两边都尝试提升为Primary配置fencing策略,恢复时手工确认数据较新的一方丢弃另一侧
Keepalived VIP来回漂移两台节点都配置了state MASTER,或者优先级设置不合理全部配置state BACKUP并开启nopreempt
NFS切换后客户端长时间不恢复客户端挂载参数是默认的soft或timeo太小在Kubernetes节点上配置hard,timeo=600,retrans=2
PVC删除后NFS目录还在,但PV被删reclaimPolicy为Delete,PV没了但Provisioner归档逻辑异常把delete回收策略换为Retain,检查Provisioner日志
NFS服务启动但监听地址是物理IP不是VIP服务启动时机早于VIP设置,或者监听了固定的主网卡IP调整启动顺序,或者配置NFS绑定到0.0.0.0,确保在master脚本中启动NFS

DRBD脑裂是双机主备方案最需要警惕的问题。脑裂发生后,两台节点各自认为自己是Primary,对外同时提供NFS服务,数据会出现分叉。解决思路有两个层面:一是预防,保证节点间心跳通道至少有两条,Keepalived的advert_int不要设得太短,也不要用共享网段做唯一心跳;二是止损,脑裂发生后不要自动恢复,先用drbdadm status确认两边数据状态,选取数据更新的那一边作为唯一权威节点,另一侧直接丢弃并重新同步。自动脑裂恢复在数据完整性上没有保障,宁可停机五分钟手动处理,也不要让机器自己瞎合并。

6.2 巡检脚本与监控告警

高可用不等于免维护。我给这套集群写过一个简单的巡检脚本思路,核心是五分钟一轮检查。检查项包括:两边的DRBD连接状态和角色、VIP落在哪台节点、NFS进程是否存活、导出目录是否正常、磁盘余量和inode余量、归档目录数量。

脚本核心片段大致长这样:

#!/bin/bash check_node() { local node=$1 ssh root@$node "drbdadm status; ip addr show eth0 | grep '192.168.50.100'; systemctl is-active nfs-kernel-server" } check_node 192.168.50.11 check_node 192.168.50.12

当然真实生产环境不会只靠脚本,会把drbd状态、VIP存活、磁盘空间、inode使用率、NFS服务状态这些指标接入监控系统。重点盯着两件事:DRBD连接状态是否持续Connected/UpToDate,以及VIP是否同时只存在一台节点上。如果监控面板上出现“两节点都显示Primary”或“VIP不归属任何节点”,要在睡觉前就把它处理掉,不要等天亮。

inode使用率是NFS场景里特别容易漏掉的一个指标。NFS树下一堆小文件,比如CI产物、npm包、对象存储分片,磁盘容量还很充裕但inode先把盘占满了,导致目录创建失败。Kubernetes动态供应器会报“create directory failed”,但排查时磁盘空间又显示正常。这个坑我踩过一次之后,把inode使用率永久加入了告警项。

6.3 数据备份与恢复思路

DRBD解决的是节点故障问题,不是逻辑错误和灾难恢复问题。如果有人误删了NFS目录,或者某段数据被业务写坏,DRBD的实时镜像会把错误也同步到备节点。所以备份动作不能省。

我的建议是三层备份。第一层,利用DRBD的数据一致性,定期在两个节点都健康时做快照,重点是确保快照时刻主备数据完全一致。第二层,用rsync把重要的NFS子目录增量同步到另一台独立的备份服务器,消费NFS导出的方式或者直接读文件系统的数据都可以,注意同步时对正在写入的目录加锁或选择业务低峰期。第三层,关键业务的数据目录由业务方自己维护备份策略,存储平台不能替业务承担所有备份责任。

恢复演练同样重要。每季度至少在测试环境做一次从备份服务器恢复数据的完整流程,记录从恢复开始到业务可用的总耗时。这个数字才是真实的可预期恢复时间,而不是PPT里写的那个“高可用秒级切换”。

还有一个恢复相关的细节:DRBD主备切换是不感知应用语义的,文件系统层面没问题不代表业务数据没问题。如果正在写一半的业务数据遇到切换,可能出现文件内容不完整的情况。这种问题无法通过存储层完全规避,只能业务侧自己做事务性处理或配置应用层面的降级策略。想清楚这一点,再去看存储层的高可用,心态会稳很多。

我自己现在维护这套NFS高可用集群时,最深的体会是:高可用真正的价值不是保证永不故障,而是让故障发生后的每一步都已知、可控、可验证。把切换脚本、巡检指标、恢复演练这些基础工作做到位,哪怕节点真挂了,也能从从容容地处理,而不是全团队盯着一个卡死的日志目录干着急。这也是我写下这篇过程记录的原因,希望给打算自己搭NFS高可用动态存储的同学一个相对完整的参考。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询