Skip to content

节点运维

Kubernetes 节点上跑着 kubelet、容器运行时、CNI 插件、CSI 驱动和一堆系统服务。Pod 的生命周期、网络、存储、日志都落在节点上。很多 Pod 的异常看起来是应用问题,实际根因在节点——runtime 连不上、CNI 还没准备好、磁盘触发 DiskPressure、镜像拉取凭据过期、kubelet 证书或配置有问题。

控制平面负责期望状态,但把期望落成真实容器的是每台节点上的 kubelet。

节点状态和 Condition

bash
kubectl get nodes -o wide
kubectl describe node <node-name>

describe node 的 Conditions 字段是节点健康状况的集中反映:

Condition含义NotReady/True 时的后果
Readykubelet 正常上报心跳False 后该节点不再接收新 Pod
MemoryPressure节点内存不足kubelet 开始驱逐低优先级 Pod
DiskPressure磁盘空间或 inode 不足镜像拉取、容器创建、日志写入受影响
PIDPressure进程数接近上限新进程 fork 失败,容器无法启动
NetworkUnavailableCNI 未初始化完成Pod 网络不通,容器可能无 IP

节点 NotReady 后,上面的容器不一定会立刻停止——kubelet 可能还在运行但无法上报心跳。控制平面会将节点标记为不可用,调度绕开它,Service 的 EndpointSlice 里去掉该节点上的 Pod IP。

kubelet 的角色

kubelet 是节点上的核心代理。它从 apiserver 拿到 Pod 的期望状态,依次调用 CRI(创建容器)、CNI(配置网络)、CSI 或挂载逻辑(处理卷):

看 kubelet 日志是节点排错很关键的一步,kubectl describe pod 里看不到的底层错误在这里能看到:

bash
systemctl status kubelet --no-pager
journalctl -u kubelet -n 200 --no-pager

kubelet 常见故障:

现象可能的底层原因
kubelet 起不来配置文件格式错误、证书过期、CRI socket 对不上
节点 NotReadyCNI 未就绪、runtime 挂了、节点网络异常
Pod 卡在 ContainerCreating镜像拉取失败、CNI 分配 IP 失败、卷挂载失败
卷挂载超时CSI driver 异常、NFS 客户端问题、目录权限
探针连续失败应用端口不对、超时设太短、启动时间不够

journalctl -u kubelet 里的错误消息通常比 Events 更直接。Events 里写 "Failed to create pod sandbox",kubelet 日志里会有具体是 CNI 返回了什么错误码、哪个二进制执行失败了。

containerd 和 crictl

多数新集群的容器运行时是 containerd。kubelet 通过 CRI 和 containerd 通信,节点上有没有 docker 命令和 Pod 能不能跑没有关系。

bash
systemctl status containerd --no-pager
journalctl -u containerd -n 100 --no-pager
crictl info    # 看 CRI 运行时版本和状态
crictl ps -a   # 看所有容器(包括已停止的)

crictl 是 CRI 层的客户端工具,和 kubelet 看到的是同一个视角。配置文件放 /etc/crictl.yaml

yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false

crictlctr 的作用不同。crictl 走 CRI 接口,看的是 kubelet 管理的容器和镜像(k8s.io namespace)。ctr 是 containerd 的原生客户端,看的是 containerd 本身的 namespace。排查 Pod 问题用 crictl 就够了。

在出问题的节点上直接验证镜像拉取:

bash
crictl pull harbor.example.com/demo/app:v1.0.0

管理机能 pull 镜像,不等于工作节点的 containerd 能拉到——节点的 DNS、HTTP 代理、镜像仓库证书、凭据配置都可能不同。

磁盘、日志和镜像

节点磁盘打满是触发 DiskPressure 的常见原因。各路径对应什么数据:

路径存什么占空间的主要原因
/var/lib/containerd镜像层、容器读写层镜像多、旧镜像未清理
/var/lib/kubeletkubelet 工作目录、Pod 卷emptyDir、Secret/ConfigMap 挂载
/var/log/podsPod 日志(stdout/stderr 的真实文件)日志切割失效、应用输出量巨大
/var/log/containers容器日志的软链接通常是符号链接,本身不占空间

快速看磁盘:

bash
df -h
df -ih    # inode 耗尽时 df -h 可能显示有空间,但文件创建失败
du -sh /var/lib/containerd /var/lib/kubelet /var/log/pods 2>/dev/null
crictl images   # 看镜像数量和大小

清理未使用镜像:

bash
crictl rmi --prune

crictl rmi --prune 只删没有被容器引用的镜像。清完之后新建 Pod 如果用到这些镜像,需要重新拉取——所以更好的做法不是人工定期清理,而是配置 kubelet 的镜像垃圾回收和日志轮转,让节点自己持续管理。

kubelet 的关键参数:

参数控制什么
containerLogMaxSize单个容器日志文件最大多大,超过后轮转
containerLogMaxFiles单个容器保留多少个轮转文件
imageGCHighThresholdPercent磁盘使用超过这个百分比触发镜像回收
imageGCLowThresholdPercent镜像回收的目标磁盘使用百分比

kubeadm 集群查看当前配置:

bash
cat /var/lib/kubelet/config.yaml | grep -E 'containerLog|imageGC|eviction'

节点驱逐

kubelet 在节点资源紧张时会主动驱逐 Pod,让节点不至于完全不可用。驱逐的依据来自 eviction threshold(硬驱逐和软驱逐阈值),常见信号:

压力信号现象
内存memory.available 低于阈值Pod 被驱逐或 OOMKill
磁盘nodefs.availableimagefs.availableDiskPressure、新 Pod 挂载失败
inodenodefs.inodesFree看起来有空间但无法创建文件
PIDpid.available新进程创建失败

看驱逐相关事件:

bash
kubectl describe node <node-name> | grep -A10 Conditions
kubectl get events -A --sort-by=.lastTimestamp | grep -E 'Evicted|Pressure'
# 被驱逐的 Pod 还在列表里,状态显示 Evicted
kubectl get pods -A | grep Evicted

驱逐和控制器重建是两个独立动作。Pod 被节点驱逐后,Deployment 会在其他节点上创建新 Pod。如果 Pod 用了 emptyDir 或 Local PV,驱逐意味着本地数据丢失。

节点维护操作

日常维护的标准三部曲:

bash
# 1. 停止向节点调度新 Pod
kubectl cordon <node-name>

# 2. 看当前节点上跑着哪些 Pod
kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name>

# 3. 驱逐 Pod 到其他节点
kubectl drain <node-name> \
  --ignore-daemonsets \
  --delete-emptydir-data

维护完成恢复:

bash
kubectl uncordon <node-name>

drain 卡住的常见原因:

现象原因处理
drain 一直不返回PDB 阻止了驱逐kubectl get pdb -A 找到冲突的 PDB
提示 DaemonSet 管理的 PodDaemonSet 不会随 drain 删除--ignore-daemonsets
提示 emptyDirPod 用了 emptyDir 但没授权删除确认数据可丢后加 --delete-emptydir-data
uncordon 仍然不调度节点上还有其他 Condition 异常kubectl describe node 看 Conditions

节点故障处理

节点出问题时从两个视角看——集群视角看节点状态和 Pod 分布,主机视角看系统和进程:

集群视角:

bash
kubectl get nodes
kubectl describe node <node-name>
kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name>

主机视角:

bash
systemctl status kubelet containerd --no-pager
journalctl -u kubelet -n 200 --no-pager
journalctl -u containerd -n 100 --no-pager
ip addr && ip route
df -h && free -h

处理分支:

场景优先处理
kubelet 进程异常查配置、证书、CRI socket,修完后恢复 kubelet
containerd 异常查 containerd 日志、磁盘、镜像层
CNI 异常查 CNI Pod 状态、节点路由和 iptables/eBPF 规则
磁盘打满定位 containerd、日志、emptyDir、kubelet 卷,先清再找根因
主机确认报废kubectl delete node,并单独处理该节点上的 Local PV 和本地数据

短时间能修复的节点,优先恢复 kubelet、containerd 和网络。确认报废的节点,在 delete node 之后还要确认上面原来跑的有状态 Pod(StatefulSet、Local PV、单副本服务)的数据和处理方案。

节点故障复盘时把两条线分开:业务 Pod 为什么异常是一条线,节点为什么进入压力或 NotReady 是另一条线。节点 Condition、kubelet/containerd 日志、磁盘状态、驱逐事件、Pod 分布和时间线——这六样对标在一起才有复盘价值。