带本地盘的部署:升级能滚、迁移会丢数
带本地盘的部署:升级能滚、迁移会丢数
TL;DR 私有云上给有状态应用挂本地盘,看起来只是多一个 Volume 配置;真正难的是把「运维动作」拆成两套语义——同机升级/重启保留数据,跨机迁移/删除清理数据。盘在宿主机上,Pod 可以走,数据不会跟着走。产品必须把这句话写进每一个按钮的后果里,而不是写在事后工单里。
📌 本文要点
- 本地盘 ≠ 分布式存储:路径绑在物理机上,所有「搬家」语义都要单独设计
- 升级能滚的关键:业务 IP 与宿主机绑定不换,Pod 重建后仍挂回同一路径
- 迁移会丢数不是 bug:跨机 = 新盘;旧路径要主动清,否则脏数据会变成定时炸弹
- 宕机分两种:机器还能起来 → 原地恢复;机器彻底没了 → 只能迁 + 接受丢数或走业务侧恢复
- 产品折中:同机同集群实例上限、删除保留倒计时、操作互斥——都是在约束「本地盘的物理现实」
一、为什么还要本地盘?
无状态服务挂 emptyDir 或干脆不挂盘,Pod 随便调度,滚动升级、节点腾空都简单。
但私有云上总有一批应用就是要本地盘:
| 典型诉求 | 为什么网络盘/共享盘不够 |
|---|---|
| 日志/缓存落盘,吞吐要高 | 本地 SSD 延迟和 IOPS 更稳 |
| 中间件有状态目录 | 单机语义清晰,少一层网络故障域 |
| 成本与运维习惯 | 存量物理机本地盘「已经在那儿」,不必先上共享存储集群 |
于是平台会提供一类 本地存储集群:配置容器内挂载路径,对应到宿主机上的普通盘或 SSD 目录。路径形态可以抽象成:
宿主机根路径/
└── {集群名}/
└── {分组名}/
└── …(业务实际写入的子路径)
容器里看到的是业务配置的 mount path;宿主机上是平台按集群/分组拼出来的 hostPath。
这一层看起来只是挂载,后面所有运维语义的分叉,都从这里开始。
二、一个必须先钉死的物理事实
┌──────────── 物理机 A ────────────┐ ┌──────────── 物理机 B ────────────┐
│ 本地盘路径 │ │ 本地盘路径 │
│ /.../clusterX/groupY/ ← 数据在这 │ │ (空,或别的实例的数据) │
│ ▲ │ │ ▲ │
│ │ hostPath │ │ │ │
│ Pod P1 │ │ Pod P1'(迁过来之后) │
│ 业务 IP: 10.x.x.x │ │ 业务 IP: 10.x.x.x(可保留) │
└────────────────────────────────────┘ └────────────────────────────────────┘
数据不会自动复制 ─────────────────→
- 业务 IP 可以跟着实例逻辑身份走(平台做 IP 绑定/释放)
- 本地目录 只属于物理机 A,不会 magically 出现在 B
所以:
| 动作 | Pod | 业务 IP | 本地数据 |
|---|---|---|---|
| 同机重启 | 重建 | 可不变 | 在 |
| 同机升级(指定/滚动) | 重建 | 可不变 | 在 |
| 迁到别的物理机 | 重建 | 可不变 | 默认不在 |
| 删节点 / 删部署 | 消失 | 释放 | 清理 |
「升级能滚、迁移会丢数」不是两句营销话术,而是上表的产品承诺。
三、升级为什么「能滚」:把实例钉在机器上
无状态滚动升级常见套路:先起新 Pod → 就绪 → 再删旧 Pod。新 Pod 可能落在任意节点,无所谓。
本地盘集群不能这样玩。路径在宿主机上,新 Pod 必须回到原来那台机器,才能挂上原来的目录。
平台侧通常会组合几件事(实现细节因系统而异,语义一致即可):
-
节点选择记录
每个实例维护业务 IP ↔ 宿主机 IP ↔ Pod 名一类映射,部署与升级都查这张表。 -
调度约束
升级重建时用 node affinity / 指定候选宿主机 IP,把 Pod 钉回原机;不是「集群里随便找一台」。 -
版本标签与滚动
指定升级:只动某一个节点到目标版本,IP 与物理机绑定不变。
滚动升级:在部署或指定升级之后,整组推进版本,每个实例仍各自钉在原物理机上。 -
同机实例密度限制
默认「一台物理机上,同一集群不同分组的实例」有上限(例如 100)。
这不是拍脑袋:本地盘路径、磁盘 IO、故障域都按「同机多实例」放大,必须有天花板。
一句话概括升级路径:
换镜像、换配置、重建 Pod——可以;换宿主机——不行。
所以滚动升级在本地盘语义下是「原地滚动」,不是「自由调度滚动」。
用户感知上:点升级,实例版本变了,数据还在。这正是本地盘产品最该守住的承诺。
四、迁移为什么「会丢数」:搬家不等于搬盘
业务上「迁移」往往是:这台物理机要下线、磁盘满了、机房调整——实例必须挪到另一台机器。
对本地盘实例,迁移的真实步骤更接近:
操作者 → 部署平台:指定实例迁移
│
├─→ 物理机 A:摘除旧 Pod / 解除调度绑定
├─→ 物理机 A:清理旧 hostPath(异步,可失败重试)
├─→ 平台:选新宿主机 B(避开已占用列表)
├─→ 平台:业务 IP 在新节点侧重新绑定
└─→ 物理机 B:创建 Pod + 新路径挂载
结果:旧数据默认不搬;新路径是空目录
几个容易误解的点:
1. 「IP 还在」≠「数据还在」
迁移链路里常会做 IP 重置/再绑定,让业务入口尽量不变。
但这只保证网络身份连续,不保证磁盘内容连续。两者是解耦的。
2. 旧路径必须主动清理
旧机上的目录若留下:
- 占磁盘;
- 下次若调度巧合挂回同路径,可能读到过期/脏数据;
- 合规与租户隔离也不允许「删了实例磁盘还在」。
所以迁移/删除后会触发 按宿主机 + 路径的远程删除(节点 agent 删目录),失败则重试、节点 NotReady 则告警——清理是一等公民操作,不是可选项。
3. 这是产品边界,不是实现偷懒
本地盘产品若承诺「跨机迁移数据不丢」,就要自带:
- 应用层复制 / 主从同步;
- 或共享存储 / 网络盘;
- 或显式的「备份 → 迁 → 恢复」流水线。
私有云常见折中是:平台不假装能搬本地盘,把「跨机 = 数据重新初始化」写清楚,把「同机升级保数据」做扎实。
应用若不能接受丢数,应走有状态中间件自己的副本机制,而不是指望调度器搬目录。
五、一张操作后果表(比文档正文更重要)
内部说明文档里,本地存储操作几乎可以压成下面这张表。对外产品文案也应该长这样,而不是只写「支持本地盘」。
| 操作 | 业务 IP / 物理机 | 本地数据 |
|---|---|---|
| 初次部署 | 新绑定 | 新路径(空) |
| 指定升级 | 绑定不变 | 保留 |
| 滚动升级 | 绑定不变 | 保留 |
| 同机重启 | 绑定不变 | 保留 |
| 迁移到其他物理机 | 换物理机(IP 可重置保留) | 清理(丢) |
| 指定节点删除 | 规模 -1 | 清理 |
| 批量删除 | 规模缩小 | 清理 |
| 删除整次部署 | 全部释放 | 清理 |
把这张表钉在控制台每个危险按钮旁,比写十页 ARCHITECTURE 都管用。
六、宕机:能重启 vs 不能重启,是两种事故
本地盘故障域的边界就是那台物理机。
物理机可重启
机器还能起来(内核 panic 后 reboot、短暂掉电恢复等):
- 宿主机上的本地目录还在;
- 节点 Ready 后,实例按原绑定拉起;
- 数据不丢——这是本地盘相对「一迁移就清空」的最大红利。
平台侧还可给强依赖本地盘/DB 类工作负载加长 not-ready / unreachable 容忍时间,减少「机器只是抖一下就被误迁」的概率(误迁在本地盘语义下等于主动丢数)。
物理机不可重启
硬盘坏了、机器报废、整机被拔线:
- 旧路径物理上没了或再也访问不到;
- 只能 手动迁移到备用机;
- 平台语义下:数据丢失,或依赖业务事先备份/多副本自行恢复。
这里没有魔法。诚实的产品文案应是:
单机本地盘不提供跨机高可用。HA 请用应用层副本或网络存储;本地盘只保证「同机生命周期内」的数据连续。
七、围绕「丢数」的产品缓冲:保留与清理倒计时
纯「删了就清盘」太狠;纯「永不清理」又脏。常见折中是 Hold 数据:
- 删除/缩容时可选「暂保留本地数据(及可选保留 IP)」;
- 后台任务按
holdTime倒计时; - 到期前告警;到期后删路径、解绑 IP、删映射记录;
- 保留期内支持查询、续期、恢复或主动删除。
这解决的是操作误触与变更窗口,不是跨机 HA。
它承认:清理必须发生,但给人和业务一点缓冲——这和「迁移默认丢数」并不矛盾。
八、资源与密度:本地盘集群不是无限叠罗汉
异常场景里高频两条:
- 物理机资源不足 → 加机器,而不是硬塞;
- 同机同集群实例触顶(默认 100 这类上限) → 加机器或调配额。
本地盘集群的容量规划,不能只看 CPU/内存配额,还要看:
- 单机磁盘容量与 IO;
- 路径数量与清理失败堆积;
- 故障时「一台机器挂掉影响多少实例」。
上限就是在提醒:你在用故障域很尖的存储模型。
九、和 K8s 原生概念怎么对齐(便于对外解释)
若用社区语言对照,本地存储集群接近:
| 平台语义 | 接近的 K8s 概念 |
|---|---|
| 宿主机目录挂进容器 | hostPath / Local PV |
| 升级钉死原机 | node affinity / 固定 nodeName 一类约束 |
| 迁移 = 新机新盘 | 不是 PVC 在线迁移;更像删旧 PV 声明 + 新节点重建 |
| 有状态副本 | 应用自己负责;平台 StatefulSet 也解决不了「本地盘自动搬家」 |
社区 Local PersistentVolume 同样强调:调度要考虑卷在哪台节点,而不是卷跟着 Pod 飞。
私有云本地盘产品,本质是把这套约束做成了运维按钮级的产品语义。
十、写给使用方与平台方
使用方(业务/运维)
- 选本地盘前先回答:单机挂了能否接受重建?有没有应用层副本或备份?
- 日常变更优先同机升级/重启,不要把「迁移」当常规扩缩手段。
- 物理机下线流程:先业务侧迁主/备份 → 再平台迁移 → 默认按丢数验收。
- 删除类操作看清表:缩容、删节点、删部署都会清盘;需要缓冲就用保留期,不要赌「平台没删干净」。
平台方(做产品的人)
- 每个写操作写清数据后果,升级/迁移/删除三分法,禁止模糊文案。
- 清理与部署同级:异步删目录、NotReady 告警、失败重试、事件可查。
- 迁移与升级互斥:避免半升级半迁移把绑定表写乱。
- 不要用「自动迁移」掩盖本地盘限制:自动迁 = 自动丢数,除非上层已确认无状态或已有副本。
- 宕机演练分两种:reboot 恢复 vs 整机报废迁走,验收标准完全不同。
十一、收束
本地盘部署的核心矛盾就一句:
计算可以编排,本地磁盘不能瞬移。
所以产品必须诚实:
- 升级能滚:因为我们把实例钉在原物理机上,路径还在,数据还在;
- 迁移会丢数:因为换机就是新路径,旧路径要清,平台默认不帮你搬磁盘。
这不是功能缺陷,是存储模型的边界。
边界讲清楚了,本地盘才是好用的性能与成本工具;边界糊成「和普通集群一样随便迁」,线上丢数就只是时间问题。
附录:机制速查
同机(升级 / 重启)
绑定表:IP + host 不变
调度:钉回原 host
路径:原 hostPath 继续挂
数据:保留
跨机(迁移)
绑定表:host 变;IP 可重置保留
调度:新 host
路径:新 hostPath(空)+ 旧路径删除
数据:默认丢失
删除(节点 / 批量 / 部署)
路径删除(可 Hold 倒计时)
映射与 IP 按策略释放
数据:清理(Hold 只是延期)
若你的环境还在「有状态也当无状态迁」,不妨先把上面三行贴进变更 checklist——比再写一篇事故复盘便宜得多。