如何检查一个进程是不是在 docker 容器内运行?

Posted

技术标签:

【中文标题】如何检查一个进程是不是在 docker 容器内运行?【英文标题】:How to check if a process is running inside docker container?如何检查一个进程是否在 docker 容器内运行? 【发布时间】:2014-06-24 03:35:37 【问题描述】:

[Updated1] 我有一个shell,它会在一些函数中改变TCP内核参数,但是现在我需要让这个shell在Docker容器中运行,也就是说,shell需要知道它在一个容器中运行并停止配置内核。

现在我不知道如何实现,这里是容器内/proc/self/cgroup 的内容:

9:hugetlb:/
8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/

我可以使用上面的任何标志来确定此进程是否在容器内运行?

[Updated2]:我也注意到Determining if a process runs inside lxc/Docker,但在这种情况下似乎不起作用,我的容器/proc/1/cgroup中的内容是:

8:perf_event:/
7:blkio:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/docker/25ef774c390558ad8c4e9a8590b6a1956231aae404d6a7aba4dde320ff569b8b
1:cpuset:/

没有 /lxc/containerid

【问题讨论】:

不是一个很清楚的问题。为什么需要这个? 重复***.com/questions/20010199/… @fish no /lxc/ 在我的情况下,请参阅更新 @HenkLangeveld 内核参数在 Docker 容器中是只读的,所以我需要知道我的 shell 是否在容器中运行并在我的 shell 中禁用内核功能。查看更新。 脚本中的一些步骤尝试修改内核参数,在Docker中运行时需要跳过。清除。 【参考方案1】:

golang 代码,通过 /proc/%s/cgroup 来检查 docker 中的进程,包括 k8s 集群

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

【讨论】:

想解释一下代码的作用?谢谢。【参考方案2】:

使用环境变量

为了我的钱,我更喜欢在 docker 映像中设置一个环境变量,然后应用程序可以检测到该环境变量。

例如,这是一个演示 Dockerfile 配置的开始:

FROM node:12.20.1 as base
ENV DOCKER_RUNNING=true
RUN yarn install --production
RUN yarn build

第二行设置了一个名为 DOCKER_RUNNING 的 envar,然后很容易检测到它。问题在于,在多阶段构建中,每次 FROM 关闭外部图像时,您都必须重复 ENV 行。例如,您可以看到我 FROMnode:12.20.1 不同,其中包含很多额外的东西(例如 git)。稍后在我的Dockerfile 中,然后我将COPY 转换为基于node:12.20.1-slim 的新图像,该图像要小得多:

FROM node:12.20.1-slim as server
ENV DOCKER_RUNNING=true
EXPOSE 3000
COPY --from=base /build /build
CMD ["node", "server.js"]

即使此映像目标server 位于同一个Dockerfile 中,它也需要重新定义 ENV var,因为它具有不同的基本映像。

如果您使用 Docker-Compose,您可以轻松地在那里定义一个 envar。例如,您的 docker-compose.yml 文件可能如下所示:

version: "3.8"
services:
  nodeserver:
    image: michaeloryl/stackdemo
    environment:
      - NODE_ENV=production
      - DOCKER_RUNNING=true

【讨论】:

这很好,只是很明确【参考方案3】:

基于 Dan Walsh 的 comment 关于使用 SELinux ps -eZ | grep container_t,但不需要安装 ps

$ podman run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c56,c299
$ podman run --rm alpine cat /proc/1/attr/current
system_u:system_r:container_t:s0:c558,c813
$ docker run --rm fedora:31 cat /proc/1/attr/current
system_u:system_r:container_t:s0:c8,c583
$ cat /proc/1/attr/current
system_u:system_r:init_t:s0

这只是告诉你你在 a 容器中运行,而不是在哪个运行时。

没有检查其他容器运行时,但https://opensource.com/article/18/2/understanding-selinux-labels-container-runtimes 提供了更多信息并表明这已被广泛使用,可能也适用于 rkt 和 lxc?

【讨论】:

【参考方案4】:

对我有用的是检查 '/.' 的 inode 号 在码头内部,这是一个非常高的数字。 在码头外,它是一个非常低的数字,例如“2”。 我认为这种方法还取决于所使用的文件系统。

例子

码头内部:

# ls -ali / | sed '2!d' |awk 'print $1'
1565265

码头外

$ ls -ali / | sed '2!d' |awk 'print $1'
2

在脚本中:

#!/bin/bash
INODE_NUM=`ls -ali / | sed '2!d' |awk 'print $1'`
if [ $INODE_NUM == '2' ];
then
        echo "Outside the docker"
else
        echo "Inside the docker"
fi

【讨论】:

在 MSYS2 中 ls -ali / | sed '2!d' |awk 'print $1' 232779805740174872 ls -di / ?似乎 inode num 在不同平台上不可靠 这是唯一能让我区分 Xen domU 主机和它的 docker 容器的方法 stat -c %ils -ali / | sed '2!d' |awk 'print $1' 简单【参考方案5】:

我们使用 proc 的 sched (/proc/$PID/sched) 来提取进程的 PID。容器内进程的 PID 与主机(非容器系统)上的 PID 不同。

例如,/proc/1/sched 在容器上的输出 将返回:

root@33044d65037c:~# cat /proc/1/sched | head -n 1
bash (5276, #threads: 1)

在非容器主机上:

$ cat /proc/1/sched  | head -n 1
init (1, #threads: 1)

这有助于区分您是否在容器中。例如你可以这样做:

if [[ ! $(cat /proc/1/sched | head -n 1 | grep init) ]]; then 
    echo in docker
 else 
    echo not in docker
 fi

【讨论】:

这实际上是一个非常有价值的信息。谢谢 根据操作系统,“init”可能需要替换为“systemd”。有关 systemd 的更多信息here。 正如@BrianV 提到的,这对我也不起作用。 在 k8s 集群上运行的 Docker 容器中,head -n1 /proc/1/sched 返回dumb-init (1, #threads: 1),因此此答案中建议的检查失败。 (此外,与答案所暗示的相反,PID 在该行中显示为“1”,尽管我在容器中执行此操作。) 这绝对不是一个通用的解决方案。您可以(在某种程度上)对容器的 PID 1 使用任何您想要的东西。例如如果你这样做docker run --init ...,它将是docker-init。如果你这样做,例如docker run ... head -n 1 /proc/1/sched 将是 head【参考方案6】:

Docker 在容器目录树的顶部创建 .dockerenv.dockerinit (removed in v1.11) 文件,因此您可能需要检查它们是否存在。

这样的事情应该可以工作。

#!/bin/bash
if [ -f /.dockerenv ]; then
    echo "I'm inside matrix ;(";
else
    echo "I'm living in real world!";
fi

【讨论】:

当然,除非您或其他人在您的主机上创建了/.dockerinit(可能是偶然的),在这种情况下,它在容器之外是错误的。 如果其他人在 / 那么他们是 root 并且你遇到的问题比知道你是否在 docker 中更糟糕。 谨防长期依赖/.dockerenv。它是not intended to be used this way。 fwiw,Podman 不会创建 /.dockerenv。它确实创建了/run/.containerenv,但通过类似的逻辑,听起来像是不依赖的实现细节。请参阅 github.com/containers/libpod/issues/3586 了解一些特定于 podman 的替代方案。【参考方案7】:

我们需要排除在容器中运行的进程,但我们决定将/proc/&lt;pid&gt;/ns/pid/proc/1/ns/pid 的init 系统进行比较,而不是只检查docker cgroups。示例:

pid=$(ps ax | grep "[r]edis-server \*:6379" | awk 'print $1')
if [ $(readlink "/proc/$pid/ns/pid") == $(readlink /proc/1/ns/pid) ]; then
   echo "pid $pid is the same namespace as init system"
else
   echo "pid $pid is in a different namespace as init system"
fi

或者在我们的例子中,如果进程不在容器中,我们想要一个生成错误的行

bash -c "test -h /proc/4129/ns/pid && test $(readlink /proc/4129/ns/pid) != $(readlink /proc/1/ns/pid)"

我们可以从另一个进程执行,如果退出代码为零,则指定的 PID 在不同的命名空间中运行。

【讨论】:

对我不起作用。在 k8s 调度的 Docker 容器中,readlink /proc/self/ns/pidreadlink /proc/1/ns/pid 产生相同的输出。 @StefanMajewsky 可能想尝试使用github.com/jessfraz/amicontained 来查看容器运行时中启用了哪些功能。【参考方案8】:

Thomas 的代码解决方案:

running_in_docker() 
  (awk -F/ '$2 == "docker"' /proc/self/cgroup | read non_empty_input)


注意

带有虚拟变量的read 是一个简单的习惯用法,用于这会产生任何输出吗?。这是一种将可能很冗长的grepawk 转换为模式的测试 的紧凑方法。

Additional note on read

【讨论】:

除了...这在某些环境中会失败,因为例如,3:cpu,cpuacct:/system.slice/docker-1ce79a0dec4a2084d54acf187a1e177e0339dc90d0218b48b4456576ecaf291e.scope 将不匹配。 grep -q docker /proc/1/cgroup 更简单;结果代码也应该足够了。 read 可能适用于bash,但在最常用的dash shell 中,您必须使用read dummy(或类似的)或使用[ -n "$(command)" ] 之类的构造 @DanielAlder 很好,丹尼尔。我会更新文本。 之前声称任何与 Bourne 兼容的 shell 都支持没有变量名的普通 read。这仅适用于 bash 和 ksh93。 Opengroup 仅指定read var 并且没有提及read 行为而没有至少一个变量。在 bashksh93 中,如果没有给出 var,则 read 使用 shell 变量 REPLY 为什么我们不能只使用awk -F: '$3 ~ /docker/' /proc/self/cgroup | read?为我工作。【参考方案9】:

可以通过/proc/1/cgroup 来检查 Docker 容器是否在 Docker 容器中。正如this post 建议的那样,您可以执行以下操作:

在 docker 容器之外,/proc/1/cgroup 中的所有条目都以 / 结尾,您可以在此处看到:

vagrant@ubuntu-13:~$ cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/
5:memory:/
4:cpuacct:/
3:cpu:/
2:cpuset:/

在 Docker 容器内,一些控制组将属于 Docker(或 LXC):

vagrant@ubuntu-13:~$ docker run busybox cat /proc/1/cgroup
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
5:memory:/
4:cpuacct:/
3:cpu:/docker/3601745b3bd54d9780436faa5f0e4f72bb46231663bb99a6bb892764917832c2
2:cpuset:/

【讨论】:

@Founder 答案更简洁 严格来说,“在 docker 容器之外 /proc/1/cgroup 中的所有条目都以 / 结束”。例如,在 ubuntu 16.04 上,我有:12:perf_event:/ 11:blkio:/init.scope 10:cpuset:/ 9:devices:/init.scope 8:hugetlb:/ 7:cpu,cpuacct:/init.scope 6:net_cls,net_prio:/ 5:memory:/init.scope 4:pids:/init.scope 3:rdma:/ 2:freezer:/ 1:name=systemd:/init.scope 这几乎只适用于 Linux,不适用于 Darwin 或其他甚至不使用 procfs 的 BSD。 @Christian Docker/LXC 是只有 Linux 的东西,所以没关系,对吧 :)? @RobertLacroix 所以你说如果你没有找到 procfs,你不在 Docker 中?好吧,我想这很公平......

以上是关于如何检查一个进程是不是在 docker 容器内运行?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查 docker 引擎和 docker 容器是不是正在运行?

通过Docker进程pid获取容器id

Docker容器内多进程管理(草稿)

docker容器内远程调试运行进程

如何对 Docker 容器中运行的 Spring Boot 应用程序进行健康检查?

Dockerfile的HEALTHCHECK指令