得到进程在下层 Namespace 里的 PID

容器通过 Linux Namespace 技术,对网络、PID、用户等等信息的隔离。

从进程的角度来说,你可以在 Host 上看到所有容器内的进程。

或者更准确的说,当你在 Host 所在的 root PID namespace 时,可以看到其所有下层 PID namespace 里的进程。

当你在 Host 上得到了一个进程的 PID,比如是 6052,那么怎么知道它在其所属的 PID namespace 里的 PID 呢?

Linux Kernel >= 4.1

host_pid 指代你在 root PID namespace 里看到的进程 ID

从 Linux Kernel 4.1 开始,在 /proc/[host_pid]/status 里有一个 NSpid 字段,它就是其在自己的 PID namespace 里的 PID:

1
$ grep 'NSpid' /proc/[host_pid]/status | awk '{print $2}'

Linux Kernel < 4.1

在 Linux Kernel 4.1 之前的版本则没有 NSpid 字段可以利用,那么就需要迂回一点,下面有两种方法。

方法一

利用 procfs 的 /proc/[pid]/sched 伪文件来匹配查找。

如果你打开任何进程的 /proc/[pid]/sched 文件,可以看到类似下面的内容:

1
2
3
4
5
6
7
$ cat /proc/8416/sched
bash (918, #threads: 1)
-------------------------------------------------------------------
se.exec_start                                :    2664031641.263979
se.vruntime                                  :       2781932.438772
se.sum_exec_runtime                          :             5.544571
se.nr_migrations                             :                    5

8416 是进程在下层 namespace 里的 PID,而 918 是进程在 root namespace 里的 PID。

所以你可以这样:

  • 进入 host_pid 的 namespace
  • 用 ps 列出这个 namespace 下所有的 PID,下面称为 cont_pid,然后遍历:
    • 提取下层 namespace 里 /proc/[cont_pid]/sched 里的 PID
    • 如果和 host_pid 一样,则 cont_pid 就是答案

下面是脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function get_container_pid_by_host_pid
{
  local host_pid=$1
  for cont_pid in $(nsenter --target "$host_pid" --mount --pid ps -o 'pid=')
  do
    # 通过 /proc/[pid]/sched 来找 host_pid 对应的 cont_pid (容器 pid),这个文件的内容如下:
    local sched_pid=$(nsenter --target "$host_pid" --mount --pid head -1 /proc/"$cont_pid"/sched | sed 's/[(),#:]//g' | awk '{print $2}')
    if [[ "$host_pid" == "$sched_pid" ]]; then
      echo "$cont_pid"
      return 0
    fi
  done
  echo ""  
}

上面这个方法是受到 jattach 项目的这个 issue 和这个 commit 的启发。

方法二

利用 procfs 的 /proc/[pid]/maps 伪文件来匹配查找。

具体原理是,只要两个 PID 实际指向的是同一个进程,那么 /proc/[pid]/maps 文件就应该是一样的,因为这个文件记录了进程的内存映射信息。

具体步骤:

  • 进入 host_pid 的 namespace
  • 用 ps 列出这个 namespace 下所有的 PID,下面称为 cont_pid,然后遍历:
    • 在 root namespace 里对 /proc/[host_pid]/maps 计算 md5sum
    • 在下层 namespace 里对 /proc/[cont_pid]/maps 计算 md5sum
    • 比较两者的结果,如果一样,则 cont_pid 就是答案

下面是脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function get_container_pid_by_host_pid
{
  local host_pid=$1
  for cont_pid in $(nsenter --target "$host_pid" --mount --pid ps -o'pid=')
  do
    # 通过 md5 来找 host_pid 对应的 cont_pid (容器 pid)
    # 注意 /proc/[pid]/maps 文件会变,如果两次命令的间隔期间文件变了,则得到的 md5 会不同
    host_maps_md5=$(md5sum /proc/"$host_pid"/maps | awk '{print $1}')
    cont_maps_md5=$(nsenter --target "$host_pid" --mount --pid md5sum /proc/  "$cont_pid"/maps | awk '{print $1}')
    if [[ "$host_maps_md5" == "$cont_maps_md5" ]]; then
      echo "$cont_pid"
      return 0
    fi
    echo ""
  done
}

这个方法有两个点要注意:

  1. 是否会出现两个不同的进程 /proc/[pid]/maps 一样的情况,这个以我目前的知识来说,不能完全排除这种可能性。
  2. /proc/[pid]/maps 是随时变化的,在上面的脚本里实际上对同一个进程的 /proc/[pid]/maps 做了两次 md5sum,因此可能会出现匹配不到的情况。

参考资料

版权

评论