III
VOL. 03 — STORAGE & CLUSTER CORE

存储 / 调度 / 核心组件

THE OPS FIELD MANUAL
PV · PVC · etcd · API Server
Node · Scheduler · kubelet
Production Edition
PVC Pending 挂载失败 节点 NotReady etcd 异常 API Server 慢 证书过期

存储和集群核心组件的故障,影响范围比 Pod 故障大一个数量级。一个 etcd 抖动可能让整个集群停摆,一个 PV 挂错可能让 StatefulSet 起不来,一个证书过期能让 kubectl 直接罢工。这一卷专治"看起来不太对劲但又说不清楚"的疑难杂症。

PART II · 节点与调度
01

PVC 一直 Pending,绑定不到 PV

DYNAMIC PROVISIONING OR STATIC BINDING FAILURE
高频 必会
AVAILABLE
PV 已创建,未绑定
BOUND
PV 与 PVC 已绑定
RELEASED
PVC 删了,PV 还在
FAILED
回收失败
RECYCLED
已回收,可复用
  • 没有匹配的 PV(静态供给场景):storageClass、accessModes、capacity 任一不匹配
  • StorageClass 不存在(动态供给场景),或 provisioner 异常
  • 没有默认 StorageClass,但 PVC 没指定
  • NFS 服务器不通、卷插件 Pod 异常
# 1. 看 PVC 状态和事件
kubectl describe pvc <pvc-name>
# 重点看 Events 段,会明确告诉你哪里不匹配

# 2. 列出可用 PV(静态场景)
kubectl get pv
# 看 STATUS=Available 的 PV,对比 PVC 要求

# 3. 检查 StorageClass(动态场景)
kubectl get storageclass
kubectl get sc -o wide
# 默认的 SC 后面会有 (default) 标识

# 4. 检查 provisioner Pod
kubectl -n kube-system get pod | grep -E "nfs|csi|provisioner"
kubectl -n kube-system logs <provisioner-pod> --tail=50
模式简写含义典型存储
ReadWriteOnceRWO单节点读写云盘、本地盘
ReadOnlyManyROX多节点只读NFS、CephFS
ReadWriteManyRWX多节点读写NFS、CephFS
ReadWriteOncePodRWOP单 Pod 读写(K8s 1.22+)云盘
常踩 · MySQL 用 NFS 存数据是反模式。NFS 锁机制和 InnoDB 不兼容,容易数据损坏。生产环境数据库一定要用块存储(RWO)。
kubectl patch storageclass <sc-name> -p \
  '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
02

挂载失败 MountVolume.SetUp failed

VOLUME ATTACH OR MOUNT ERROR
高频 进阶
  • NFS 服务端不通,或共享路径不存在
  • 节点上没装挂载工具(NFS 缺 nfs-utils,Ceph 缺 ceph-common)
  • 云盘已挂载到其他节点,RWO 模式不允许多挂
  • 权限问题:Pod runAsUser 没有 NFS 共享目录的写权限
  • fsGroup 未设置,导致挂载后 Pod 没法写
# 1. 详细错误信息
kubectl describe pod <pod> | grep -A 10 MountVolume

# 2. 节点上手动挂载测试 NFS
ssh <node>
showmount -e <nfs-server-ip>
mount -t nfs <nfs-ip>:/data /mnt/test

# 3. 检查挂载工具
rpm -qa | grep nfs-utils    # CentOS
apt list --installed | grep nfs-common  # Ubuntu

# 4. 看节点 kubelet 日志
journalctl -u kubelet -f | grep -i mount
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000   # 关键!挂载后会把卷的属组改成 fsGroup,Pod 可写
  containers:
    - name: app
      image: myapp
      volumeMounts:
        - name: data
          mountPath: /data
NFS 服务端配置 · 共享目录要有 no_root_squash 选项,否则 Pod 内 root 用户写入会变成 nobody。生产环境不建议这么配,而是用 fsGroup + 非 root 用户。
03

PV 卡 Terminating / Released

FINALIZER OR RECLAIM POLICY ISSUE
中频 进阶
  • PVC 已删,但 PV 的 reclaimPolicy 是 Retain,变成 Released 状态(正常,但需手动处理)
  • finalizer kubernetes.io/pv-protection 未清理
  • 底层存储删除失败(云盘 API 调用失败)
# 1. 看 PV 详情
kubectl describe pv <pv-name>

# 2. Released 状态:删除 claimRef 让它回到 Available
kubectl patch pv <pv> -p '{"spec":{"claimRef":null}}'

# 3. Terminating 卡死:移除 finalizer(谨慎!确认底层存储已清)
kubectl patch pv <pv> -p '{"metadata":{"finalizers":null}}'
策略PVC 删除后使用建议
Retain保留 PV 和数据,变 Released生产数据,需人工干预
DeletePV 和底层存储一起删测试环境、临时存储
Recycle清空数据后变 Available(已废弃)不推荐使用
finalizer 误删的代价 · 直接 patch 移除 finalizer 会绕过 K8s 的资源保护机制。如果底层云盘 API 还没成功删除,云盘会变成"孤儿盘",持续计费。
04

StatefulSet 重建后数据丢失

PVC NAMING AND VOLUMECLAIMTEMPLATES PITFALL
低频 致命
  • StatefulSet 删了重建,Pod 起来后数据全没了
  • 明明配了 volumeClaimTemplates,看起来没生效
  • volumeClaimTemplates 创建的 PVC 命名规则:<template>-<sts-name>-<序号>
  • 删除 StatefulSet 不会删除 PVC(这是设计如此,保护数据)
  • 但如果 PV 是 reclaimPolicy: Delete,删 PVC 时数据就没了
  • 重建时如果改了 sts 的名字,新 PVC 不会绑到老 PV
# 删除 StatefulSet 但保留数据(标准做法)
kubectl delete sts <name> --cascade=orphan
# 或先用这个命令把 PVC 的 reclaimPolicy 改成 Retain

# 查看 sts 关联的 PVC
kubectl get pvc | grep <sts-name>
生产铁律 · 任何挂载关键数据的 PV,reclaimPolicy 必须改为 Retain,即使 StorageClass 默认是 Delete 也要 patch 单个 PV 修改。删数据前用 kubectl get pv -o yaml > backup.yaml 备份元数据。
05

节点 NotReady,Pod 全飘移

KUBELET / NETWORK / RUNTIME FAILURE
高频 必会
  • kubelet 服务挂了或 OOM
  • 容器运行时(containerd/docker)异常
  • 节点磁盘满 / inode 满 → kubelet 拒绝调度
  • 节点和 master 网络不通
  • CNI 插件 Pod 异常,网络初始化失败
  • kubelet 证书过期
# 1. 看节点状态详情
kubectl describe node <node> | grep -A 5 Conditions
# 关注 Type/Status/Reason,定位是哪类问题

# 2. 登录节点检查关键服务
ssh <node>
systemctl status kubelet
systemctl status containerd  # 或 docker

# 3. kubelet 日志(最有价值)
journalctl -u kubelet --since "10 min ago" | tail -100

# 4. 节点资源
df -h
df -i
free -h
top

# 5. 网络连通性
ping <master-ip>
curl -k https://<master-ip>:6443

# 6. 重启 kubelet
systemctl restart kubelet
Condition TypeTrue 含义排查方向
Ready节点正常False 时看其他 Conditions
MemoryPressure内存压力大free -h, 找内存大户
DiskPressure磁盘压力大df -h, 清理镜像和日志
PIDPressure进程数过多ps aux 看进程,可能是 fork 炸弹
NetworkUnavailable网络异常检查 CNI
06

节点磁盘压力,清理实战

DISK PRESSURE CLEANUP PLAYBOOK
高频 入门
# 1. 找大目录
du -h --max-depth=1 / 2>/dev/null | sort -hr | head
du -h --max-depth=1 /var/lib | sort -hr

# 2. 容器镜像占用(通常是 90% 的元凶)
crictl images
crictl rmi --prune    # containerd 清理悬挂镜像

# Docker 环境
docker system df
docker system prune -af --volumes  # 清所有未使用资源

# 3. 容器日志(单个容器日志默认无上限!)
find /var/lib/docker/containers/ -name "*-json.log" -size +500M
find /var/log/pods/ -name "*.log" -size +100M

# 4. 系统日志
journalctl --vacuum-time=7d
journalctl --vacuum-size=500M
# 修改 /etc/docker/daemon.json (Docker)
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

# containerd: kubelet 配置 --container-log-max-size=100Mi
清理顺序 · 先清未使用的,再清旧版本镜像。docker system prune -af 加上 --volumes 会一并清理 dangling 卷,误删风险高,生产慎用。
07

API Server 响应慢 / kubectl 卡顿

CONTROL PLANE PERFORMANCE DEGRADATION
中频 高级
  • etcd 慢(磁盘 IO 瓶颈,90% 是这个)
  • 大量 LIST 请求(没有 resourceVersion 的 list 全表扫描)
  • 大量 watch 连接耗尽 fd
  • API Server 内存不足,频繁 GC
  • 认证后端慢(LDAP/OIDC 上游响应慢)
# 1. 测 API Server 响应
time kubectl get nodes
time kubectl get pods -A

# 2. 看 API Server 日志
kubectl -n kube-system logs -l component=kube-apiserver --tail=100
# 重点看 "slow request" 警告

# 3. API Server 指标
kubectl get --raw /metrics | grep apiserver_request_duration

# 4. 谁在频繁请求 API(找出元凶)
kubectl get --raw /metrics | grep apiserver_request_total | sort | tail
常见的 API 风暴源 · 自定义 controller 写 reconcile 死循环,不停 update 资源;Operator bug 反复创建删除资源;监控系统每秒 list 全集群 Pod。
08

etcd 异常,集群整体不可用

DATABASE BACKEND CRITICAL FAILURE
低频 致命
  • kubectl 全部超时
  • API Server 日志大量 "etcd connection refused"
  • etcd 日志出现 "took too long" / "leader election"
# 1. 看 etcd Pod 状态
kubectl -n kube-system get pod | grep etcd

# 2. 集群健康检查
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  endpoint health

# 3. 看成员列表和 leader
etcdctl member list
etcdctl endpoint status --write-out=table

# 4. etcd 数据库大小(默认 quota 2GB,超了会拒写)
etcdctl endpoint status --write-out=json | grep dbSize

# 5. 数据库压缩(碎片整理)
etcdctl compact <rev>
etcdctl defrag
etcd 灾难恢复 · 必须有定期快照备份。etcdctl snapshot save backup.db 可热备份。所有 etcd 节点同时挂掉是最严重情况,只能从快照恢复,会丢失最后一次快照之后的数据。
预防 · etcd 一定要 SSD,IOPS 至少 5000+。监控 etcd_disk_wal_fsync_duration_seconds,p99 超过 25ms 就该报警。
09

证书过期,集群组件全部失联

CERTIFICATE EXPIRATION — KUBEADM CLUSTERS
低频 致命
  • kubectl 报 x509 错误:certificate has expired
  • kubelet 日志大量 TLS handshake error
  • 节点全部 NotReady,Pod 无法调度
# 1. 检查证书过期时间(每个 master 都要查)
kubeadm certs check-expiration
# 输出每个证书的剩余有效期

# 2. 一键续签所有证书(kubeadm 集群)
kubeadm certs renew all

# 3. 重启 control plane 组件让新证书生效
cd /etc/kubernetes/manifests
mv kube-apiserver.yaml /tmp/
sleep 30   # 等 kubelet 删掉旧 Pod
mv /tmp/kube-apiserver.yaml .
# controller-manager 和 scheduler 同样操作

# 4. 更新 kubeconfig
cp -f /etc/kubernetes/admin.conf $HOME/.kube/config

# 5. 验证
kubeadm certs check-expiration
kubectl get nodes
kubelet 证书自动轮转 · kubelet 客户端证书默认 1 年,但启用 rotateCertificates: true 后会自动续签。生产环境务必开启,不然 1 年后所有节点变 NotReady。
预警 · 加监控:每天定时 kubeadm certs check-expiration,小于 30 天就报警。
10

kubectl 连不上集群

KUBECONFIG / NETWORK / AUTH ISSUE
高频 入门
报错含义解决
connection refusedAPI Server 端口不通检查 6443 端口、防火墙
x509 expired证书过期kubeadm certs renew
x509 unknown authorityCA 不匹配kubeconfig 用错集群
Unable to connect to serverkubeconfig 无效检查 KUBECONFIG 环境变量
ForbiddenRBAC 没权限看 ClusterRoleBinding
Unauthorizedtoken/证书无效重新生成 kubeconfig
# 1. 看当前 context
kubectl config current-context
kubectl config view --minify

# 2. 测 API Server 端口连通性
curl -k https://<api-server>:6443/healthz

# 3. 详细模式看请求过程
kubectl get nodes -v=8

# 4. RBAC 权限问题:看自己有什么权限
kubectl auth can-i --list
kubectl auth can-i get pods -n default

"运维不是把出问题的修好,而是让该出的问题
在出现之前就被发现
监控、告警、备份、演练,缺一不可。"