ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Docker与Kubernetes集群日常巡检:命令、脚本与避坑实战

Docker与Kubernetes集群日常巡检:命令、脚本与避坑实战 简介面向互联网运维与容器平台管理人员这份docx资料系统梳理了Docker容器和Kubernetes集群日常巡检的完整方法。内容从docker/podman ps查看容器状态、HealthCheck健康检查、docker stats资源监控到利用Prometheus、cAdvisor、Grafana等开源工具构建可视化监控与告警再到Kubernetes中master组件、节点状态、资源对象和事件日志的检查覆盖了容器云环境的关键巡检点。特别针对容器日志层级做了区分给出引擎日志与容器日志的查看、挂载及ELK采集思路。内容由拥有十余年运维经验的高级运维leader总结包含命令示例、状态解读和排错逻辑适合刚接触容器云或希望建立标准化巡检体系的运维技术人员参考。包内含1个docx文档大小5.54MB已有81人学习下载可作日常运维手册直接查阅。1. 巡检不是救火是把事故挡在生产发生之前一个集群很少是“突然”挂掉的。绝大多数生产事故在发生前 24 小时甚至一周就已经露出了征兆某个节点内存持续走高、镜像仓库里的悬空镜像堆积到磁盘告警、某个 CronJob 连续三天失败但没人看事件。日常巡检的价值就在这儿——它不是为了应对已经发生的故障而是把故障苗头在变成事故之前掐掉。这份针对 Docker 容器和 Kubernetes 集群的巡检指南整理的是我日常维护多套环境时真正在用的一套检查项、命令和判断逻辑适合运维工程师、后端开发兼职运维、以及一个人管多套集群的人照着执行。看完你能得到一份可复现的巡检脚本以及每条检查项背后的判断标准和踩坑边界。2. Docker 层巡检容器状态、资源水位与镜像存储的四个必看项2.1 容器状态docker ps -a 里的 5 种异常状态一眼识别很多人巡检 Docker 就是敲一条docker ps看到有Up就算过了。实际上docker ps只显示运行中的容器真正的问题往往藏在docker ps -a里。我的习惯是每天至少执行一次docker ps -a --format把非运行状态的容器单独筛出来看。docker ps -a --format table {{.Names}}\t{{.Status}}\t{{.ExitCode}} | grep -v ^NAMES这条命令把容器名、状态和退出码拉平输出方便直接对照。常见的异常状态有 5 种Exited容器退出需要看退出码和日志、Restarting重启中通常是健康检查失败或启动即崩溃、Dead容器卡死在移除流程、Created创建后从未启动多为配置错误、Paused被暂停常见于资源竞争时人为操作。退出码不是玄学137表示被 SIGKILL 杀死常见于 OOM143是 SIGTERM 后的正常退出1和2通常是应用自身启动失败。看到Exited (137)第一反应不是重启容器而是查内存限制和宿主机可用内存。docker logs --tail 200 container_name 21 | grep -i error\|panic\|fatal看日志不要从头翻到尾。--tail 200只取末尾 200 行再用grep过滤 error 级别的关键字。这里有个容易翻车的地方很多容器日志走的是 stdout但有些应用会同时写文件日志docker logs只能看到 stdout/stderr看不到应用自己写进容器内部文件的日志。如果容器日志是空的别急着下结论docker exec进容器里看应用日志文件才是完整视角。2.2 资源水位docker stats 的坑与容器的真实内存占用docker stats是最直观的资源巡检入口但它的输出会骗人。默认的MEM USAGE一列显示的是容器在宿主机视角下的内存使用量这个值包含了 page cache。也就是说一个应用真实只用了 300MB但docker stats可能显示 1.2GB因为它把读写文件产生的缓存也算进去了这导致不少人在巡检时误判内存泄漏。docker stats --no-stream --format table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}--no-stream是关键不加它docker stats会持续刷屏没法在脚本里用。输出里的MEM USAGE是“已用 / 限额”但已用是包含缓存的总量。要看真实内存占用得用docker inspect读内核态的指标。docker inspect container_name \ --format{{.State.Status}} Memory{{.HostConfig.Memory}} MemoryUsage{{.State.MemoryStats.Usage}} MemoryLimit{{.State.MemoryStats.Limit}} Cache{{.State.MemoryStats.Stats.total_inactive_file}}MemoryUsage减去total_inactive_file才是相对真实的匿名内存占用。我一般把这两个值都记下来Usage用于判断容器是否快要撞上LimitUsage-Cache用于判断应用自身的内存增长趋势。如果你发现Usage稳定但Usage-Cache持续上涨那不是泄漏是缓存命中率高反过来Usage一路涨且 Cache 没变化那就要准备扩容或调参了。CPU 使用率也要看时间窗口。docker stats的 CPU 百分比是实时值瞬时冲高不代表有问题。我的做法是抓三次间隔 10 秒取平均值。单核满载持续超过 10 分钟才需要关注是死循环还是正常承载上线流量。2.3 镜像与存储docker system df 和 /var/lib/docker 的膨胀边界镜像堆积是 Docker 巡检里最容易被忽视的隐患。docker images一张张看永远看不出总量问题用docker system df才能一眼看清磁盘都被谁吃了。docker system df docker images -f danglingtruedocker system df会把镜像、容器、本地卷和构建缓存的空间占用汇总列出来。这里有一个非常典型的巡检场景每天构建多次镜像旧镜像一层层变成悬空镜像docker images -f danglingtrue会列出所有没有标签且没有被容器引用的镜像层。这些悬空镜像占用的空间完全可以通过docker image prune释放掉。docker image prune -f --filter until168huntil168h表示只清理 7 天前创建的悬空镜像给最近一周的构建产物留后悔药。但注意这条命令不会动docker system df里的.Build Cache那行。构建缓存膨胀是另一个坑很多时候你看到/var/lib/docker目录快满docker system df里最大的却是 Build Cache那就需要单独清docker builder prune -f --filter until168h还有一个更隐蔽的存储问题容器本地卷。docker system df的Local Volumes一列只统计了卷的数量没显示每个卷的大小。卷文件直接落在宿主机的/var/lib/docker/volumes下面有些应用比如日志采集器、消息队列会往卷里疯狂写数据。我的巡检习惯是定期执行du -sh /var/lib/docker/volumes/* | sort -hr | head -20把最大的 20 个卷列出来看看有没有异常增长。2.4 Docker Daemon 自身的健康检查与常见启动失败Docker 巡检不能只看容器还要看 Docker Daemon 本身。Daemon 挂了所有容器虽然还在跑但容器编排、日志收集、健康检查全部失效。systemctl status docker --no-pager systemctl is-active dockersystemctl is-active docker返回active才算正常。如果返回inactive或failed先别急着systemctl start docker要看/var/log/messages或/var/log/syslog里的 dockerd 日志常见的有这么几类启动失败failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen这种是 Docker Desktop 的 Windows 管道问题发生在本地开发机而不是服务器上处理方式是检查 Docker Desktop 的 WSL2 后端是否启动init: error running init这类是容器运行时组件异常operation not permitted这类多半是权限问题。Docker Daemon 巡检里还有一个必须检查的项dockerd 的--storage-driver和--log-opt配置。日志驱动用默认的json-file时每个容器的 stdout 日志都会写进/var/lib/docker/containers/id/下的 json 文件如果不限制大小单个容器的日志文件能涨到几十 GB最后把根分区写满。正规做法是在/etc/docker/daemon.json里加上日志轮转配置{ log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }max-size和max-file配好之后单个容器的日志文件最多 300MB。这个配置只对新创建的容器生效巡检中如果发现已有容器的日志文件已经很大需要重建容器才能生效所以这块应该作为巡检的配置基线来检查而不是等出事再调。3. Kubernetes 集群巡检从控制平面到工作负载的完整链路3.1 控制平面API Server、etcd 和调度器的存量检查Kubernetes 巡检沿着两条线走控制平面和业务平面。控制平面挂掉整个集群的 API 都不可用业务容器虽然还在跑但扩缩容、滚动更新、故障转移全部瘫掉等于集群已经半死了。控制平面巡检从组件状态开始。kubectl get componentstatuses但这里有个变化需要注意componentstatuses在新版 Kubernetes 里已经被标记为 deprecated很多新集群执行它会得到一堆Unhealthy的结果这是接口转发方式变化导致的假阳性。更可靠的替代做法是直接请求 API Server 的健康接口kubectl get --raw/healthz?verbose kubectl get --raw/readyz?verbosehealthz返回ok表示 API Server 进程存活readyz返回ok表示 API Server 的所有依赖组件都处于就绪状态。这两个接口会返回每个子检查项的详细信息比如etcd、informer-sync、logs等。如果readyz挂了而healthz正常通常是 etcd 连接不稳定或 informer 同步超时这时候要查 etcd 的健康状态。kubectl -n kube-system exec -it etcd-node-name -- sh -c \ etcdctl endpoint health --cluster --cacert/etc/kubernetes/pki/etcd/ca.crt --cert/etc/kubernetes/pki/etcd/server.crt --key/etc/kubernetes/pki/etcd/server.keyetcd 巡检的核心指标是health和leader以及端到端请求延迟。用etcdctl endpoint health直连检查返回healthy才是真的活。在集群规模大的场景下我还会加一句etcdctl endpoint status --cluster -w table看一下各节点 etcd 的DB SIZE是否差异过大——如果有一个节点的 DB SIZE 和其他节点差很多说明 raft 同步有问题后续可能出现数据不一致。控制平面的另一个巡检点是调度器。调度器挂了不会马上出事但新的 Pod 调度不出来滚动更新会卡住。检查方式是看 kube-system 命名空间里 scheduler Pod 的日志和状态kubectl -n kube-system get pod | grep scheduler kubectl -n kube-system logs scheduler-pod --tail50 | grep -i error3.2 节点状态NotReady 的根因与网络插件排查节点巡检是 Kubernetes 巡检的第二条腿。kubectl get nodes的输出里如果出现NotReady整个巡检可以先停下优先处理这个。kubectl get nodes -o wide kubectl describe node node-name | grep -A 5 Conditions:describe node能看到每个 condition 的详情。Ready条件的Status为Unknown或False时Last Heartbeat Time字段会告诉你节点最后一次心跳的时间。节点心跳默认每 10 秒上报一次如果心跳时间距离当前超过 1 分钟node controller会把节点标记为NotReady。常见原因有三类kubelet 挂掉、网络不通、节点资源耗尽导致Pressure。资源耗尽会在 condition 列表里看到MemoryPressure或DiskPressure为True这类问题可以提前用资源水位巡检挡掉kubelet 挂掉就直接看进程和日志systemctl status kubelet journalctl -u kubelet -n 200 --no-pager | grep -i error\|failed网络层面先看节点上的 Pod 网络插件状态。Calico 和 Flannel 都有自己的 DaemonSet在 kube-system 下运行。如果节点 NotReady 而 kubelet 状态正常优先排查 CNI 插件的 Pod 是否在这个节点上处于 Running 状态kubectl -n kube-system get pod -o wide | grep calico kubectl -n kube-system logs calico-pod --tail100 | grep -i error还有一种容易翻车的情况节点状态正常但节点上某个 Pod 的网络是断的。热词里的 “docker网络不通” 对应的就是这个场景。这种问题的典型现象是 Pod 里可以exec进去但ping不通别的 Pod 或 Service。排查链路是先从 Pod 内的路由表看起再去节点上看 CNI 的 veth 设备是否存在最后看 kube-proxy 的 iptables 规则。我见过不少节点巡检时只盯节点状态漏掉 Pod 网络的情况最后业务方报“某个服务间歇性超时”一查就是 CNI 插件在个别节点上悄悄退出了。3.3 工作负载Pod 状态、事件流与部署版本回退的判断节点正常不代表业务就正常。工作负载的巡检要看的是 Deployment、StatefulSet、DaemonSet 的期望副本数和实际可用副本数是否一致。kubectl get deployment -A | awk { if ($2 ! $3) print } kubectl get statefulset -A | awk { if ($2 ! $3) print }这里的输出逻辑是DESIRED和CURRENT不一致说明有副本没就绪。但kubectl get deployment的第一列是命名空间第二列是名称第三列才是 DESIRED具体列号要以-o wide的输出为准。我实际更常用的是一条kubectl get pods -A --field-selectorstatus.phase!Running把所有不在 Running 状态的 Pod 全部拉出来kubectl get pods -A --field-selectorstatus.phase!Running -o wide kubectl get pods -A | grep -v Running\|Completed第二条命令更朴素也更可靠因为field-selector对CrashLoopBackOff这种状态是筛不出来的——它的 phase 还是 Running只是 restarts 在涨。所以还得加一个条件看重启次数。kubectl get pods -A -o wide输出里RESTARTS列短时间内持续增长就是有问题。判断是否持续增长需要一个时间基准我的做法是连续执行两次间隔 5 分钟对比相同 Pod 的RESTARTS数值是否变大。事件流是工作负载巡检里信息量最大的入口kubectl get events -A --sort-by.lastTimestamp | tail -50事件流里会记录Scheduled、Pulled、Created、Started的正常步骤也会记录FailedScheduling、FailedMount、Unhealthy、BackOff的异常环节。BackOff事件后面的back-off pulling image对应镜像拉取问题FailedMount对应存储卷挂载问题FailedScheduling对应资源不足或调度约束不满足。巡检时把事件按时间排个序tail 最近的 50 条能直接看到集群最近 10 分钟内在发生什么。3.4 巡检频率与节奏kubelet 心跳、事件保留和巡检窗口“日常巡检”的“日常”两个字值得单独说一说频率设计。Docker 层和 Kubernetes 层的巡检节奏应该是不同的。容器层的巡检建议每 4 小时跑一次因为 Docker 本身的故障多数是渐进式的——内存缓慢上涨、磁盘空间慢慢减少4 小时看到趋势就够了。Kubernetes 层建议每 15 分钟跑一次快速巡检每 4 小时跑一次全量巡检。全量巡检包括节点状态、控制平面组件、事件流、存储卷容量、工作负载副本数。快速巡检只检查kubectl get nodes是否有 NotReady、kubectl get pods -A是否存在非 Running 状态、kubectl get cs健康状态这三个信号。事件保留时间也是巡检节奏的一部分。Kubernetes 事件默认保留 1 小时如果不定期导出很多故障在事后排查时找不到根因。我的习惯是用kubectl get events -A -o yaml --sort-by.lastTimestamp每晚导出一次事件快照和巡检记录一起归档。这样两周后排查疑难故障时还能找到事发前的事件轨迹。还有一个细节巡检窗口尽量避开业务高峰。如果集群承载的是周期性业务比如每天 10 点有定时任务洪峰巡检脚本就不要安排在 10 点整。kubectl get events -A这样的操作虽然只读但在大规模集群上会加大 API Server 的负载触发流控。我的做法是把 crontab 里 10:00-10:30 的巡检任务全部挪到 11 点之后。4. 把巡检命令变成可落地的巡检脚本输出结构化结果可复查4.1 第一个脚本Docker 巡检的 10 个检查项与退出码设计前面讲的都是手工操作步骤真正要“日常化”得把它们写成脚本跑定时任务。下面是我在用的一个 Docker 巡检脚本的最小化成形版本包含 10 个检查项Daemon 状态、容器异常状态、重启次数、持续运行时间、内存使用率、磁盘使用率、悬空镜像、构建缓存、本地卷大小、日志文件大小。退出码设计为0 正常、1 有非致命警告、2 有需要立即处理的故障这样定时任务可以根据退出码决定是否触发告警。#!/bin/bash # docker_health_check.sh # 依赖: docker CLI, jq set -o pipefail RESULT_FILE/var/log/docker_check_$(date %Y%m%d_%H%M%S).log FAIL_COUNT0 WARN_COUNT0 log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* | tee -a $RESULT_FILE } # 1) Docker daemon 状态 if ! systemctl is-active docker /dev/null 21; then log ERROR: docker daemon not active let FAIL_COUNT else log OK: docker daemon active fi # 2) 异常容器状态 BAD_CONTAINERS$(docker ps -a --filter statusexited --filter statusdead --format {{.Names}}) if [ -n $BAD_CONTAINERS ]; then for c in $BAD_CONTAINERS; do log ERROR: container $c in abnormal status let FAIL_COUNT done else log OK: no abnormal container status fi # 3) 内存使用率 (采集两次超过 85% 警告超过 95% 故障) for c in $(docker ps --format {{.Names}}); do MEM_USAGE$(docker stats --no-stream --format {{.MemPerc}} $c | sed s/%//) if (( $(echo $MEM_USAGE 95 | bc -l) )); then log ERROR: container $c memory usage is ${MEM_USAGE}% let FAIL_COUNT elif (( $(echo $MEM_USAGE 85 | bc -l) )); then log WARN: container $c memory usage is ${MEM_USAGE}% let WARN_COUNT fi done # 4) 本地卷大小 Top 检测 LARGE_VOL$(du -sh /var/lib/docker/volumes/* 2/dev/null | sort -hr | head -5) log INFO: Top5 volumes:, $LARGE_VOL # 5) 悬空镜像 DANGLING$(docker images -f danglingtrue -q | wc -l) if [ $DANGLING -gt 20 ]; then log WARN: $DANGLING dangling images detected let WARN_COUNT fi if [ $FAIL_COUNT -gt 0 ]; then exit 2; fi if [ $WARN_COUNT -gt 0 ]; then exit 1; fi exit 0脚本逐段拆开看第一段systemctl is-active docker检查的是 systemd 视角的 Daemon 状态脚本在执行这个检查前先确认自己有 root 权限因为docker ps需要读/var/run/docker.sock。权限不够的时候docker ps会直接报permission denied这是热词里“docker权限错误怎么解决”对应的场景解法是把巡检用户加入docker组但为了最小权限原则我的做法是给巡检脚本单独配一个只读的DOCKER_HOST或专用证书。容器状态检查里用--filter statusexited一次性过滤多个状态比grep输出更稳。这里要注意Exited的容器分两种正常退出和报错退出。脚本目前在exit code 0的情况下也会报警会有误报所以这里完整版应该加一个docker inspect --format {{.State.ExitCode}}做二次判断或者把已知的正常退出容器比如每天跑完就退出的 CronJob 容器加白名单。内存检查里docker stats --no-stream的MemPerc是相对容器自身 limit 的百分比不是宿主机的百分比配了高内存 limit 的容器在这里会显得百分比很低这个口径偏差我在避坑章节会单独展开。4.2 第二个脚本K8s 巡检的 8 个检查项与事件抓取Kube 巡检脚本的关键在于减少对 API Server 的压力。不需要每个检查项都调用一次kubectl get一条kubectl get pods -A拿到的数据可以反复用。下面这个脚本把节点状态、工作负载状态和事件抓取合并在三次 API 请求内完成#!/bin/bash # k8s_health_check.sh # 依赖: kubectl, jq set -o pipefail CONTEXT${1:-default} OUTPUT_DIR/var/log/k8s_check mkdir -p $OUTPUT_DIR FAIL_COUNT0 SNAPSHOT$OUTPUT_DIR/k8s_snapshot_$(date %Y%m%d_%H%M%S).txt # 1) 节点状态总览 kubectl get nodes --no-headers $SNAPSHOT.nodes NOT_READY$(awk $2 ! Ready $SNAPSHOT.nodes) if [ -n $NOT_READY ]; then echo $NOT_READY | while read -r line; do echo [ERROR] node not ready: $line let FAIL_COUNT done fi # 2) Pod 异常状态 kubectl get pods -A --no-headers $SNAPSHOT.pods awk $4 ! Running $4 ! Completed $SNAPSHOT.pods | grep -E CrashLoopBackOff|Error|Pending|ImagePullBackOff|CreateContainerError $SNAPSHOT.badpods # 3) 频繁重启的 Pod awk \$5 5 $SNAPSHOT.pods $SNAPSHOT.restarts # 4) 事件快照 kubectl get events -A --sort-by.lastTimestamp $SNAPSHOT.events # 5) 输出报告 log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* } if [ -s $SNAPSHOT.badpods ]; then log ERROR: $(wc -l $SNAPSHOT.badpods) pods in abnormal status cat $SNAPSHOT.badpods let FAIL_COUNT2 else log OK: all pods running fi if [ -s $SNAPSHOT.restarts ]; then log WARN: $(wc -l $SNAPSHOT.restarts) pods with high restart count cat $SNAPSHOT.restarts fi exit $FAIL_COUNT这个脚本有几个设计要点。第一是对kubectl get nodes --no-headers直接落盘后续的巡检分析基于本地快照文件而不是反复请求 API Server。第二是状态判断逻辑$4 ! Running $4 ! Completed里面的Completed对应 Job 类的 Pod运行完正常退出不算异常。第三是 awk 判断中的转义在单引号字符串里$5要写成\$5否则 bash 会先展开变量。补丁点脚本里let FAIL_COUNT2的设计逻辑是“节点 NotReady 的严重程度是 Pod 异常的两倍”让监控系统可以按数值分级告警。分支里let FAIL_COUNT只加了 1但原始命令行里的let新写法是(( FAIL_COUNT ))两者行为一致。4.3 巡检结果落库与保留策略别让巡检日志自己成为事故巡检脚本最容易忽略的问题是巡检日志和快照文件无限堆积。如果一个脚本每小时跑一次每次生成 1MB 日志一年下来就是 8.7GB这些文件如果放在/var/log里会把根分区写满最终导致你一直巡检的那个——磁盘压力之下的集群自己 Eviction。这是个典型的巡检陷入循环的坑巡检想防磁盘满巡检日志又把磁盘搞满了。# iptables 规则: 保留最近 30 天的巡检日志 find /var/log/docker_check_*.log -mtime 30 -delete find /var/log/k8s_check -type f -mtime 30 -delete # 也可以每天打包一次, 例如: tar czf /var/log/k8s_archive_$(date -d yesterday %Y%m%d).tar.gz /var/log/k8s_check/* rm -rf /var/log/k8s_check/*这两条命令放在 crontab 里每天凌晨执行一次能保证巡检日志盘占用不会成为新的故障点。归档后的文件再用gzip压缩一次可以再省 60% 的空间但压缩和解压的 CPU 成本在低配服务器上也需要考虑——如果你巡检的是边缘节点就用简单的-mtime 30 -delete直接清掉不归档。巡检快照的保留策略和日志不一样快照里存着事件和 Pod 状态通常保留 7 天就够排查使用了。5. Docker 与 Kubernetes 巡检避坑现象、原因、解决的 5 条真实记录5.1 docker ps 显示 Up 不代表容器健康探针失败导致流量断裂现象巡检脚本检查docker ps时一切正常容器显示Up且已经运行了 5 天。但业务反馈服务响应超时。原因容器进程活着但容器内的应用卡死了——可能是死锁、连接池耗尽、goroutine 泄漏。docker ps的Up状态只代表容器的主进程没有被杀掉不代表应用能正常对外提供服务。解决巡检不能只看进程状态要加应用层探针。最简单的是用docker exec发起业务自身的健康检查请求比如执行curl访问容器的/healthz接口判断响应的 HTTP 状态码。如果容器里没有 curl就用docker exec执行容器内的脚本或二进制自带的 healthcheck 命令。对于已经配置了HEALTHCHECK指令的镜像docker inspect里的.State.Health.Status字段会返回healthy或unhealthy遇到unhealthy时直接看应用日志和网络连接数定位问题。5.2 docker stats 的内存口径会让 OOM 判断迟到一步现象容器运行正常docker stats显示内存使用率只有 60%巡检判定为健康。但当晚容器被 OOM killer 杀死业务中断 20 分钟。原因docker stats输出的MEM USAGE是包含文件缓存的数值。一个大量读写文件的应用缓存占比可能超过 50%算上缓存后显示的内存使用率远低于真实匿名内存占用。等到文件缓存被回收后匿名内存涨到 limit系统才会触发 OOM而此时已经是临界状态。解决巡检脚本里改用docker inspect读取MemoryStats.Stats.total_inactive_file用Usage - total_inactive_file计算真实内存。具体命令在 2.2 节已经给出。这个计算逻辑应该写进巡检脚本而不是靠人肉看因为docker stats的--no-stream在批量获取时输出格式很统一但内存口径有歧义机器算才靠谱。5.3 kubectl get nodes 显示 NotReady但 kubelet 状态却正常现象kubectl get nodes显示节点为NotReady但 SSH 到节点上执行systemctl status kubelet返回active (running)。原因kubelet 活着不代表它和 API Server 的通信正常。常见三种情况第一节点和 API Server 之间的网络路径出现丢包或延迟导致 kubelet 的心跳上报超时node controller在 40 秒后标记NotReady第二kubelet 的证书过期无法重新建立与 API Server 的长连接第三kubelet 的某个内部 goroutine 死锁导致状态更新停摆进程还在但实际已经不干活了。解决先看 kubelet 日志里有没有Failed to update node status之类的关键字再确认节点到 API Server 的网络连通性journalctl -u kubelet --since 10 minutes ago | grep -i update node status kubectl get node node-name -o jsonpath{.status.conditions[?(.typeReady)].lastHeartbeatTime}另外用kubectl get --raw/healthz?verbose看控制平面的健康状态排除 API Server 本身的问题。如果日志频繁出现context deadline exceeded则大概率是通信链路质量问题需要结合 tcpdump 抓包确认。这里有个容易踩的坑NotReady的节点不要直接kubectl delete node再重新注册正确顺序是先把 kubelet 修好让它恢复心跳节点会自动回到Ready。5.4 nodefs 和 imagefs 混在一起根分区被写满导致整个集群 Eviction现象Kubernetes 集群突然出现多个节点状态变为MemoryPressure或DiskPressure大量 Pod 被重新调度业务中断。原因kubelet 有nodefs和imagefs两组磁盘阈值判断。nodefs是/var/lib/kubelet所在分区imagefs是容器镜像存储所在分区。当这两个路径在同一个分区很多生产环境把/、/var/lib/docker、/var/lib/kubelet都放在根分区下任何一方的增长都会触发同一个分区的压力阈值。最常见的就是容器日志文件和镜像层把根分区写满随后 kubelet 开始按优先级驱逐 Pod。解决巡检里增加df -h的检查单独监控/、/var/lib/docker、/var/lib/kubelet三个路径的使用率任意一个超过 80% 就要告警。另外可以在 kubelet 配置里设置--eviction-hardnodefs.available10%,imagefs.available15%这样的阈值让 kubelet 优先清理不可迁移的镜像缓存而不是驱逐 Pod。生产环境的正确做法是把/var/lib/docker和/var/lib/kubelet挂载到独立的数据盘巡检脚本里同步检查分区挂载是否还在防止有人重装系统后把独立盘挂载丢了导致回落到根分区。5.5 刚部署完的 Calico 显示 CrashLoopBackOff别急着把它删掉现象集群刚部署完成kubectl get pods -n kube-system里 calico-node 的 Pod 处于CrashLoopBackOff巡检脚本报警。原因Calico 组件的启动依赖 etcd 或 Kubernetes API 中的某些数据状态首次部署时节点之间还没有建立 BGP 会话或者 CNI 配置尚未正确写入每个节点会导致部分 calico-node 启动失败后不断重启。但如果给它时间随着集群状态的收敛它会自己恢复正常。解决巡检脚本对网络插件类 DaemonSet 要做特殊处理。判断标准不是“当前是否 Running”而是“重启次数是否在持续增长”。如果 5 分钟内重启次数没有变化说明它已经稳定在 CrashLoopBackOff 状态没有再恶化可以先看日志确认原因如果重启次数还在涨再介入处理。处理方式通常有两种一种是把 calico-node Pod 删掉让 DaemonSet 重建另一种是在节点上重新执行/opt/cni/bin/calico的安装脚本重新写 CNI 配置。不要一看到 CrashLoopBackOff 就执行kubectl delete pod如果当时正是网络插件在初始化关键路由信息删除操作会扩大故障面。6. 让巡检结果自动告警纯 Shell 的 crontab 方案与告警去重巡检脚本跑起来了下一步是把结果及时送达到人。很多团队用的是 Prometheus Alertmanager但如果你还没有这套监控平台纯 Shell 的告警方案也能先顶上。核心思路是把脚本的退出码翻译成告警级别再通过 webhook 推送到钉钉、飞书或企业微信这类已有的协作工具。下面这个脚本片段演示了如何把前两章写的巡检脚本接进来#!/bin/bash # alert_webhook.sh # 使用方式: /usr/local/bin/k8s_health_check.sh; echo $? | alert_webhook.sh read -r EXIT_CODE if [ $EXIT_CODE -eq 0 ]; then exit 0 fi # 告警去重: 同一个检查项 30 分钟内只推一次 LAST_ALERT_FILE/var/run/k8s_alert_last_$(date %Y%m%d).tmp ALERT_HASH$(md5sum $(hostname)_$EXIT_CODE | cut -d -f1) if [ -f $LAST_ALERT_FILE ]; then PREV_HASH$(cat $LAST_ALERT_FILE) if [ $PREV_HASH $ALERT_HASH ]; then exit 0 fi fi echo $ALERT_HASH $LAST_ALERT_FILE # 推送 webhook curl -s -X POST $WEBHOOK_URL -H Content-Type: application/json \ -d {\msg_type\:\text\,\content\:\[巡检告警] $(hostname) 退出码 $EXIT_CODE, 请查看/var/log/k8s_check/最新快照\}告警去重逻辑是这套方案里最值得复制的一段。哈希值由“主机名 退出码”两部分构成同一个节点同一个故障级别在 30 分钟内只会推送一次但文件路径里带了日期第二天会重新开始计算保证每天至少能收到一次提醒。这里有一个取舍如果退出码在 1 和 2 之间切换告警还是会重复推。更精细的做法是把 5.3 节里那些NotReady节点的名称也拼进哈希让告警粒度对齐到具体对象。我的习惯是把这套巡检安排在每天三个固定时间点早上 7 点巡检集群是否正常过夜、下午 3 点巡检业务高峰后的资源水位、晚上 11 点做一次全量快照用于次日排查。三个点覆盖了大部分故障可能出现的时间窗口又不会因为频率太高挤占业务资源。最后说一句血泪经验巡检脚本上线前一定要在测试集群上跑一周把误报阈值调好再上生产。我第一次上线巡检脚本时没调阈值把内存 85% 的告警设成了故障级别结果周末告警刷了一整页真正的大问题反而被淹没了。希望你不用踩这个坑也希望这套巡检方案能帮你把集群握在手里而不是等着故障来找你。本文还有配套的精品资源点击获取
返回列表