宿主机进程反查 Docker 容器:一个高 CPU PID 的溯源实战

举报
yd_248971855 发表于 2026/11/02 09:23:53 2026/11/02
【摘要】 宿主机进程反查 Docker 容器:一个高 CPU PID 的溯源实战摘要:宿主机 top 里发现 CPU 飙高的进程,却看不出它属于哪个容器——这是接手容器化环境时最常见的“看得到摸不到”问题。本文从 /proc/<pid>/cgroup 的底层机制讲起,给出从 PID 反查容器 ID 的多种命令链,覆盖 docker top、docker inspect、cgroup 文件系统三条路径...

宿主机进程反查 Docker 容器:一个高 CPU PID 的溯源实战

摘要:宿主机 top 里发现 CPU 飙高的进程,却看不出它属于哪个容器——这是接手容器化环境时最常见的“看得到摸不到”问题。本文从 /proc/<pid>/cgroup 的底层机制讲起,给出从 PID 反查容器 ID 的多种命令链,覆盖 docker top、docker inspect、cgroup 文件系统三条路径,并延伸到 CPU 限流排查和脚本化巡检,适用于 Docker 主机日常运维与故障定位。

一、场景引入

某天宿主机告警:CPU 使用率持续 90%+。登上去一看:

$ top
  PID USER      %CPU  COMMAND
 29656 root     198.7  java
 30122 root      35.2  node

一个 java 进程吃了两个核,但你的服务全是容器化部署的,宿主机上根本不应该直接跑业务进程。这个 PID 是谁家容器里的?宿主机有几十个容器在跑,总不能挨个 docker exec 进去找。
反过来还有一个对称问题:容器内看到的 PID 从 1 开始编号,和宿主机 PID 完全对不上。运维在宿主机上 strace、perf 一个容器内进程时,需要知道宿主机侧的 PID 才能挂上工具。
这两个问题共享同一套解法,核心都藏在 /proc/<pid>/cgroup 里。

二、核心机制:/proc/<pid>/cgroup 里的容器指纹

Linux 内核给每个进程维护一个 /proc/<pid>/cgroup 文件,记录它归属的 cgroup 路径。Docker 启动容器时会为它创建专属 cgroup,容器内所有进程的 cgroup 路径里都带着容器 ID——这就是反查的钥匙。
对宿主机上任意一个可疑 PID,先看它是不是容器进程:

$ cat /proc/29656/cgroup

cgroup v1(较老主机)输出形如:

12:blkio:/docker/bf85501b3084601ba76b8cb303917134d58b5e7783c14c1636ff1c56a3d83c1f
...

cgroup v2(Ubuntu 22.04+、Debian 12+ 等新系统)输出形如:

0::/system.slice/docker-bf85501b3084601ba76b8cb303917134d58b5e7783c14c1636ff1c56a3d83c1f.scope

不管哪个版本,路径里那串 64 位十六进制就是完整容器 ID。截取前 12 位即可用于 docker 命令:

$ docker ps --no-trunc | grep bf85501b3084
$ docker inspect --format '{{.Name}}' bf85501b3084
/backend

如果 /proc/<pid>/cgroup 的路径是 / 或不含 docker 字样,说明这是宿主机原生进程,排查方向就变了。

三、三条反查命令链

命令链一:docker ps --no-trunc 粗筛

最直接的一步。先拿到容器的完整 ID:

$ docker ps --no-trunc
CONTAINER ID                                                       IMAGE
bf85501b3084601ba76b8cb303917134d58b5e7783c14c1636ff1c56a3d83c1f   backend:runtime

把 cat /proc/29656/cgroup 里的 ID 段和列表比对,命中即锁定。容器不多时这招最快。

命令链二:docker inspect 精确反查

如果知道 PID 想反向确认容器,可用 cgroup ID 去匹配。更常用的场景是已知容器、找它的宿主机 PID(用于 strace/perf):

$ docker inspect --format '{{.State.Pid}}' backend
29610

输出即该容器主进程(PID 1)在宿主机上的真实 PID。拿到后:

$ docker top backend -eo pid,ppid,pcpu,comm,args
PID    PPID  %CPU COMMAND
29610  1     0.1  java -jar app.jar
29656  29610 198.7 java

能看到 29656 确实是这个容器的子进程,父子关系(PPID 指向容器主进程)也验证了归属。

命令链三:cgroup 文件系统直读

不想碰 docker 命令时(比如排查脚本运行在最小化环境里),直接读文件系统:

# cgroup v1
$ CID=$(docker inspect --format '{{.Id}}' backend)
$ cat /sys/fs/cgroup/memory/docker/${CID}/memory.usage_in_bytes
# cgroup v2
$ cat /sys/fs/cgroup/system.slice/docker-${CID}.scope/memory.current

这个路径同时是内存排查的入口,能直接读出容器内存占用、上限、OOM 次数等真实数据。

四、从容器视角到宿主机视角的 PID 映射

容器内 PID 命名空间独立,同一个进程两套编号:

视角 查看命令 输出
容器内 docker exec backend ps 从 1 开始的编号
宿主机 docker top backend -o pid,cmd 宿主机真实 PID
docker top 的输出全部是宿主机视角,可直接用宿主机工具操作。典型组合:
# 找到容器内某进程的宿主机 PID,直接 strace
$ HOST_PID=$(docker top backend -o pid,cmd | grep "my-process" | awk '{print $1}')
$ sudo strace -p $HOST_PID

这是容器排障的高级技巧——不进容器、不装工具,用宿主机全套调试手段分析容器内进程。

五、反查到容器之后的下一步:CPU 限流排查

锁定容器后,如果它 CPU 高但宿主机整体没满,要检查是不是限流导致(限流会让容器内进程表现为高 CPU,即使宿主机有大量空闲)。

# 先看整体容器资源占用
$ docker stats --no-stream --format \
  'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}'
# 下钻到具体容器内的进程
$ docker top backend -eo pid,ppid,pcpu,pmem,comm,args
# 配合日志
$ docker logs --since 10m --tail 100 backend

docker stats 里 CPUPerc 超过 100% 是正常现象(多核叠加)。要确认限流是否真的发生,读 cgroup 的 CPU 统计:

# cgroup v2
$ cat /sys/fs/cgroup/system.slice/docker-${CID}.scope/cpu.stat

关注 nr_throttled 和 throttled_usec——非零说明发生过限流,此时应调高 --cpus 或优化应用逻辑,而不是继续在宿主机侧找“谁在抢 CPU”。

六、封装成巡检脚本

把反查流程固化成脚本,输入 PID、输出容器名:

#!/bin/bash
# 用法: ./pid2container.sh <PID>
PID=$1
CGROUP=$(cat /proc/$PID/cgroup 2>/dev/null)
# cgroup v1 和 v2 都兼容的提取方式
CID=$(echo "$CGROUP" | grep -oE '[0-9a-f]{64}' | head -1 | cut -c1-12)
if [ -z "$CID" ]; then
    echo "PID $PID 是宿主机原生进程"
else
    docker inspect --format '{{.Name}}' $CID
fi

配合告警系统,在 CPU 飙高时自动执行,运维看到的就是"backend 容器"而不是一串裸 PID——排查起点直接前进了两步。

七、常见坑

坑一:容器已停止,cgroup 文件消失。容器停止后 /proc/<pid>/cgroup 无从谈起,只能在停止前采集,或事后看容器日志(docker logs 在容器删除前都有效)。
坑二:cgroup v1 与 v2 路径差异。v1 是 /docker/<id>,v2 是 /system.slice/docker-<id>.scope,脚本里两种模式都要兼容。
坑三:Docker 套 K8s 环境。K8s 场景下容器 cgroup 路径形如 /kubepods/podxxx/<id>,不是 docker 字样,用 crictl ps 而非 docker ps 查。
坑四:jq 不可用时。直接用 grep 加 cut 提取 ID 段,不依赖额外工具,脚本可移植性更好。

八、总结

PID 反查容器的完整决策链:

步骤 命令 目的
1. 定性 cat /proc/<pid>/cgroup 判断是否容器进程
2. 提取 从路径截取 64 位容器 ID 拿到钥匙
3. 归属 docker inspect --format '{{.Name}}' <ID前12位> 得到容器名
4. 验证 docker top <容器名> -o pid,ppid,cmd 确认父子关系
5. 下钻 docker stats + 容器日志 + cpu.stat 定位根因
理解“容器进程的 cgroup 路径必带容器 ID”这个底层事实,比死记命令更重要——它能推导出所有反查命令的原理,遇到非标准环境(K8s、Podman、containerd)也能举一反三。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。