Docker容器中运行的进程在主机上的PID是什么?

32

有几个进程在Docker容器中运行,它们的PID在容器命名空间中被隔离,是否有一种方法可以找出它们在Docker主机上的PID?

例如,在Docker容器内运行着一个Apache Web服务器(我使用Docker Hub上的Apache+PHP镜像),当Apache启动时,在容器内创建更多的工作进程。这些工作进程实际上正在处理传入的请求。为了查看这些进程,我在Docker容器内运行pstree命令:

# pstree -p 1
apache2(1)-+-apache2(8)
           |-apache2(9)
           |-apache2(10)
           |-apache2(11)
           |-apache2(12)
           `-apache2(20)

父级Apache进程在容器进程命名空间内的PID 1上运行。然而,从主机的角度来看,它也可以被访问,但是在主机上的PID不同,可以通过运行docker compose命令来确定:
 $ docker inspect --format '{{.State.Pid}}' container
 17985

从这里我们可以看到,容器进程命名空间中的PID 1映射到主机上的PID 17985。因此,我可以在主机上运行pstree来列出Apache进程的子进程:
$ pstree -p 17985
apache2(17985)─┬─apache2(18010)
               ├─apache2(18011)
               ├─apache2(18012)
               ├─apache2(18013)
               ├─apache2(18014)
               └─apache2(18164)

我推断在容器中PID 1映射到主机上的PID 17985,同样地,它也映射:
  • 容器中的PID 8到主机上的PID 18010
  • PID 9到PID 18011;
  • PID 10到PID 18012等等......
这使我能够使用仅在主机上而不是容器中可用的工具(如strace)从docker容器调试进程。
问题是我不知道假设pstree以相同顺序列出容器和主机中的进程有多安全。
如果有人能建议一种更可靠的方法来检测运行在Docker容器内的特定进程在主机上的PID将会很棒。

PIDs 在 Linux 上也可以表示操作系统线程。您可以通过创建多个线程并计算所得到的 PIDs 来使用 Java(或其他语言)进行检查。 - ieugen
3个回答

40

您可以查看/proc/<pid>/status文件,以确定名称空间PID与全局PID之间的映射关系。例如,如果在Docker容器中我启动了几个sleep 900进程,就像这样:

# docker run --rm -it alpine sh
/ # sleep 900 &
/ # sleep 900 &
/ # sleep 900 &

我可以看到它们在容器中运行:

/ # ps -fe
PID   USER     TIME   COMMAND
    1 root       0:00 sh
    7 root       0:00 sleep 900
    8 root       0:00 sleep 900
    9 root       0:00 sleep 900
   10 root       0:00 ps -fe

我可以在主机上查看这些内容:

# ps -fe | grep sleep
root     10394 10366  0 09:11 pts/10   00:00:00 sleep 900
root     10397 10366  0 09:12 pts/10   00:00:00 sleep 900
root     10398 10366  0 09:12 pts/10   00:00:00 sleep 900

对于其中的任何一个,我都可以查看status文件以查看命名空间pid:

# grep -i pid /proc/10394/status
Pid:    10394
PPid:   10366
TracerPid:  0
NSpid:  10394   7

观察 NSpid 行,可以看到在 PID 命名空间内,该进程的 pid 为 7。如果我在主机上杀死进程 10394,确实如此:

# kill 10394

然后,在容器中,我发现 PID 7 不再运行:

/ # ps -fe
PID   USER     TIME   COMMAND
    1 root       0:00 sh
    8 root       0:00 sleep 900
    9 root       0:00 sleep 900
   11 root       0:00 ps -fe

2
看起来这个解决了我的问题,按照这些建议我可以使用这个一行命令获取PID的完整映射:for i in $(ps -ef | grep $(docker inspect --format '{{.State.Pid}}' php-sandbox) | awk '{print $2}') ; do grep NSpid: /proc/$i/status ; done - Luke 10X
4
请注意,NSpid仅在Linux 4.1及以上版本可用,因此在较旧的机器上可能无法使用。作为替代方法,您可以通过docker exec执行grep -l pid /proc/*/sched来查找容器内pid。 - Gordon
这在 Mac 上似乎不起作用。当我运行 ps 命令时,docker top 命令给出的 pid 不存在。其次,在 Mac 上没有 /proc 目录。 - Parth Thakkar
2
这是因为在Mac上,你是在Linux虚拟机中运行Docker。如果你登录Linux虚拟机,你会看到这里描述的行为。 - larsks
很棒的问答和评论。这个命令对我有用 - for i in $(ps -ef | grep \docker inspect --format '{{.State.Pid}}' containerID` | awk '{print $2}') ; do grep NSpid: /proc/$i/status ; done`。有人能提供一下为什么原始命令不起作用吗? - samshers
似乎在CentOS 8,内核4.18上无法工作。在状态和sched文件中都看不到主机pid。 - TangXC

4

如果您知道主机pid或容器pid,您可以通过在主机上搜索所有NSpid映射来查找:

# grep NSpid.*10061 /proc/*/status 2> /dev/null
/proc/1194200/status:NSpid: 1194200 10061
  • 1194200是主机 PID
  • 10061是容器 PID

2>/dev/null 是为了忽略短暂的进程,以避免出现像这样的 grep 错误: grep: /proc/1588467/status: No such file or directory


答案是正确且简短的,但本质上与@larsks的答案原理相同,后者实际上更详细。我因其简洁而选择了这个答案,但现在我将保留相同的答案作为已接受的答案。谢谢。 - Luke 10X
在实践中,虽然对于人类用户来说可能足够接近,但不应在脚本中使用此解决方案。不能保证不会有另一个PID带有10061后缀,这可能会产生两个或更多的结果,而不是一个。 - matt

0
  • 调用Docker SDK API的ContainerList方法,获取容器ID映射Docker镜像
  • 读取/proc/<pid>/cgroup,获取Docker容器ID
  • 您可以获取PID、容器ID和Docker镜像
func GetContainerID(pid int32) string {
    cgroupPath := fmt.Sprintf("/proc/%s/cgroup", strconv.Itoa(int(pid)))
    return getContainerID(cgroupPath)
}

func GetImage(containerId string) string {
    if containerId == "" {
        return ""
    }
    image, ok := containerImage[containerId]
    if ok {
        return image
    } else {
        return ""
    }
}
func getContainerID(cgroupPath string) string {
    containerID := ""
    content, err := ioutil.ReadFile(cgroupPath)
    if err != nil {
        return containerID
    }
    lines := strings.Split(string(content), "\n")
    for _, line := range lines {
        field := strings.Split(line, ":")
        if len(field) < 3 {
            continue
        }
        cgroup_path := field[2]
        if len(cgroup_path) < 64 {
            continue
        }
        // Non-systemd Docker
        //5:net_prio,net_cls:/docker/de630f22746b9c06c412858f26ca286c6cdfed086d3b302998aa403d9dcedc42
        //3:net_cls:/kubepods/burstable/pod5f399c1a-f9fc-11e8-bf65-246e9659ebfc/9170559b8aadd07d99978d9460cf8d1c71552f3c64fefc7e9906ab3fb7e18f69
        pos := strings.LastIndex(cgroup_path, "/")
        if pos > 0 {
            id_len := len(cgroup_path) - pos - 1
            if id_len == 64 {
                //p.InDocker = true
                // docker id
                containerID = cgroup_path[pos+1 : pos+1+64]
                // logs.Debug("pid:%v in docker id:%v", pid, id)
                return containerID
            }
        }
        // systemd Docker
        //5:net_cls:/system.slice/docker-afd862d2ed48ef5dc0ce8f1863e4475894e331098c9a512789233ca9ca06fc62.scope
        docker_str := "docker-"
        pos = strings.Index(cgroup_path, docker_str)
        if pos > 0 {
            pos_scope := strings.Index(cgroup_path, ".scope")
            id_len := pos_scope - pos - len(docker_str)
            if pos_scope > 0 && id_len == 64 {
                containerID = cgroup_path[pos+len(docker_str) : pos+len(docker_str)+64]
                return containerID
            }
        }
    }
    return containerID
}

这段 Golang 代码将获取容器的进程 ID


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接