一台 4 核 8 GB 的 VPS 出现 CPU 周期性冲到 99% 的现象:每隔一两秒短暂回落到七八成,随后再次升高。原作者当时运行的是 sub2api v0.1.177。最终定位到多组没有正常退出的 docker logs --tail 进程持续触发 Docker 守护进程读取容器 JSON 日志;终止这些残留进程后,CPU 占用恢复正常。

需要先说明:这次排障确认了直接诱因和恢复方法,但没有证明这些进程为何被启动、又为何没有退出。原作者怀疑 sub2api 升级脚本或 1Panel 的日志监控,其中后者嫌疑较大;这只是推测,不应直接当成根因。

1. 从高 CPU 进程开始

先查看完整命令行与 CPU 占用:

top -c

现场看到的关键进程类似:

PID   USER  %CPU   COMMAND
822   root  283.4  /usr/bin/dockerd

多核系统中,单个进程显示超过 100% 并不异常:283.4% 大致代表同时消耗 2.83 个逻辑核心。此时只能确定热点在 dockerd,还不能直接判断是某个容器业务进程、日志系统还是 Docker 本身的问题。

2. 用 strace 找到高频系统调用

对 Docker 守护进程做 10 秒汇总采样:

DOCKERD_PID=$(pidof dockerd)
timeout 10 strace -f -c -p "$DOCKERD_PID"

结果中出现约 95,644 次 pread64,说明 dockerd 正在高频按偏移量读取文件。为了查看具体文件描述符,再只跟踪 pread64:

DOCKERD_PID=$(pidof dockerd)
timeout 5 strace \
  -f \
  -e trace=pread64 \
  -p "$DOCKERD_PID" \
  -o /tmp/dockerd-pread.txt

采样中反复出现类似记录:

971434 pread64(102, "", 1024, 689081798) = 0

这里 102 是文件描述符;返回 0 表示本次读取已到文件末尾。大量重复出现同一模式,说明守护进程在不断进行没有读到新数据的读取。

strace -p 会附加到已有进程,-f 跟踪子进程或线程,-c 汇总系统调用,-e trace=pread64 则只保留目标调用。附加线上守护进程会带来额外开销,应缩短采样时间并在低风险窗口操作。

3. 从文件描述符反查容器日志

通过 /proc/<PID>/fd/<FD> 找出文件描述符对应的真实路径:

DOCKERD_PID=$(pidof dockerd)
readlink -f "/proc/$DOCKERD_PID/fd/102"

返回路径指向:

/var/lib/docker/containers/696eef3921d1.../696eef3921d1...-json.log

路径中的长字符串就是容器 ID。接着把它映射到容器名称:

docker ps -a --no-trunc \
  --format '{{.ID}}  {{.Names}}  {{.Status}}' \
  | grep '^696eef3921d1'

现场结果为:

696eef3921d1...  sub2api  Up 45 hours (healthy)

这一步把问题范围从整个 Docker 守护进程缩小到 sub2api 容器的 JSON 日志文件。Docker 官方文档说明,默认 json-file 日志驱动会把容器标准输出和标准错误写入每个容器各自的 JSON 文件;这些文件应由 Docker 守护进程管理,不建议直接修改。

4. 检查残留的日志读取进程

搜索与目标容器有关的 docker logs 进程:

ps -ef | grep -E '[d]ocker.*logs.*sub2api'

现场发现三组长时间未退出的命令:

bash -c docker logs --tail 40 sub2api
docker logs --tail 40 sub2api

bash -c docker logs --tail 30 sub2api
docker logs --tail 30 sub2api

bash -c docker logs --tail 5 sub2api
docker logs --tail 5 sub2api

这些命令没有 -f。按照 Docker 官方说明,普通 docker logs --tail N 应批量读取命令执行时已有的末尾日志并退出;只有 -f 或 --follow 才会持续跟随新输出。因此,现场这些不带 -f 却长期存在的进程属于异常状态,但仅凭这一现象仍不能判定是 Docker、面板、升级脚本还是调用方式中的其他环节造成。

5. 恢复与验证

原作者在确认 PID 与命令后,终止了现场的六个父子进程:

kill 40469 40470 47800 47801 53495 53496

这些 PID 只属于原作者当时的现场,不能直接复制使用。实际操作前必须重新通过 ps 核对 PID、父子关系和完整命令行,避免误杀其他任务。优先发送默认的 SIGTERM 并观察进程是否退出,不要一开始就使用 kill -9。

终止残留进程后,1Panel 显示 CPU 占用恢复正常。建议同时做三项复核:

# 1. 确认残留日志进程已消失
ps -ef | grep -E '[d]ocker.*logs.*sub2api'

# 2. 观察 dockerd CPU 是否持续回落
top -c

# 3. 再做一次短采样,确认 pread64 不再异常增长
DOCKERD_PID=$(pidof dockerd)
timeout 5 strace -f -c -p "$DOCKERD_PID"

6. 后续预防

这次处理只是消除直接诱因。为了找出真正触发源,应继续检查残留进程的父 PID、启动时间、面板任务记录和升级脚本调用链,并观察问题是否复现。若使用默认 json-file 日志驱动,还应设置日志轮转,避免日志无限增长;Docker 官方示例是为新建容器配置 max-size 和 max-file。现有容器不会自动继承新的守护进程日志配置,通常需要重新创建后才会生效。

官方参考:

原作者:WEP(Wep-56)
原帖:https://linux.do/t/topic/2783014