Kubernetes 基础体系 · 第 72/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 节点排障:Kubelet、Runtime、DiskPressure、PID 和网络
Kubernetes 节点上的故障,通常不是单个进程“挂了”这么简单。一个 Pod 能否正常运行,至少要经过以下链路:
API Server
│
├── Node 对象、PodSpec、Lease
│
▼
Kubelet ─────── CRI ─────── 容器运行时
│ │
│ ├── sandbox
│ ├── container
│ └── image
│
├── CNI:Pod 网络
├── Probe:存活、就绪、启动探针
├── cAdvisor / cgroup:资源统计
└── eviction manager:驱逐决策
因此,“Pod 一直 Pending”“Pod 变成 Unknown”“节点 NotReady”“容器启动失败”“节点 DiskPressure”可能分别来自控制面、Kubelet、CRI Runtime、CNI、文件系统、PID 资源或探针,而不应只查看 kubectl describe pod 的最后一行事件。
本文使用当前 Kubernetes 稳定 API 的通用概念。具体配置项、默认阈值和节点服务路径会受到 Kubernetes 版本、Linux 发行版、容器运行时、云厂商镜像以及 kubeadm 或托管集群实现的影响。
一、先建立故障模型:节点状态从哪里来
1. Node 对象、Condition、Lease 和 Taint 不是同一个东西
Kubernetes API 中的 Node 对象包含节点的身份、容量、可分配资源、地址、状态和污点等信息。常用状态字段包括:
status.conditions:节点条件,例如Ready、DiskPressure、PIDPressure、MemoryPressure、NetworkUnavailable。status.capacity:节点理论资源容量。status.allocatable:调度器认为可以分配给 Pod 的资源。spec.taints:调度和驱逐使用的污点。metadata与spec.providerID:节点标识和云平台关联信息。Lease对象:节点心跳。
Condition 描述“节点当前观察到的状态”,而 Taint 描述“调度器和控制器应如何对待该节点”。二者经常相关,但不是同一个字段。
例如:
kubectl get nodes
kubectl describe node <node-name>
kubectl get node <node-name> -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{" message="}{.message}{"\n"}{end}'
kubectl get lease -n kube-node-lease <node-name> -o yaml
kubectl get node <node-name> -o jsonpath='{.spec.taints}{"\n"}'
可能看到:
Ready=False reason=KubeletNotReady message=container runtime is down
DiskPressure=True reason=EvictionThresholdMet message=nodefs.available...
PIDPressure=False reason=KubeletHasSufficientPID
这几行应分别解释:
- Kubelet 是否认为自己能正常工作;
- 本地磁盘或 inode 是否触发资源压力;
- 节点 PID 是否接近耗尽;
- 是否有 Lease 心跳;
- 是否有
node.kubernetes.io/not-ready、node.kubernetes.io/unreachable等污点。
2. Lease 和 Node Status 的数据流
Kubelet 会定期更新节点状态,并更新位于 kube-node-lease 命名空间中的同名 Lease。Lease 的 spec.renewTime 是轻量级心跳;Node Status 更新包含更多信息,例如 Condition、地址和容量。
控制器管理器中的 Node Lifecycle Controller 观察节点心跳。如果节点在一段时间内没有有效心跳,控制器会把节点标记为不可用,并可能添加:
node.kubernetes.io/not-ready
node.kubernetes.io/unreachable
具体等待时间由集群配置决定,不能把某个固定秒数当成 Kubernetes API 保证。
故障路径可以抽象为:
sequenceDiagram
participant K as Kubelet
participant L as Lease
participant A as API Server
participant C as Node Controller
participant S as Scheduler
participant P as Pod
K->>A: 更新 Node Status
K->>L: 更新 Lease renewTime
C->>L: 检查心跳
C->>A: 设置 NotReady / 添加 Taint
S->>A: 调度新 Pod
S-->>P: 避免调度到带有不容忍污点的节点
一个常见误解是:“Ready=False 后,所有 Pod 会立即从节点消失。”实际情况取决于:
- 节点控制器是否已经识别到故障;
- Pod 是否有匹配的
tolerationSeconds; - Pod 是否由 Deployment、StatefulSet、DaemonSet 等控制器管理;
- 节点是否只是 Kubelet 失联,还是节点本身已经断电;
- 网络恢复后,旧 Pod 是否仍在节点上运行。
NotReady 是控制面观察到的节点状态,不是远程关机命令;它不会自动保证节点上的进程已经停止。
二、第一阶段诊断:先区分控制面、Kubelet 和 Runtime
1. 最小证据集
遇到节点异常时,先收集同一时间窗口内的四类信息:
NODE=<node-name>
kubectl get node "$NODE" -o wide
kubectl describe node "$NODE"
kubectl get events --all-namespaces \
--field-selector involvedObject.kind=Node,involvedObject.name="$NODE" \
--sort-by=.lastTimestamp
kubectl get pods -A -o wide --field-selector spec.nodeName="$NODE"
kubectl get lease -n kube-node-lease "$NODE" -o yaml
这些命令的意义不同:
get node:快速查看Ready和压力条件;describe node:查看 Conditions、Taints、容量、分配量和事件;- Node 事件:查看压力、心跳和驱逐相关记录;
- 按节点列 Pod:判断是全节点故障还是个别工作负载故障;
- Lease:判断 Kubelet 是否仍能访问 API Server 并执行心跳更新。
如果节点 SSH 可达,再查看主机服务:
sudo systemctl status kubelet --no-pager
sudo journalctl -u kubelet --since "30 min ago" --no-pager
sudo systemctl status containerd --no-pager
sudo journalctl -u containerd --since "30 min ago" --no-pager
sudo crictl info
sudo crictl ps -a
sudo crictl pods
crictl 直接通过 CRI 访问 Runtime,不经过 Kubernetes API。它的 endpoint 应由 /etc/crictl.yaml 或发行版配置明确指定,例如:
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
不要在未知环境中随意把 endpoint 改成 Docker socket、旧的 containerd socket 或不存在的路径。错误 endpoint 会制造“Runtime 没有容器”的假象。
2. 典型判别
| 现象 | 更可能的方向 |
|---|---|
Node Lease 持续更新,Ready=True,单个 Pod 启动失败 |
Pod、镜像、探针、CNI 或应用问题 |
| Lease 停止更新,SSH 也不可达 | 主机、电源、网络或内核问题 |
| Lease 停止更新,但 SSH 可达 | Kubelet 卡死、API Server 路径断开、CPU/IO 饥饿 |
| Kubelet 日志出现 CRI RPC timeout | Runtime 失效、Runtime 线程阻塞、磁盘或内核资源异常 |
crictl info 失败,但 Kubelet 仍运行 |
Runtime socket、权限、协议或 Runtime 本身问题 |
DiskPressure=True,但 df -h 还有空间 |
inode、单独的 imagefs、eviction 统计、挂载点或 deleted-open 文件 |
PIDPressure=True,但进程数量看起来不多 |
PID cgroup 限制、线程数、容器内 PID 限制或统计范围不同 |
| Pod 能启动但访问不了 Service | CNI、kube-proxy、路由、iptables/nftables、conntrack 或 DNS |
三、Kubelet:节点上的期望状态协调器
1. Kubelet 做什么
Kubelet 运行在每个节点上,核心职责包括:
- 从 API Server 获取分配给本节点的 PodSpec;
- 对静态 Pod 读取本地清单;
- 根据 PodSpec 计算本地应达到的状态;
- 通过 CRI 创建或删除 Pod sandbox 和容器;
- 通过 CNI 配置 Pod 网络;
- 执行 startup、liveness、readiness 探针;
- 采集节点和容器资源状态;
- 在资源压力下执行 Pod 驱逐;
- 向 API Server 汇报 Node Status 和 Lease。
Kubelet 不是“容器进程本身”。它通过 Runtime 管理容器,通过 API Server 汇报状态;因此:
- Kubelet 存活不代表容器运行正常;
- Runtime 存活不代表 Kubelet 能访问 API Server;
- Node 显示
Ready=True不代表每个应用都健康; - Pod 显示
Running不代表应用已经可以接收流量。
2. Pod Sync 的基本过程
Kubelet 的目标不是执行一次命令,而是持续收敛:
期望状态:
PodSpec 要求 sandbox、init container、app container、volume、probe
实际状态:
Runtime 当前有哪些 sandbox 和 container
CNI 当前是否有网络
volume 是否已挂载
进程是否仍在运行
差异:
缺少对象 → 创建
配置不一致 → 重建或重启
多余对象 → 清理
一个简化的同步过程是:
flowchart TD
A[PodSpec / 静态Pod / 内部事件] --> B[Pod Manager]
B --> C[Pod Worker]
C --> D[生成容器与Sandbox动作]
D --> E{CRI调用}
E --> F[Runtime创建Sandbox]
F --> G[CNI配置网络]
G --> H[创建容器]
H --> I[执行启动后状态检查]
I --> J[更新PodStatus和事件]
J --> A
这里的循环不是严格的单线程顺序。Kubelet 内部有多个 worker、缓存和异步事件;所以日志中可能先看到“创建容器”,随后才看到状态事件,不能只按日志文本顺序推断所有因果关系。
3. PLEG:Runtime 状态变化如何被 Kubelet 发现
PLEG,即 Pod Lifecycle Event Generator,用于发现 Runtime 中 Pod 和容器状态变化,并把变化交给 Kubelet 的 Pod Sync 流程。
常见日志包括:
PLEG is not healthy
GenericPLEG: pleg was last seen active ...
PLEG 异常通常不是根因,而是结果。常见根因有:
- Runtime API 响应慢或卡住;
- 节点磁盘 IO 长时间阻塞;
- 容器数量过多,状态扫描成本升高;
- Runtime 数据库或 shim 异常;
- CPU 被高负载进程长期占用;
- 内核、cgroup 或文件系统出现问题。
诊断时应同时查看:
sudo journalctl -u kubelet --since "15 min ago" --no-pager | \
egrep -i 'PLEG|runtime|sync|pod sandbox|container'
sudo journalctl -u containerd --since "15 min ago" --no-pager | \
egrep -i 'error|timeout|shim|snapshot|overlay|failed'
time sudo crictl pods
time sudo crictl ps -a
如果 crictl pods 本身就很慢,优先检查 Runtime 和磁盘,而不是先重启 Kubelet。重启 Kubelet 可能暂时清除症状,但不会修复 Runtime 数据库、文件系统或镜像目录。
四、Kubelet 与容器运行时:CRI、Sandbox 和容器生命周期
1. CRI 是什么
CRI,即 Container Runtime Interface,是 Kubelet 与容器运行时之间的 gRPC 接口。Kubelet 通过 CRI 请求 Runtime 执行容器生命周期操作,主要包括:
- RuntimeService:Pod sandbox、容器创建、启动、停止、删除、状态查询;
- ImageService:拉取、列出、删除镜像和查询镜像状态。
Kubernetes 不规定 Runtime 必须使用 containerd 或 CRI-O,但要求 Runtime 提供兼容的 CRI 接口。Docker Engine 曾经通过 dockershim 被 Kubernetes 间接使用;dockershim 已从 Kubernetes 1.24 移除,现代集群不能假设 Docker socket 就是有效 CRI endpoint。
CRI 中的 Pod 不是单个容器,而是:
Pod
└── Pod sandbox
├── infra/pause 容器或等价网络持有者
├── init container
└── app container
Sandbox 通常承载 Pod 的网络命名空间。应用容器启动失败时,sandbox 可能仍然存在;因此:
sudo crictl pods
sudo crictl ps -a
sudo crictl inspectp <pod-id>
sudo crictl inspect <container-id>
可以区分:
- sandbox 创建失败:常见于 CNI、Runtime、网络命名空间问题;
- sandbox 成功、镜像拉取失败:镜像仓库、认证、DNS 或磁盘问题;
- 容器创建失败:挂载、权限、cgroup、Runtime 配置问题;
- 容器启动后立即退出:应用、entrypoint、信号处理或资源问题。
2. 一个完整的启动故障推导
假设 Pod 事件显示:
FailedCreatePodSandBox
failed to setup network for sandbox
不要直接得出“应用容器坏了”。正确的链路是:
- Kubelet 收到 PodSpec;
- Kubelet 通过 CRI 请求创建 sandbox;
- Runtime 创建网络命名空间;
- Runtime 调用 CNI ADD;
- CNI 分配地址、配置接口和路由;
- CNI 失败;
- sandbox 未达到可运行状态;
- 应用容器尚未进入正常启动阶段。
因此应检查:
kubectl describe pod <pod> -n <namespace>
sudo journalctl -u kubelet --since "20 min ago"
sudo journalctl -u containerd --since "20 min ago"
sudo ls -la /etc/cni/net.d/
sudo ls -la /opt/cni/bin/
ip link
ip route
CNI 插件的具体日志位置不由 Kubernetes API 统一规定,可能在 DaemonSet Pod 日志、宿主机日志或插件自己的目录中:
kubectl get pods -n kube-system -o wide
kubectl logs -n kube-system <cni-pod> --all-containers --since=20m
3. Runtime 恢复后的验证顺序
Runtime 重启是有风险的操作,因为运行中的容器可能短暂失去管理面,部分 Runtime 或 shim 的恢复行为也依版本和配置而异。生产环境中应先确认:
sudo systemctl is-active containerd
sudo crictl info
sudo crictl pods
sudo crictl ps
kubectl get node <node>
验证重点不是“服务进程存在”,而是:
- CRI endpoint 可以响应;
- 已有 sandbox 和容器可以被列出;
- Kubelet 能重新观察到 Runtime 状态;
- Node Condition 恢复;
- 新 Pod 可以创建;
- 原有 Pod 的 readiness 和业务流量恢复。
如果 Runtime 反复崩溃,应保存 Runtime 日志、内核日志和磁盘状态后再重启,避免丢失根因证据。
五、Node Condition 与 NotReady 的具体含义
1. Ready
Ready=True 表示 Kubelet 认为节点满足正常运行 Pod 的基本条件。它不是应用健康检查,也不等价于:
- 所有 Pod 都是 Ready;
- Pod 网络可访问所有外部系统;
- 磁盘永远不会触发驱逐;
- Runtime 没有延迟;
- 节点没有业务层面的故障。
Ready=False 的 reason 和 message 很重要,例如:
KubeletNotReady
container runtime is down
runtime network not ready
PLEG is not healthy
应把它们作为线索,而不是最终结论。runtime network not ready 可能源于 CNI 初始化失败;PLEG is not healthy 可能源于 Runtime 卡顿或磁盘 IO,而非 PLEG 自身有独立服务。
2. Unknown
当控制面无法从节点获得足够的新状态时,节点可能进入 Unknown。这通常意味着心跳中断,不一定意味着 Kubelet 进程已经退出。例如:
- API Server 到节点的返回路径断开;
- 节点防火墙丢弃连接;
- Kubelet 被 CPU 或 IO 饥饿;
- 节点宕机;
- TLS、认证或 DNS 路径异常。
排障时同时从控制面和节点发起测试:
# 控制面侧
kubectl get --raw="/api"
kubectl get lease -n kube-node-lease <node-name> -o yaml
# 节点侧
curl -k https://<api-server-address>:<port>/healthz
ip route
ss -tnp
sudo journalctl -u kubelet --since "20 min ago"
节点侧的 curl 只验证网络和 HTTP/TLS 路径,不代表 Kubelet 的客户端证书、授权和请求路径一定正确。
3. 污点如何影响调度和驱逐
典型系统污点包括:
node.kubernetes.io/not-ready
node.kubernetes.io/unreachable
node.kubernetes.io/disk-pressure
node.kubernetes.io/memory-pressure
node.kubernetes.io/pid-pressure
node.kubernetes.io/network-unavailable
调度器默认不会把不容忍这些污点的 Pod 调度到节点上。已经运行的 Pod 是否被驱逐,则取决于污点容忍、控制器行为和节点控制器的处理过程。
查看容忍关系:
kubectl get pod <pod> -n <namespace> -o yaml
不要把以下操作当作修复:
kubectl taint node <node-name> node.kubernetes.io/disk-pressure:NoSchedule-
这只是删除污点。如果 Kubelet 仍然报告磁盘压力,污点可能再次出现;即使污点暂时消失,也可能允许新的 Pod 进入一个无法安全运行的节点。
六、DiskPressure:磁盘空间、inode 和驱逐机制
1. DiskPressure 不等于 df -h 使用率高
DiskPressure=True 表示 Kubelet 的驱逐管理器认为一个或多个磁盘相关信号低于配置阈值。可能涉及:
nodefs.available:节点文件系统可用空间;nodefs.inodesFree:节点文件系统可用 inode;imagefs.available:镜像文件系统可用空间;imagefs.inodesFree:镜像文件系统可用 inode;- 其他版本或布局相关的统计信号。
因此,df -h 只覆盖空间,不覆盖 inode,也不一定覆盖 Kubelet 识别的独立 imagefs。
检查主机:
df -hT
df -ih
findmnt
lsblk -f
检查常见占用目录:
sudo du -xhd1 /var/lib/kubelet 2>/dev/null | sort -h
sudo du -xhd1 /var/lib/containerd 2>/dev/null | sort -h
sudo du -xhd1 /var/log 2>/dev/null | sort -h
sudo du -xhd1 /var/lib/docker 2>/dev/null | sort -h
/var/lib/docker 不应在所有节点上都存在,也不代表当前 Runtime 正在使用它。必须先确认 Runtime 配置和实际挂载点。
2. 为什么删除文件后空间不一定恢复
Linux 中,文件名被删除并不等于文件占用的 inode 和数据块立即释放。如果进程仍持有打开的文件描述符,空间会保持占用:
sudo lsof +L1
输出中常见 REG 文件的 LINK 数为 0。处理方式不是直接删除更多日志,而是定位持有文件的进程,并通过安全的日志轮转、服务重载或服务重启释放描述符。
另一个边界是挂载点。du -x 不跨文件系统,避免把挂载在 /var/lib/kubelet 下的独立文件系统重复计算;而普通 du 可能把结果混在一起,造成判断错误。
3. 阈值的形式化理解
对某个资源信号 ,Kubelet 可以比较“当前可用值”与驱逐阈值:
当阈值使用百分比时,通常可理解为:
例如,某文件系统容量为 100 GiB,可用 8 GiB,则:
如果有效阈值是 10%,则该信号满足:
Kubelet 还会考虑 eviction-minimum-reclaim,它表示触发驱逐后希望至少回收多少资源。阈值决定“何时进入压力状态”,minimum reclaim 决定“回收目标不能只停在刚刚越过阈值的位置”。
实际配置可通过 Kubelet 配置文件或启动参数查看,不能假设所有集群都使用同一默认值:
ps -ef | grep '[k]ubelet'
sudo grep -R "eviction" /var/lib/kubelet /etc/kubernetes 2>/dev/null
4. DiskPressure 的完整故障路径
日志、容器可写层、镜像、卷或 inode 增长
│
▼
Kubelet 资源统计发现可用值低于阈值
│
▼
DiskPressure=True
│
├── 节点可能获得 disk-pressure:NoSchedule 污点
│
└── 驱逐管理器选择 Pod
│
├── 受 QoS、优先级、资源使用和是否超过请求等因素影响
└── Pod 被终止,释放临时存储或容器资源
这里存在一个重要边界:节点磁盘压力与 Pod ephemeral-storage 请求/限制有关,但不是完全相同的机制。
- Pod 的
emptyDir、容器可写层、容器日志等可能消耗临时存储; - Pod 声明
ephemeral-storage请求后,调度器可以把它作为可调度资源; - Pod 超过临时存储限制时,可能被驱逐;
- 宿主机上的系统日志、镜像缓存、Runtime 元数据也可能造成节点压力;
- Kubelet 是否能精确统计某类数据,依赖文件系统布局和统计能力。
5. DiskPressure 的安全恢复顺序
先确认:
kubectl describe node <node-name>
df -hT
df -ih
sudo du -xhd1 /var/lib/kubelet /var/log 2>/dev/null
sudo crictl images
然后按来源处理:
- 无用镜像:使用 Runtime 支持的镜像清理方式;
- 容器日志:通过日志轮转配置控制增长,不要直接删除正在写入的文件;
/var/log:检查 systemd journal、审计日志和应用日志;emptyDir:找出具体 Pod,并确认删除数据是否会破坏业务;- deleted-open 文件:重载或重启持有进程;
- inode 耗尽:查找大量小文件,而不是只删除大文件;
- 独立 imagefs:清理实际挂载的镜像文件系统。
不建议直接执行:
sudo rm -rf /var/lib/kubelet/*
sudo rm -rf /var/lib/containerd/*
这会破坏 Kubelet、Runtime 的状态和正在运行的 Pod,可能造成节点无法恢复。只有在明确理解节点重建、数据丢失和控制器重建后果时,才应执行有针对性的清理。
恢复验证不能只看 df:
kubectl get node <node-name> -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{"\n"}{end}'
kubectl describe node <node-name>
kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name>
应确认 DiskPressure=False、相关污点消失或不再重新出现,且新 Pod 能正常创建。
七、PIDPressure:进程、线程和 PID cgroup 的资源耗尽
1. PID 是什么
Linux 中,每个进程和线程都需要 PID 资源。Kubernetes 节点上的 PID 可能来自:
- Kubelet、Runtime、系统服务;
- 每个 Pod 的容器进程;
- 应用创建的子进程;
- 应用线程,具体表现取决于统计路径和内核/cgroup 配置;
- 僵尸进程占用的进程表项,直到父进程回收。
Kubernetes 使用 pid.available 表达节点 PID 资源的剩余情况。抽象地说:
当可用 PID 低于配置阈值时,Kubelet 可能设置:
PIDPressure=True
阈值可按绝对数量或百分比配置,具体支持和默认值取决于 Kubernetes 版本及 Kubelet 配置。
2. “进程不多”为什么仍可能 PIDPressure
先查看节点级进程:
ps -eLf | wc -l
ps -e -o stat= | awk '$1 ~ /Z/ {z++} END {print "zombies=" z+0}'
cat /proc/sys/kernel/pid_max
再查看 cgroup。现代系统常使用 cgroup v2,但路径和层级取决于发行版:
stat -fc %T /sys/fs/cgroup
find /sys/fs/cgroup -name pids.current -o -name pids.max 2>/dev/null | head
如果定位到某个 cgroup:
cat <cgroup-path>/pids.current
cat <cgroup-path>/pids.max
可能出现三种不同情况:
- 节点全局 PID 接近上限;
- 某个 Pod 的
pids.max很低; - 大量僵尸进程存在,但总进程数量未达到直观预期;
- 应用线程数暴涨,
ps -eLf比ps -ef大很多; - Kubelet 统计的是节点资源,而操作者查看的是某一个容器或某一种统计口径。
3. 典型进程泄漏算例
假设节点 PID 容量为 32,768,系统和已有 Pod 使用 31,000 个 PID:
如果阈值是 2,000,则:
Kubelet 会认为 PID 资源不足。此时即使磁盘有 500 GiB 可用、内存也有 20 GiB 可用,节点仍可能进入 PIDPressure。
反例是:某个 Pod 内部创建了大量进程,但该 Pod 的 cgroup pids.max 先被耗尽。此时该容器可能报:
fork: Resource temporarily unavailable
但节点级 PIDPressure 不一定为 True。这是“容器级 PID 限制”和“节点级 PID 压力”两个层次,不能混为一谈。
4. PIDPressure 的恢复
定位进程来源:
ps -e -o pid,ppid,user,stat,etime,cmd --sort=-etime | head -50
ps -eLf -o pid,ppid,tid,user,stat,cmd --sort=-pid | head -50
pstree -ap
如果怀疑某个 Pod:
kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name>
kubectl top pod -A --containers 2>/dev/null
kubectl top 依赖 Metrics Server,且主要反映 CPU/内存,不是 PID 的权威来源。PID 仍应以节点 /proc、cgroup 和 Runtime 视角为准。
恢复方法依来源不同:
- 应用 fork 泄漏:修复应用或滚动重启;
- 僵尸进程:修复父进程的
wait()逻辑,不能只杀僵尸本身; - 单 Pod PID 限制:调整合理的 PID 限制,但要防止失控;
- 节点系统服务泄漏:修复服务并释放资源;
- 节点已无法可靠运行:隔离节点后重建通常比强行清理安全。
八、网络排障:区分节点网络、Pod 网络、Service 和 DNS
1. Kubernetes 网络不是一条链路
至少要区分四层:
应用进程
│
├── Pod loopback / Pod eth0
│
├── CNI 创建的 Pod 网络
│
├── Node 主机网络、路由、iptables/nftables、conntrack
│
├── Service 虚拟 IP / kube-proxy 或等价实现
│
└── 外部网络、云路由、安全组、负载均衡
另有 DNS 层:
Pod → resolv.conf → CoreDNS Service → CoreDNS Pod → 上游 DNS
因此,“Pod 不能访问服务”可能是:
- Pod 没有 IP;
- Pod 网卡或默认路由缺失;
- CNI 节点插件异常;
- 节点到其他节点的路由不通;
- kube-proxy 规则没有安装;
- Service 没有 Endpoint;
- CoreDNS 不可达;
- 网络策略拒绝;
- 云安全组或外部防火墙丢包。
2. 先判断失败边界
查看 Pod 状态和地址:
kubectl get pod <pod> -n <namespace> -o wide
kubectl describe pod <pod> -n <namespace>
kubectl get svc <service> -n <namespace>
kubectl get endpointslice -n <namespace> \
-l kubernetes.io/service-name=<service> -o wide
如果服务没有 Endpoint,先不要抓包,也不要修改节点路由;这更可能是 selector、readiness 或 EndpointSlice 问题。
在 Pod 内执行测试:
kubectl exec -n <namespace> <pod> -- sh -c '
cat /etc/resolv.conf
ip addr 2>/dev/null || true
ip route 2>/dev/null || true
getent hosts kubernetes.default.svc 2>/dev/null || true
'
生产镜像可能没有 sh、ip 或 getent。此时可以使用临时调试容器或一次性诊断 Pod,但应遵守集群安全策略:
kubectl debug -n <namespace> pod/<pod> \
--image=busybox:1.36 --target=<container-name> -it -- sh
kubectl debug 的 --target 是否能共享目标容器的进程命名空间,取决于 Runtime 和配置;它不保证自动拥有宿主机权限。调试 Pod 的镜像来源也必须是集群允许的可信仓库。
3. 节点侧网络检查
ip addr
ip route
ip neigh
ss -s
ss -lntup
检查到 API Server 的路径:
nc -vz <api-server-address> <port>
curl -k https://<api-server-address>:<port>/healthz
检查 DNS:
getent hosts kubernetes.default.svc
resolvectl status 2>/dev/null || true
检查规则和连接跟踪:
sudo iptables-save 2>/dev/null | head -100
sudo nft list ruleset 2>/dev/null | head -100
sudo conntrack -S 2>/dev/null
不要因为系统同时安装了 iptables 和 nft 命令,就假设 Kubernetes 使用的是某一种后端。应确认 kube-proxy、CNI 插件和发行版实际配置;直接清空规则可能导致整个节点或集群通信中断。
4. Pod Sandbox 网络失败的因果链
一个典型 CNI 故障如下:
Kubelet 请求 Runtime 创建 Pod sandbox
│
▼
Runtime 创建 network namespace
│
▼
调用 CNI ADD
│
├── CNI 配置文件不存在
├── CNI 二进制缺失
├── IPAM 地址池耗尽
├── 节点到网络控制器不可达
├── 网卡、路由或内核模块异常
└── 旧网络残留或权限错误
│
▼
Sandbox 创建失败
│
▼
Pod 卡在 ContainerCreating
针对不同错误检查:
sudo ls -l /etc/cni/net.d/
sudo ls -l /opt/cni/bin/
kubectl get pods -n kube-system -o wide
kubectl logs -n kube-system <cni-node-pod> --all-containers --since=20m
如果只有一个节点的 Pod 无法创建,而其他节点正常,应优先比较该节点的:
- CNI DaemonSet 状态;
/etc/cni/net.d文件;/opt/cni/bin二进制;- 网卡和路由;
- 内核模块;
- 节点到其他节点或控制面的连通性。
5. NetworkUnavailable 的边界
NetworkUnavailable 是 Node Condition,但具体由谁设置取决于集群网络实现。某些云控制器、节点初始化逻辑或网络插件会设置或清除它。它不是“所有 Pod 到所有地址都已验证可达”的统一探针。
所以看到:
NetworkUnavailable=True
应结合:
kubectl describe node <node-name>
kubectl get pods -n kube-system -o wide
kubectl get events -A --sort-by=.lastTimestamp
以及 CNI 插件自身状态进行判断,不能只依赖这一项 Condition。
九、Probe:为什么 Pod Running 仍可能不可用
探针由 Kubelet 执行,但探针结果与 Node Ready 是两个层次。
1. 三种探针
startupProbe:应用启动阶段使用。未成功前,Kubelet 通常不会根据 liveness/readiness 判断应用已正常启动。livenessProbe:判断容器是否需要重启。readinessProbe:判断容器是否可以接收 Service 流量。
典型配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: probe-demo
spec:
replicas: 1
selector:
matchLabels:
app: probe-demo
template:
metadata:
labels:
app: probe-demo
spec:
containers:
- name: app
image: nginx:1.27
ports:
- containerPort: 80
startupProbe:
httpGet:
path: /
port: 80
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /
port: 80
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
启动探针最多允许的启动窗口近似为:
上例为:
这只是探针调度的近似上限,实际还会受到请求耗时、Kubelet 调度和容器启动时间影响。
2. 探针失败的诊断
kubectl describe pod probe-demo-<suffix>
kubectl get pod probe-demo-<suffix> \
-o jsonpath='{.status.containerStatuses[*].state}{"\n"}'
kubectl logs probe-demo-<suffix> --previous
判断方式:
- readiness 失败:容器可能仍是
Running,但不会被加入 Service 的可用 Endpoint; - liveness 失败:Kubelet 重启容器,可能出现
CrashLoopBackOff; - startup 失败:应用尚未通过启动条件,后续探针通常不会按预期接管;
- 探针连接失败:可能是应用监听地址、端口、路径、网络策略或节点资源问题;
- HTTP 500:说明网络请求已到达应用,但应用认为自身不健康。
不要用 liveness 探针代替 readiness 探针。一个暂时无法访问数据库的应用,可能仍然活着但不应接收流量;用 liveness 处理会造成无意义重启。
十、把条件组合起来:一个可复用的诊断流程
第一步:确认节点和 Pod 的范围
kubectl get nodes -o wide
kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name>
kubectl get events -A --sort-by=.lastTimestamp | tail -100
如果只有一个 Pod 异常,先查应用、镜像、卷、探针和 CNI;如果同一节点多个 Pod 同时异常,优先查节点层。
第二步:读取 Condition、Lease 和 Taint
kubectl describe node <node-name>
kubectl get lease -n kube-node-lease <node-name> \
-o jsonpath='{.spec.renewTime}{"\n"}'
kubectl get node <node-name> -o jsonpath='{.spec.taints}{"\n"}'
形成以下判断:
Lease 新鲜 + Ready=True
→ 节点控制面基本连通,查 Pod/Runtime/CNI/应用
Lease 新鲜 + Ready=False
→ Kubelet 能汇报,但自身检查未通过,查 Runtime、PLEG、网络、资源
Lease 陈旧 + SSH 可达
→ 查 Kubelet、API Server 路径、CPU、IO、TLS、DNS
Lease 陈旧 + SSH 不可达
→ 查主机、电源、云网络、内核或节点故障
第三步:检查 Kubelet 和 Runtime
sudo systemctl is-active kubelet
sudo systemctl is-active containerd
sudo journalctl -u kubelet --since "30 min ago" --no-pager
sudo journalctl -u containerd --since "30 min ago" --no-pager
sudo crictl info
sudo crictl pods
sudo crictl ps -a
把错误分成:
connection refused:socket 不存在、服务未监听或服务已退出;context deadline exceeded:服务可能卡死、IO 阻塞或请求排队;permission denied:socket 权限、SELinux/AppArmor 或执行用户问题;unknown service/ 协议错误:CRI endpoint 或 Runtime 版本不匹配;image pull错误:镜像仓库、凭据、代理、DNS 或 imagefs 问题。
第四步:检查 DiskPressure 和 PIDPressure
df -hT
df -ih
sudo du -xhd1 /var/lib/kubelet /var/log 2>/dev/null | sort -h
sudo lsof +L1
ps -eLf | wc -l
ps -e -o stat= | awk '$1 ~ /Z/ {z++} END {print z+0}'
必须区分:
- 空间不足;
- inode 不足;
- 镜像文件系统不足;
- deleted-open 文件;
- PID 全局不足;
- 单个 cgroup PID 限制;
- 应用泄漏。
第五步:检查网络和探针
kubectl get svc,endpointslice -n <namespace>
kubectl describe pod <pod> -n <namespace>
kubectl exec -n <namespace> <pod> -- cat /etc/resolv.conf
ip addr
ip route
ss -s
如果 sandbox 创建失败,查 CNI;如果 Pod 已有 IP 但 Service 不通,查 EndpointSlice、Service 规则、kube-proxy/CNI dataplane、conntrack 和 NetworkPolicy;如果只有 readiness 失败,查应用监听和探针参数。
十一、恢复操作的风险边界
1. 先隔离,再处理
如果节点正在持续产生故障,且业务允许迁移,可以先阻止新 Pod 调度:
kubectl cordon <node-name>
cordon 只影响后续调度,不会停止已有 Pod。
需要迁移可驱逐的工作负载时,才考虑:
kubectl drain <node-name> \
--ignore-daemonsets \
--delete-emptydir-data
风险包括:
emptyDir数据会丢失;- 单副本工作负载会中断;
- PodDisruptionBudget 可能阻止驱逐;
- DaemonSet 不会被普通 drain 移除;
- 无控制器管理的裸 Pod 可能无法安全恢复;
- StatefulSet、本地卷和拓扑约束需要额外确认。
不要在尚未确认节点状态时使用 --force、--disable-eviction 等选项绕过保护。它们会把调度器和控制器提供的安全约束交给操作者承担。
2. 重启服务不是默认答案
重启 Kubelet 适合处理明确的 Kubelet 进程异常,但不适合掩盖:
- 磁盘已满;
- Runtime 数据库损坏;
- CNI 配置缺失;
- API Server 网络不通;
- PID 已耗尽;
- 内核或文件系统错误。
重启 Runtime 的影响更大。执行前保存:
sudo journalctl -u containerd --since "1 hour ago" > /tmp/containerd.log
sudo dmesg -T | tail -200 > /tmp/dmesg.log
df -hT > /tmp/df.txt
df -ih > /tmp/df-i.txt
然后确认服务恢复、CRI 可用、Kubelet 能重新同步、Node Condition 恢复以及业务流量重新进入。
3. 什么时候应重建节点
以下情况通常更适合隔离并重建,而不是在生产节点上反复手工修补:
- 文件系统损坏或持续产生 I/O error;
- Runtime 状态目录无法可靠恢复;
- 节点内核、cgroup 或网络配置偏离标准;
- PID、磁盘、网络故障反复出现且根因不明;
- 节点上存在无法验证来源的手工修改;
- 云实例底层状态异常。
重建前必须确认:
- 工作负载由控制器管理;
- 持久卷数据不依赖本地临时目录;
- PodDisruptionBudget 和副本数允许迁移;
- 节点是否承载本地卷、GPU、特殊设备或静态 Pod;
- 云厂商的节点删除流程不会误删业务数据。
十二、常见误判
误判一:Ready=True 就说明节点没有问题
Ready 主要反映 Kubelet 和节点运行条件。应用 readiness、Service Endpoint、DNS、外部依赖和网络策略仍可能失败。
误判二:DiskPressure=True 只要执行 docker system prune
现代 Kubernetes 集群可能使用 containerd 或 CRI-O,Docker 命令可能清理不到实际镜像;即使能清理,也可能误删仍需缓存的镜像。应先确定 Runtime 和 imagefs。
误判三:删除日志文件就能立即释放磁盘
deleted-open 文件仍被进程持有时,空间不会释放。应检查 lsof +L1 并安全重载或重启相关进程。
误判四:Pod 的 PID 限制和节点 PIDPressure 是一回事
单个 Pod 的 pids.max 耗尽,可能只影响该 Pod;节点 PIDPressure 则表示节点级可用 PID 进入压力阈值。必须同时看进程总量和 cgroup 限制。
误判五:Pod Running 就一定能访问 Service
Running 只说明容器进程状态达到运行阶段。Service 是否有可用 Endpoint 由 readiness 等条件影响;网络路径还可能在 CNI、kube-proxy、路由、DNS 或策略层失败。
误判六:重启 Kubelet 能修复所有 NodeNotReady
如果根因是 API Server 路径、Runtime、CNI、磁盘或内核,重启 Kubelet 只能改变观察窗口,不能修复底层资源。
十三、最终验证:恢复必须是可观察的状态收敛
一次完整恢复至少应验证以下结果:
kubectl get node <node-name>
kubectl describe node <node-name>
kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name>
sudo systemctl is-active kubelet
sudo crictl info
sudo crictl pods
sudo crictl ps
df -hT
df -ih
ps -eLf | wc -l
然后验证业务路径,而不是只验证控制面:
kubectl get svc,endpointslice -A
kubectl run netcheck --image=busybox:1.36 --restart=Never \
--rm -it -- sh
在临时 Pod 中根据实际环境测试:
nslookup kubernetes.default.svc
wget -qO- http://<service>.<namespace>.svc.cluster.local:<port>
如果集群没有对应工具、镜像仓库不可用或安全策略禁止临时 Pod,应使用已有诊断容器或节点侧工具替代。测试完成后确认临时资源已删除,避免把诊断 Pod 留在生产环境中。
节点排障的核心不是找到一个“万能重启命令”,而是沿着状态来源反推故障边界:
Lease / Node Status
→ Kubelet 是否能工作并汇报
Kubelet
→ 是否能同步 Pod 期望状态
CRI Runtime
→ sandbox、镜像、容器是否能被管理
Disk / PID
→ 节点是否仍有能力创建和维持进程
CNI / 路由 / Service / DNS
→ Pod 是否能建立并使用网络
Probe / Endpoint
→ 应用是否真正可接收流量
只要每一步都用对应组件的证据验证,就能把 NotReady、DiskPressure、PIDPressure、Runtime 超时和网络失败从一个模糊的“节点坏了”,还原为可定位、可验证、可恢复的故障路径。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 工作负载排障:Pending、CrashLoop、ImagePull、OOM 和 Probe
- 下一篇:Kubernetes 事件响应:止损、证据、审计、凭据轮换和恢复
- 延伸:Kubernetes Node 生命周期:Condition、Lease、Taint、NotReady 和恢复
- 延伸:Kubelet 与容器运行时:CRI、Pod Sync、PLEG、Probe 和资源状态
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论