TiDB 集群 local-path 存储下,如何安全维护 Kubernetes 节点(PD + TiKV 踩坑实录)

环境:TiDB Operator + TidbCluster(v7.5.6),StorageClass 为 local-path(本地存储不可自动迁移),PD / TiKV 均为 3 副本
参考:本文的整体思路来自 PingCAP 官方文档《维护 TiDB 集群所在的 Kubernetes 节点》,在此基础上补充了本地存储场景下的实操细节和踩坑记录(含 PD 和 TiKV 两条完整链路)。

一、问题背景

集群使用 local-path 作为 StorageClass,PV 与节点是强绑定的,也就是官方文档里说的”节点存储不可自动迁移“的场景。这种场景下,无论是 TiKV 还是 PD,只删除 Pod 是没用的——PVC 跟着节点走,Pod 重建后大概率还会调度回同一个节点,甚至因为本地盘丢失而起不来。

本文以把调度到磁盘空间较小节点(ai-node)上的 tidb-standalone-tikv-1 迁移走为主线,同时补充了 PD Pod 的对应处理方式,因为线上运维中这两者往往是同一个节点下线场景下需要一起处理的。

二、整体思路:先判断存储能否自动迁移

官方文档把节点维护分成两大类:

存储类型 TiKV 处理方式 PD 处理方式
存储可自动迁移(如 EBS) 只需迁移 Region Leader,再删 Pod 即可,Store 不用下线 只需迁移 Leader,再删 Pod 即可,Member 不用下线
存储不可自动迁移(如本地盘,本文场景) 需要完整下线 Store(Tombstone)后再删 PVC + Pod 需要下线 Member(pd-ctl member delete)后再删 PVC + Pod

我们用的是 local-path,属于第二类,所以 PD 和 TiKV 都要走”先下线、等状态收敛、再解绑存储”的完整流程。

三、前置检查

在下线 TiKV 之前,必须先确认:

下线后剩余的 TiKV 数量,必须 ≥ PD 的 max-replicas(默认 3)

本次场景中集群只有 3 个 TiKV,直接下线会变成 2 个,不满足条件,常见两种处理方式:

  1. 先扩容到 4 个,再下线目标节点(最稳妥,生产环境推荐,也是官方文档给出的方式)
  2. 临时把 max-replicas 改成 2(本次实际采用的方式,操作完成后必须改回来)
1
2
3
4
5
# 查看当前值
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl config show | grep max-replicas

# 修改为 2
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl config set max-replicas 2

四、标记节点不可调度

1
kubectl cordon ai-node

先看一下这个节点上到底有哪些 PD / TiKV Pod(这也是官方文档推荐的排查方式,用 grep 过滤节点名):

1
2
kubectl get pod --all-namespaces -o wide | grep ai-node | grep tikv
kubectl get pod --all-namespaces -o wide | grep ai-node | grep pd

五、TiKV 迁移流程(本地存储,需完整下线 Store)

1. 驱逐 Leader

给 TiKV Pod 打上 tidb.pingcap.com/evict-leader annotation,触发 Region Leader 迁移:

1
kubectl -n middleware annotate pod tidb-standalone-tikv-1 tidb.pingcap.com/evict-leader="none"

确认 leaderCount 降为 0。这里有个小坑:status.tikv.stores 在 TidbCluster CRD 里是一个以 store ID 为 key 的 map,不是数组,用 kubectl jsonpath 遍历时要用 .* 而不是 [*];官方文档里用的是 jq,两种写法都贴一下:

1
2
3
4
5
6
# jsonpath 写法
kubectl -n middleware get tc tidb-standalone -o jsonpath='{range .status.tikv.stores.*}{.podName}{"\t"}{.leaderCount}{"\n"}{end}'

# jq 写法(官方文档采用的方式,按 podName 精确过滤更方便)
kubectl -n middleware get tc tidb-standalone -o json \
| jq '.status.tikv.stores | .[] | select(.podName=="tidb-standalone-tikv-1") | .leaderCount'

2. 下线指定 Store

先查 store-id:

1
kubectl -n middleware get tc tidb-standalone -o jsonpath='{range .status.tikv.stores.*}{.podName}{"\t"}{.id}{"\t"}{.state}{"\n"}{end}'

确认后下线(本例 store-id 为 1):

1
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store delete 1

此时 store 状态变为 Offline,PD 开始把上面的 Region 迁走。

3. 加速 Region 迁移(可选)

默认 store limit 较低时迁移会很慢,可临时调高:

1
2
3
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store limit 1 100000000 remove-peer
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store limit 4 100 add-peer
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store limit 5 100 add-peer

4. 等待 Store 变为 Tombstone

1
kubectl exec -n middleware tidb-standalone-pd-0 -- watch /pd-ctl store 1

重点关注 state_name 是否变成 Tombstoneregion_count 是否降为 0。

🔑 这是全流程最关键的一步:千万不要提前删除 PVC。

5. 解绑 PVC 并重建 Pod

1
2
3
kubectl -n middleware get pvc -l tidb.pingcap.com/pod-name=tidb-standalone-tikv-1
kubectl delete -n middleware pvc <pvc-name> --wait=false
kubectl delete -n middleware pod tidb-standalone-tikv-1

等新 Pod 状态变为 Up,新的 store-id 会自动生成,Region Leader 也会逐步调度回来。

6. 清理 evict-leader-scheduler(容易被遗漏的一步)

官方文档特别提到,迁移完成后要记得清掉之前自动生成的 evict-leader-scheduler,否则可能残留调度策略:

1
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl scheduler remove evict-leader-scheduler-1

六、PD 迁移流程(本地存储,需下线 Member)

如果同一个节点上还有 PD Pod,处理逻辑和 TiKV 类似,但下线的对象是 PD Member 而不是 TiKV Store:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 确认该节点上的 PD Pod
kubectl get pod --all-namespaces -o wide | grep ai-node | grep pd

# 2. 查看当前 PD Leader
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl member leader show

# 3. 如果 Leader 恰好在待维护节点上,先把 Leader 迁移到其他节点的 PD Pod
# ${pod_name} 是其他节点上的 PD Pod
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl member leader transfer ${pod_name}

# 4. 下线待维护节点上的 PD Member
# ${pod_name} 是待下线的、自己所在节点的 PD Pod
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl member delete name ${pod_name}

# 5. 确认该 Member 已经从列表中消失
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl member

# 6. 解绑 PVC
kubectl -n middleware get pvc -l tidb.pingcap.com/pod-name=${pod_name}
kubectl delete -n middleware pvc ${pvc_name} --wait=false

# 7. 删除 Pod,让 Operator 重建到其他节点
kubectl delete -n middleware pod ${pod_name}

注意几个和 TiKV 流程不一样的地方:

  • PD 下线用的是 member delete,不是 store delete;也没有 Tombstone 这种中间态,member delete 之后基本立即从 pd-ctl member 列表消失。
  • PD Leader 迁移用的是 member leader transfer,不是 TiKV 的 evict-leader annotation。
  • 如果待下线 Pod 本身就是 Leader,一定要先 transfer 走,否则下线过程中可能触发一次不必要的 Leader 选举抖动。

七、踩坑记录:提前删除 PVC 导致的问题(TiKV 场景)

本次操作中,在 TiKV store 还处于 Offlineregion_count 尚未降为 0 时就删除了 PVC,导致新 Pod 启动失败,报错:

1
duplicated store address: ... already registered by id:1

原因:旧 store(id:1)的地址仍然注册在 PD 中,新 TiKV 用相同地址注册时被 PD 拒绝。

解决方法

1
2
3
4
5
6
7
8
# 再次确认状态
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store 1

# 强制移除失败的 store(谨慎使用)
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl unsafe remove-failed-stores 1

# 删除异常 Pod 让其重建
kubectl delete -n middleware pod tidb-standalone-tikv-1

⚠️ unsafe remove-failed-stores 属于强制操作,会直接丢弃尚未迁移完成的副本。本次因为已将 max-replicas 临时调整为 2,且集群中仍有其他健康 TiKV,风险相对可控,但生产环境使用前务必再三确认数据安全性——这一步官方文档里没有提到,是本地存储 + 提前删 PVC 组合出的额外坑,供大家避雷。

八、后续清理

1
2
3
4
5
6
7
8
9
10
11
12
# 1. max-replicas 改回 3
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl config set max-replicas 3

# 2. store limit 改回默认值(通常为 15)
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store limit 4 15 add-peer
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store limit 5 15 add-peer

# 3. 确认所有 store 状态正常
kubectl exec -n middleware tidb-standalone-pd-0 -- /pd-ctl store

# 4. 恢复节点调度
kubectl uncordon ai-node

九、总结与最佳实践

场景 推荐做法
只有 3 个 TiKV,需要下线一个 优先扩容到 4 再下线;或临时改 max-replicas=2
local-path 存储的 TiKV 迁移 必须走”驱逐 Leader → store delete → 等 Tombstone → 删 PVC → 删 Pod → 清理 evict-leader-scheduler”
local-path 存储的 PD 迁移 必须走”迁移 Leader → member delete → 确认 member 列表 → 删 PVC → 删 Pod”
Region 迁移过慢 调高 store limit(add-peer / remove-peer
提前删 PVC 导致 duplicated store address unsafe remove-failed-stores 强制处理,注意数据风险
用 jsonpath / jq 查询 status.tikv.stores 它是 map 不是数组:jsonpath 用 .*,或直接用官方推荐的 jq

核心原则:本地存储场景下,TiKV 靠 Store 的 Tombstone 状态、PD 靠 Member 是否从列表中消失来判断”是否可以安全删 PVC”,两者都不能提前动手;同时建议提前对目标节点执行 kubectl cordon,防止新 Pod 又被调度回去。


TiDB 集群 local-path 存储下,如何安全维护 Kubernetes 节点(PD + TiKV 踩坑实录)
https://www.boer.xyz/posts/tidb-local-storage-k8s-node-maintenance/
作者
boer
发布于
2026年7月29日
许可协议