news 2026/9/29 10:22:07

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 杀死,常见于 OOM;143是 SIGTERM 后的正常退出;1和2通常是应用自身启动失败。看到Exited (137),第一反应不是重启容器,而是查内存限制和宿主机可用内存。

docker logs --tail 200 <container_name> 2>&1 | 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用于判断容器是否快要撞上Limit,Usage-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 "dangling=true"

docker system df会把镜像、容器、本地卷和构建缓存的空间占用汇总列出来。这里有一个非常典型的巡检场景:每天构建多次镜像,旧镜像一层层变成悬空镜像,docker images -f dangling=true会列出所有没有标签且没有被容器引用的镜像层。这些悬空镜像占用的空间完全可以通过docker image prune释放掉。

docker image prune -f --filter "until=168h"

until=168h表示只清理 7 天前创建的悬空镜像,给最近一周的构建产物留后悔药。但注意:这条命令不会动docker system df里的.Build Cache那行。构建缓存膨胀是另一个坑,很多时候你看到/var/lib/docker目录快满,docker system df里最大的却是 Build Cache,那就需要单独清:

docker builder prune -f --filter "until=168h"

还有一个更隐蔽的存储问题:容器本地卷。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 docker

systemctl 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?verbose'

healthz返回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.key"

etcd 巡检的核心指标是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> --tail=50 | grep -i "error"

3.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> --tail=100 | 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-selector=status.phase!=Running,把所有不在 Running 状态的 Pod 全部拉出来:

kubectl get pods -A --field-selector=status.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_COUNT=0 WARN_COUNT=0 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$RESULT_FILE" } # 1) Docker daemon 状态 if ! systemctl is-active docker >/dev/null 2>&1; then log "ERROR: docker daemon not active" let FAIL_COUNT++ else log "OK: docker daemon active" fi # 2) 异常容器状态 BAD_CONTAINERS=$(docker ps -a --filter "status=exited" --filter "status=dead" --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 "dangling=true" -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 "status=exited"一次性过滤多个状态,比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_COUNT=0 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_COUNT+=2 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_COUNT+=2的设计逻辑是“节点 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[?(@.type=="Ready")].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-hard=nodefs.available<10%,imagefs.available<15%这样的阈值,让 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% 的告警设成了故障级别,结果周末告警刷了一整页,真正的大问题反而被淹没了。希望你不用踩这个坑,也希望这套巡检方案能帮你把集群握在手里,而不是等着故障来找你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 10:18:20

Langchain实战8-LangGraph进阶

本节进入 LangGraph 进阶&#xff1a;用 State、Node、Edge、Reducer 与条件路由构建可循环、可暂停、可恢复的 AI 工作流&#xff0c;并以“文章生成—审核—修改”为例串起核心能力。1. LangGraph 的核心抽象抽象作用State工作流当前快照&#xff0c;保存节点共享的数据Node读…

作者头像 李华
网站建设 2026/9/29 10:14:48

直流电机驱动实战:PWM、H桥与单片机协同设计指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 10:14:11

Altium Designer浮动许可智能释放方案

1. 许可瓶颈不是卡在软件上&#xff0c;而是卡在人和流程里Altium Designer许可不够用——这句话我听客户说了不下二十遍&#xff0c;每次都是同一个场景&#xff1a;设计团队五个人&#xff0c;公司只买了三套浮动许可&#xff0c;上午十点一到&#xff0c;总有人弹出“Licens…

作者头像 李华
网站建设 2026/9/29 10:08:36

WordPress targetSms插件RCE漏洞解析与加固

做安全应急这几年&#xff0c;我处理过不少“看起来人畜无害的插件突然变成突破口”的案例。最近在漏洞情报里看到WordPress的targetSms插件暴露出一个远程命令执行漏洞&#xff08;CVE-2025-3776&#xff09;&#xff0c;第一反应就是&#xff1a;又来了。短信通知、验证码、营…

作者头像 李华
网站建设 2026/9/29 10:08:32

成本限流实战:跨节点配额与分区降级配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 10:08:02

肠道菌群三大常见误区:很多人的肠道养护,一直在做无用功

肠道菌群三大常见误区&#xff1a;很多人的肠道养护&#xff0c;一直在做无用功 在肠道健康科普普及的当下&#xff0c;越来越多人开始重视肠道菌群&#xff0c;但市面上碎片化的养生认知&#xff0c;也让大众陷入了大量误区。很多人看似常年在养护肠道、调节菌群&#xff0c;实…

作者头像 李华