Longhorn 实战:让 Pod 真正跨节点漂移(含 SeaweedFS 备份 + 离线安装)
环境前置:k0s 单节点(controller + worker 合一),WSL2,私有镜像仓库
172.29.102.221:5000,SeaweedFS 已用 local-path 跑起来充当内网 OSS。
一、local-path 的问题:Pod 被钉死在节点上
做 K8s 实验时,几乎所有人都从 local-path-provisioner 起步。它极简——一个 Deployment 加一个 ConfigMap,PVC 一声明,节点目录就建好,PV 就绑定。开发体验一流。
但它的本质就一句:把节点本地目录动态包装成 PV。这带来一个绕不开的硬伤——
1 | |
PV 一旦在某节点创建,就永久绑定到这个节点。Pod 哪怕被删掉重建,调度器也会因为 PV 节点亲和性,被迫调度回原节点。Pod 没有真正的迁移自由,只是”假装能调度”。
单节点集群上这个问题被彻底放大:
| 故障 | 数据 | Pod 能否飘走 |
|---|---|---|
| 节点临时重启/网络抖动 | 不丢 | ❌ 本来就一个节点,没地方飘 |
| 节点磁盘坏/重装系统 | 丢 | ❌ |
| 想扩一个 worker 节点分担负载 | 不丢 | ❌ 老数据全锁在原节点上 |
更要命的是 local-path 的容量写法会被直接忽略——PVC 声明 100Gi,实际能写到撑爆根盘为止,没有真正的限额。
我之前部署 TiDB 时也踩过这个坑:local-path 下的 TiKV Store 强绑定节点,下线一个节点要走 Region 迁移 + Store Tombstone + 删 PVC 的完整流程,半天都收不回来。单节点 + local-path,本质上和 Docker 的 -v /host:/container 没区别,K8s 调度的弹性被掏空了。
二、Longhorn 是什么:把存储也变成可调度的微服务
Longhorn 是 CNCF 孵化项目(Rancher 出品),定位是云原生分布式块存储。一句话概括它的设计哲学:
把传统存储里那个”大而全的控制器”,拆成”每个卷一个轻量 Engine”。每个 PV 都是一个独立的微服务,故障域被隔离到单个卷。
两层架构
1 | |
Engine 把同一份数据同步写到 N 个节点上的 Replica。Pod 挂载的块设备挂的是 Engine,Engine 知道哪个 Pod 在用、哪些 Replica 是健康的。这正是它能实现”卷跟随 Pod 跨节点迁移”的根本原因。
它怎么让 Pod 飘起来
这是和 local-path 最大的本质区别。Longhorn 的卷和节点没有强绑定:
- Pod 调度到 Node B → Engine 在 Node B 启动 → 挂载 Node B 上的副本(或通过已有的副本恢复)
- Node B 故障 → Pod 被重新调度到 Node C → Engine 在 Node C 重新启动,从健康的副本拉起数据
- 节点扩容后,把副本数从 1 调到 2/3,数据自动在新节点重建,老 PV 不会被锁死
单节点场景下副本数设为 1,持久性确实和 local-path 一个级别(数据只有一份),但 Longhorn 给的是一套未来可平滑演进的体系——哪天你加了第二个 worker 节点,把 numberOfReplicas 改成 2,立刻就有跨节点冗余,老卷迁移过去即可。local-path 没有这个升级路径。
local-path vs Longhorn(副本=1)对比
| 维度 | local-path | Longhorn (副本=1) |
|---|---|---|
| 底层 | 节点本地目录 | 块设备 + 独立 Engine |
| PV 节点亲和 | 强绑定 hostname | 跟随 Pod 调度 |
| 跨节点迁移 | ❌ 不支持 | ✅ Pod 飘哪,卷去哪 |
| 容量限额 | ❌ 忽略 | ✅ 真实限额 |
| 快照 (COW) | ❌ | ✅ |
| 备份到 S3/NFS | ❌ | ✅ |
| 在线扩容 | ❌ | ✅ |
| 多节点冗余演进 | ❌ 无路径 | ✅ 改副本数即可 |
| 复杂度 | 极简 | 重(Manager/Engine/CSI 全套) |
一句话:local-path 是”动态 hostPath”,Longhorn 是”自带调度能力的分布式块存储”。
三、离线安装:所有镜像走内网仓库
内网环境无法直接拉 docker.io。核心思路:用 skopeo copy 把所有镜像从公网同步到私有仓库 172.29.102.221:5000,Helm 安装时通过 global.imageRegistry 指过去。
1. 节点前置依赖
V1 引擎依赖 iSCSI,RWX/备份依赖 NFS 客户端:
1 | |
必备 shell 工具(longhornctl 预检会查):bash curl findmnt grep awk blkid lsblk。
2. 安装预检工具
1 | |
3. WSL2 特殊处理(关键)
WSL2 下挂载传播默认不全,不加这一步 DaemonSet 会起不来:
1 | |
建议写进 /etc/rc.local 或 systemd unit,重启后自动执行。
4. 同步镜像到私有仓库
官方镜像清单在 longhorn/longhorn deploy/longhorn-images.txt,共 14 个。同步脚本(以核心组件为例,CSI 侧同理):
1 | |
生产建议用
skopeo sync --src yaml --dest registry批量同步整个清单文件,比 for 循环更稳。
5. Helm 安装(指向内网仓库)
1 | |
关键参数解释:
defaultReplicaCount=1:单节点必须改成 1,默认 3 会导致 PVC 卡在 Pending(找不到足够节点调度副本)。defaultDataPath=/opt/longhorn:数据落到本机目录,必须是 ext4/xfs,不要用软链接(ln -s),需要别名用mount --bind。global.imageRegistry:所有组件镜像统一走私有仓库,绕开docker.io拉取。
6. 验证
1 | |
四、StorageClass:单节点必须改副本数
Helm 改的是默认值,但StorageClass 里的 numberOfReplicas 会覆盖默认值。如果 StorageClass 还写 "3",单节点上 PVC 依然会卡 Pending。这是个非常常见的坑。
1 | |
创建一个测试 PVC + Pod 验证卷能动态供应:
1 | |
五、接 SeaweedFS 做 S3 备份(闭环)
单副本意味着数据只有一份,节点磁盘坏了就没了。Longhorn 自带增量备份到 S3 的能力,正好把已有的 SeaweedFS 当成内网对象存储来用——既不依赖公网云厂商,又给了数据一份异地副本。
我的环境里 SeaweedFS 已经用 local-path 跑起来,对外暴露 S3 兼容端点
http://172.29.102.221:30083,预创建了 bucketlonghorn-backup。SeaweedFS 提供 S3 协议、bucket 语义、版本控制和公开读,对 Longhorn 来说就是一个”私有 OSS”。
1. 创建备份凭据 Secret
Longhorn 读的是标准 AWS 环境变量,自定义 S3 兼容存储(MinIO/SeaweedFS)必须额外提供 AWS_ENDPOINTS:
1 | |
2. 配置默认 BackupTarget
⚠️ 版本变更(容易踩坑):从 Longhorn 1.8 起的”多备份目标”改造后,默认备份目标不再是老的
settings.longhorn.io,而是一个名为default的BackupTargetCRD。网上很多教程(包括 1.7 时代的)还在让你kubectl edit setting backup-target,这条命令在 1.12 里已经被废弃,改了不生效。helm --set defaultSettings.backupTarget同理——只对全新安装生效,已运行的集群改它不会回流到 CR。
先看一眼默认 BackupTarget 的现状(通常 URL 是空的、状态 Unavailable):
1 | |
填进去有两种等价方式,推荐第一种。
方式 A:直接 patch BackupTarget CR(最直接)
1 | |
URL 格式拆解:s3://<bucket>@<region>/<path>/
longhorn-backup= SeaweedFS 里预创建的 bucket 名@us= region 占位符,自定义 S3 兼容存储(MinIO/SeaweedFS)写us即可- 结尾的
/必须有,否则 Longhorn 报错
方式 B:通过 longhorn-default-resource ConfigMap(声明式,会被 controller reconcile 到 CR)
这个 ConfigMap 是 1.8+ 引入的”默认资源”统一入口,字段名和老 setting 类似:
1 | |
1 | |
Longhorn Manager 里的 configmap controller 会监听它,几秒内自动 reconcile 到 default 这个 BackupTarget CR 上。如果你直接 patch CR 又同时维护这个 ConfigMap,记得两者保持一致——否则 controller 下次 reconcile 可能用 ConfigMap 覆盖你对 CR 的手动改动。
验证 BackupTarget 已生效:
1 | |
AVAILABLE=true 就说明 Longhorn 已经成功连上 SeaweedFS 的 S3 端点了。如果一直是 false,看 conditions.message——最常见的是凭据错、AWS_ENDPOINTS 没写、或 bucket 不存在。
3. 创建定时备份
在 Longhorn UI → Backup → 创建 Recurring Job,或直接用 CRD:
1 | |
然后给目标卷打上 label 让它纳入计划:
1 | |
4. 验证备份落地
1 | |
或者直接看 Longhorn UI → Backup 页面,应该能看到 SeaweedFS 里的备份条目。
六、收尾:什么场景该用什么
回到选型本身,给一张决策表:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 纯开发/临时缓存/数据丢了无所谓 | local-path | 最轻,资源占用几乎为零 |
| 单节点测试 + 想要快照/备份/扩容 | Longhorn (副本=1) | 持久性和 local-path 同级,但功能完整 |
| 多节点 + 要 HA | Longhorn (副本=3) | 跨节点冗余,节点挂了 Pod 能真飘走 |
| 临时挂载配置/单机调试 | hostPath | 不需要 CSI 的最简形态 |
单节点 ≠ 没有数据保护。 把 SeaweedFS 接到 Longhorn 的 BackupTarget,就完成了”本地存储 + 异地备份”的闭环——磁盘坏了,从 SeaweedFS 拉备份恢复;节点扩了,把副本数调高,数据自动在新节点重建。
这套组合在 homelab、私有云、离线内网里都跑得起来,唯一的代价是 Longhorn 比起 local-path 要重不少。值不值,看你的数据值不值。
参考链接
- Longhorn 官方文档:https://longhorn.io/docs/1.12.0/
- Local Path Provisioner:https://github.com/rancher/local-path-provisioner
- SeaweedFS S3 配置:https://github.com/seaweedfs/seaweedfs/wiki/AWS-CLI-with-SeaweedFS
- Longhorn 镜像清单:https://github.com/longhorn/longhorn/blob/v1.12.0/deploy/longhorn-images.txt