多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Docker与Kubernetes日常巡检实战:从容器存活到服务健康

Docker与Kubernetes日常巡检实战:从容器存活到服务健康 简介这份《Docker容器和kubernetes日常巡检指南》面向互联网运维工程师、容器云平台管理员及希望系统掌握容器巡检技能的初中级技术人员聚焦生产环境中Docker与Kubernetes集群的日常健康检查、状态监测与故障排查。资源包内含1个docx文档压缩包约5.54MB内容围绕容器状态查看、HealthCheck健康检查、docker stats资源观测、PrometheuscAdvisorGrafana第三方监控体系以及Kubernetes master节点、Pods、Services、Deployments等资源对象巡检和日志检查方法展开并附有大量可直接参考的命令示例与排查思路。作者曹如熙为拥有十年以上互联网运维经验的高级运维leader内容源自其多年容器云运维实践总结。目前已有81人学习下载适合需要建立标准化巡检流程、提升容器集群稳定性保障能力的运维人员参考。1. Docker 与 Kubernetes 日常巡检从「容器还活着」到「服务真健康」很多团队上容器化部署之后日常运维就退化成了两条命令docker ps看容器在不在kubectl get pods看 Pod 是不是 Running。结果某天业务方报障登上去一看容器进程确实在但应用线程池打满、连接池泄漏、磁盘写满、探针超时——容器「活着」服务早就「死了」。这就是容器化运维最典型的翻车场景把进程存活等同于服务健康。Docker 容器和 Kubernetes 日常巡检要解决的正是这个盲区。它面向的是已经用 Docker 跑单机服务、或者用 Kubernetes 跑集群的运维工程师和 SRE目标是把巡检从「看一眼状态」升级成一套可重复执行、有明确阈值、能提前发现隐患的检查流程。巡检不是等告警响了再去救火而是每天固定时间跑一遍把 CPU、内存、磁盘、网络、镜像、证书、探针这些维度都过一遍在用户感知到之前把问题摁住。下面按「巡检什么 → 怎么查 → 坑在哪」的顺序把一套能直接抄作业的方案讲清楚。2. 容器层巡检Docker 日常该盯哪几个指标2.1 为什么docker ps远远不够docker ps只告诉你容器进程在不在它不告诉你容器内部发生了什么。一个 Java 容器可能进程正常但 JVM 堆内存已经到 95%随时 OOM一个 MySQL 容器可能端口通但慢查询堆积、连接数打满。巡检要拿到的是容器运行时的真实资源画像而不是一个布尔值。Docker 巡检的核心指标分四类资源使用CPU、内存、磁盘 IO、网络 IO、状态健康重启次数、退出码、健康检查结果、存储镜像层、数据卷、日志文件大小、配置环境变量、端口映射、挂载点。这四类里资源使用和状态健康是每天必看存储和配置可以每周做一次全量核对。常见做法是用docker stats做实时采样但它默认是流式输出不适合脚本采集。我一般用docker stats --no-stream配合格式化参数一次性拿到所有容器的快照。下面这段脚本可以直接放进 crontab每天早八点跑一次输出异常容器。#!/bin/bash # docker_daily_check.sh # 采集所有运行中容器的资源快照筛选出 CPU 或内存超阈值的容器 # 阈值CPU 80%内存 85% CPU_THRESHOLD80 MEM_THRESHOLD85 docker stats --no-stream --format \ {{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.MemUsage}} | \ while IFS$\t read -r name cpu mem memusage; do # 去掉百分号转成整数比较 cpu_val${cpu%\%} mem_val${mem%\%} cpu_int${cpu_val%.*} mem_int${mem_val%.*} if [ $cpu_int -gt $CPU_THRESHOLD ] || [ $mem_int -gt $MEM_THRESHOLD ]; then echo [WARN] 容器 $name CPU${cpu} MEM${mem} 用量${memusage} fi done这段脚本的逻辑很直白--no-stream让docker stats只输出一次就退出--format用 Go 模板把容器名、CPU 百分比、内存百分比、内存用量四列拉出来然后用 shell 做阈值比较。参数上CPU_THRESHOLD和MEM_THRESHOLD要根据业务类型调——计算密集型服务 CPU 阈值可以放到 90%内存敏感型服务建议压到 80% 以下留出突发余量。注意docker stats的 CPU 百分比是相对于单核的多核机器上 200% 才代表跑满两核阈值判断时要心里有数。2.2 容器重启次数和退出码最容易被忽略的「后悔药」容器频繁重启是比资源超限更危险的信号。一个容器如果每天重启三五次说明它一直在崩溃边缘只是重启太快你没注意到。Docker 本身不直接提供「重启次数」这个字段但可以通过docker inspect拿到RestartCount和LastExitCode。#!/bin/bash # check_restart.sh # 检查所有容器的重启次数和上次退出码 # 重启次数 3 或退出码非 0 的容器重点标记 docker ps -a --format {{.Names}} | while read -r name; do restart_count$(docker inspect --format{{.RestartCount}} $name 2/dev/null) exit_code$(docker inspect --format{{.State.ExitCode}} $name 2/dev/null) status$(docker inspect --format{{.State.Status}} $name 2/dev/null) if [ $restart_count -gt 3 ]; then echo [ALERT] $name 重启次数$restart_count 状态$status 退出码$exit_code fi # 退出码 137 通常是被 OOM Kill 或 docker stop 强制终止 if [ $exit_code -eq 137 ]; then echo [OOM?] $name 退出码 137检查是否内存超限被杀 fi done这里的关键参数是RestartCount和ExitCode。退出码 137 等于 1289表示进程收到 SIGKILL最常见的原因就是内存超限被内核 OOM Killer 干掉或者docker stop超时后强制杀。退出码 1 一般是应用自身报错退出退出码 143 是 SIGTERM 正常终止。巡检时把 137 单独拎出来基本能抓到大部分「莫名其妙重启」的根因。提示docker inspect对已停止的容器同样有效所以巡检脚本用docker ps -a而不是docker ps否则会漏掉那些反复重启后处于 Exited 状态的容器。2.3 磁盘和日志容器写满宿主机的前兆容器日志默认走 json-file 驱动不加限制的话一个话痨应用一天能写几十 GB直接把宿主机磁盘写满。巡检必须查两样东西Docker 根目录占用以及单个容器日志文件大小。# 查看 Docker 根目录整体占用 docker system df -v # 找出日志文件最大的前 10 个容器 find /var/lib/docker/containers -name *-json.log -exec du -h {} | \ sort -rh | head -10docker system df -v会列出镜像、容器、数据卷、构建缓存各自的占用如果 Build Cache 特别大说明长期没清理。日志文件路径默认在/var/lib/docker/containers/container-id/container-id-json.log如果 Docker 根目录改过用docker info | grep Docker Root Dir确认实际路径。治本的做法是在启动容器时加日志轮转参数--log-opt max-size100m --log-opt max-file3这样单个日志文件最大 100MB最多保留 3 个总共不超过 300MB。对于已经跑着的容器改这个参数需要重建容器所以最好在部署阶段就写进 docker-compose 或启动脚本里。3. Kubernetes 集群巡检节点、Pod、探针三层过滤3.1 节点层NotReady 之前就有征兆Kubernetes 巡检的第一层是节点。kubectl get nodes看到 NotReady 才去处理往往已经晚了。节点出问题前通常有征兆磁盘压力DiskPressure、内存压力MemoryPressure、PID 压力PIDPressure这些会先体现在 Node Condition 里。# 查看所有节点的状态和 Condition kubectl get nodes -o wide kubectl describe nodes | grep -A 5 Conditions: # 只看有压力的节点 kubectl get nodes -o json | jq -r .items[] | .metadata.name as $name | .status.conditions[] | select(.type | test(Pressure)) | select(.status True) | \($name) \(.type) \(.message) 上面这段用jq过滤出所有处于 True 状态的 Pressure Condition。如果看到MemoryPressure True说明节点内存快不够了kubelet 已经开始驱逐 Pod。这时候要赶紧看是哪个 Pod 吃内存而不是等它把业务 Pod 赶走。节点层还要盯的一个指标是 kubelet 和容器运行时的健康。systemctl status kubelet看服务状态journalctl -u kubelet --since 1 hour ago | grep -i error看近期报错。常见问题是容器运行时响应慢导致 kubelet 心跳超时节点被标记 NotReady但 SSH 上去看服务又是好的——这种「玄学」问题多半是磁盘 IO 打满或者 inode 耗尽用df -i查一下 inode 使用率。3.2 Pod 层Running 不等于 Readykubectl get pods显示 Running但READY列是0/1这种 Pod 其实没在提供服务。巡检脚本必须同时检查 Phase 和 Ready 状态。# 找出所有 Running 但 NotReady 的 Pod kubectl get pods --all-namespaces -o json | jq -r .items[] | select(.status.phase Running) | select(.status.containerStatuses[]?.ready false) | \(.metadata.namespace)/\(.metadata.name) Readyfalse # 统计所有非 Running 状态的 Pod kubectl get pods --all-namespaces --field-selector \ status.phase!Running,status.phase!Succeeded第一段用jq筛出 Phase 是 Running 但容器 ready 为 false 的 Pod这类 Pod 通常卡在 readinessProbe 失败。第二段用 field-selector 直接过滤掉 Running 和 Succeeded剩下的就是 Pending、Failed、Unknown 这些异常状态。Pod 层还要看重启次数。kubectl get pods的 RESTARTS 列如果数字很大说明容器在反复崩溃。用kubectl describe pod name -n ns看 Last State 里的 Reason 和 Exit Code和 Docker 那套逻辑一样137 查 OOM1 查应用报错。3.3 探针配置liveness 和 readiness 别用同一个接口这是 Kubernetes 巡检里血泪经验最多的地方。很多团队 livenessProbe 和 readinessProbe 配了同一个 HTTP 接口结果这个接口一慢readiness 失败导致 Pod 被摘流量liveness 失败导致 Pod 被重启一个慢查询直接引发雪崩。巡检时要核对探针配置是否合理探针类型作用失败后果建议接口livenessProbe判断容器是否存活重启容器轻量健康接口不查数据库readinessProbe判断容器是否可接收流量从 Service 摘除可查关键依赖但要有超时startupProbe判断应用是否启动完成重启容器启动慢的应用必配用kubectl get pod name -o yaml导出配置重点看initialDelaySeconds、periodSeconds、timeoutSeconds、failureThreshold四个参数。启动慢的 Java 应用initialDelaySeconds设太小会导致还没启动完就被 liveness 杀掉陷入无限重启。常见做法是配 startupProbe给足启动时间启动成功后再交给 liveness 接管。4. 巡检脚本落地把零散命令串成可调度任务4.1 用 CronJob 跑集群内巡检单机 Docker 巡检用 crontab 就够了Kubernetes 集群巡检更适合用 CronJob把巡检脚本做成容器镜像定时在集群里跑。好处是脚本和集群在同一个网络里访问 API Server 不用额外配 kubeconfig。apiVersion: batch/v1 kind: CronJob metadata: name: k8s-daily-inspect namespace: ops spec: schedule: 0 8 * * * # 每天早八点执行 concurrencyPolicy: Forbid # 上一次没跑完就不启动新的 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: template: spec: serviceAccountName: inspect-sa # 需要绑定只读权限 restartPolicy: Never containers: - name: inspector image: your-registry/k8s-inspector:v1 command: [/bin/bash, /scripts/inspect.sh] resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi关键参数说明concurrencyPolicy: Forbid防止巡检任务堆积successfulJobsHistoryLimit和failedJobsHistoryLimit控制历史 Job 保留数量避免 etcd 里堆积太多已完成对象。serviceAccountName绑定的 SA 只需要get、list、watch权限不要给写权限巡检脚本只读不写。4.2 巡检结果怎么输出才有用巡检脚本最怕的是输出一大堆没人看。我的做法是分级输出正常项不输出警告项输出到日志严重项直接推送到告警通道。脚本里用一个report函数统一处理。#!/bin/bash # inspect.sh 核心输出逻辑 REPORT_FILE/tmp/inspect_$(date %Y%m%d).log ALERT_LEVEL0 log() { local level$1 local msg$2 echo [$level] $(date %H:%M:%S) $msg $REPORT_FILE if [ $level CRITICAL ]; then ALERT_LEVEL1 fi } # 示例检查 NotReady 节点 notready$(kubectl get nodes --no-headers | grep -v Ready | wc -l) if [ $notready -gt 0 ]; then log CRITICAL 存在 $notready 个 NotReady 节点 else log INFO 所有节点 Ready fi # 脚本退出码反映是否有严重问题 exit $ALERT_LEVEL这样 CronJob 的 Job 状态就能反映巡检结果退出码 0 表示一切正常退出码 1 表示有严重问题。再配合告警规则监听 Job 失败就能做到「有问题才通知没问题不打扰」。5. 巡检避坑五条踩出来的经验5.1 现象容器内存没超 limit却被 OOM Kill原因docker stats和kubectl top显示的是工作集内存working set而 cgroup 的 OOM 判断基于 RSS Cache。如果应用大量读写文件Page Cache 被算进 cgroup 内存实际 RSS 没超但 cgroup 总量超了。解决巡检时同时看docker stats的 MEM USAGE / LIMIT 和宿主机cat /sys/fs/cgroup/memory/container-id/memory.stat里的total_rss和total_cache。如果 cache 占比高说明是文件 IO 导致的考虑调大 limit 或者优化应用 IO 模式。5.2 现象Pod 一直 Pending事件里写 Insufficient cpu原因节点可分配资源不足或者 Pod 的 resource requests 设得太大。Kubernetes 调度看的是 requests 不是 limits很多人只设 limits 不设 requests调度器按默认值通常等于 limits算导致节点明明有空闲 CPU 却调度不上去。解决巡检时用kubectl describe node name看 Allocated resources 那一栏对比 requests 和实际使用量。如果 requests 远大于实际使用说明资源规划偏保守可以适当下调 requests把 limits 保持不变。5.3 现象permission denied while trying to connect to the Docker API原因当前用户不在 docker 组里或者 Docker socket 权限不对。这是新手装完 Docker 最常遇到的报错。解决sudo usermod -aG docker $USER然后重新登录。巡检脚本如果用非 root 跑要确保执行用户有 docker 组权限。Kubernetes 节点上如果容器运行时是 containerd对应的 socket 是/run/containerd/containerd.sock权限问题类似。5.4 现象镜像越积越多磁盘悄悄满了原因每次构建都打新 tag旧镜像不清理docker images里一堆none的悬空镜像。Kubernetes 节点上 kubelet 有镜像回收策略但默认阈值比较宽松85% 磁盘使用率才开始回收。解决Docker 单机定期跑docker image prune -a --filter until168h清理一周前的未使用镜像。Kubernetes 节点上检查 kubelet 的--image-gc-high-threshold和--image-gc-low-threshold参数根据磁盘大小适当调低。巡检脚本里加一条磁盘使用率检查超过 75% 就告警。5.5 现象证书过期导致集群组件通信失败原因Kubernetes 集群的证书默认有效期一年kubelet 客户端证书、API Server 证书到期后节点会 NotReadykubectl 也连不上。解决巡检时用kubeadm certs check-expiration查看所有证书到期时间剩 30 天以内就要安排续期。非 kubeadm 部署的集群找到证书目录用openssl x509 -enddate -noout -in cert逐个检查。这个检查频率不用每天每周一次足够但绝对不能漏。6. 让巡检从「跑脚本」变成「看趋势」脚本跑起来只是第一步真正有价值的是把每次巡检结果存下来看趋势。我一般会把关键指标写进 Prometheus 的 Pushgateway或者简单点每次巡检输出一行 JSON 追加到文件用 Grafana 或者甚至 Excel 画个折线图。容器重启次数这周比上周多了几次、节点磁盘使用率的斜率是不是变陡了、Pod 平均就绪时间是不是在拉长——这些趋势比单次快照更能提前预警。一个具体技巧给巡检脚本加一个--diff模式把本次结果和上次结果做对比只输出变化项。比如「容器 A 重启次数从 2 变成 5」「节点 B 磁盘从 60% 涨到 72%」这样每天早上扫一眼就知道昨天夜里发生了什么不用在一堆 INFO 里翻。我自己的习惯是每周五下午花二十分钟把这一周的巡检趋势过一遍把连续三天告警的项拎出来做根因分析而不是每天被单次告警牵着走。巡检的价值不在于跑了多少条命令而在于你能不能从这些命令的输出里看出系统正在往哪个方向走。希望帮到你。本文还有配套的精品资源点击获取
返回列表