如何检查进程是否在Docker容器中运行?

151
[更新1] 我有一个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:/

是否有任何标志可以用来确定此进程是否在容器内运行?

[更新2]:我也注意到了如何确定进程是否在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


1
这不是一个非常清晰的问题。你为什么需要这个? - Henk Langeveld
2
确定进程是否在 LXC/Docker 中运行 - Johannes 'fish' Ziemke
在我的情况下,@fish 没有 /lxc/<containerid>,请查看更新。 - harryz
1
@HenkLangeveld内核参数在Docker容器中是只读的,因此我需要知道我的shell是否在容器内运行,并在我的shell中禁用内核函数。请参见更新。 - harryz
脚本中的某些步骤尝试修改内核参数,在Docker中运行时需要跳过这些步骤。明白。 - Henk Langeveld
13个回答

189
Docker会在容器的目录树顶部创建.dockerenv.dockerinit (v1.11中已移除)文件,因此您可能希望检查这些文件是否存在。
类似以下内容即可正常工作。
#!/bin/bash
if [ -f /.dockerenv ]; then
    echo "I'm inside matrix ;(";
else
    echo "I'm living in real world!";
fi

3
当然,除非您或其他人在主机上创建了/.dockerinit(可能是出于偶然),否则在容器外它将是错误的。 - sosiouxme
27
如果有其他人能够在其中运行,那么他们就是 root 用户,你面临比确认你是否在 docker 中更糟糕的问题。 - gm3dmo
28
长期依赖 /.dockerenv 可能会存在问题。这种使用方式并不是它的设计初衷,请注意。(参考链接为英文) - ReactiveRaven
2
就此而言,Podman 不会创建 /.dockerenv。它确实会创建 /run/.containerenv,但根据类似的逻辑,这似乎是不可依赖的实现细节。请参见 https://github.com/containers/libpod/issues/3586 以了解一些 Podman 特定的替代方案。 - Beni Cherniavsky-Paskin
2
@cowlinator Windows 容器更加简单,只需检查 HKLM\System\CurrentControlSet\Control\ContainerType,任何值都意味着您在 Windows 容器中。 - Mark Lopez
显示剩余2条评论

99
要在Docker容器内部检查是否在Docker容器内部,可以通过 /proc/1/cgroup 进行。正如此帖子所建议的,你可以执行以下操作: 在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:/

6
“在 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 - samfr
4
这几乎只适用于Linux,而不适用于Darwin或其他不使用procfs的BSD。 - Christian
2
@Christian Docker/LXC是仅限于Linux的东西,所以这很好,对吧 :)? - Robert Lacroix
6
在 Docker 容器中,cat /proc/1/cgroup 命令只会输出 0::/。有人能告诉我发生了什么事吗? - ipid
这对我来说行不通,在Ubuntu 22.04上运行Debian容器。/proc/self/cgroup/proc/1/cgroup只包含0::/ - undefined
显示剩余4条评论

38

我们使用 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

7
根据操作系统的不同,“init”可能需要替换为“systemd”。有关systemd的更多信息在此处 - BrianV
3
正如 @BrianV 所提到的,这对我也不起作用。 - Shubham Chaudhary
5
在运行在 k8s 集群中的 Docker 容器中,head -n1 /proc/1/sched 命令返回 dumb-init (1, #threads: 1),因此该答案中建议的检查方法失败了。(此外,与答案所说的相反,在容器中这一行显示的 PID 是 "1"。) - Stefan Majewsky
1
这个答案是唯一一个适用于Docker v20的解决方案。最简单的代码包装(如果使用不同于systemd或init的_init_运行,则退出代码为0:awk '{exit ($1 ~ /^init|systemd$/)}' /proc/1/sched - Kokos
1
请注意,这似乎不再起作用。至少在Ubuntu Jammy(内核5.15.0和Docker 20.10.12)中,/proc/nnn/sched顶部行中的PID现在是在容器内查看时来自容器内部的PID。 - cabbey
显示剩余5条评论

36

使用环境变量

就我个人而言,我更喜欢在docker镜像中设置一个环境变量,然后再由应用程序检测它。

例如,这是演示Dockerfile的配置开头:

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

第二行设置了一个名为DOCKER_RUNNING的环境变量,很容易检测到。但是在多阶段构建中,每次从外部镜像FROM后,您将不得不重复ENV行。例如,您可以看到我是基于node: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 变量,因为它有一个不同的基础镜像。

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

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

1
这很棒,只是要明确一下。 - four43
1
请注意,某些程序在启动时会清除环境(例如shiny服务器)。如果您打开一个shell,变量是存在的,但在程序内部为空。在这种情况下,请查看如何为该特定软件配置这些变量。 - Ott Toomet

23

托马斯的代码解决方案:

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

注意

read命令使用一个虚拟变量是一个简单的习惯用法,用于检查命令是否产生任何输出。它是将可能冗长的grepawk转换为对模式进行测试的紧凑方法。

有关read的附加说明


11
除非在某些环境下会失败,例如 3:cpu,cpuacct:/system.slice/docker-1ce79a0dec4a2084d54acf187a1e177e0339dc90d0218b48b4456576ecaf291e.scope 将无法匹配。更简单的方法是使用 grep -q docker /proc/1/cgroup;从那里得到的结果代码也应该足够了。 - larsks
2
read 命令在 bash 中可能有效,但在大多数使用的 dash shell 中,您必须使用 read dummy(或类似命令) 或者使用类似 [ -n "$(command)" ] 的结构。 - Daniel Alder
@DanielAlder 棒极了,Daniel。我会更新文本的。 - Henk Langeveld
1
之前这里声称任何 Bourne 兼容的 shell 都支持不带变量名的普通 read 命令。但实际上只有 bash 和 ksh93 支持。Opengroup 只规定了 read var 的用法,没有提到至少需要一个变量的情况下 read 的行为。在 bashksh93 中,如果没有给出 _var_,则 read 命令会使用 shell 变量 REPLY - Henk Langeveld
1
为什么我们不能只使用 awk -F: '$3 ~ /docker/' /proc/self/cgroup | read?在我这儿运行正常。 - Shubham Chaudhary
显示剩余3条评论

17

我使用的方法是检查'/.'的inode编号。 在Docker内部,它是一个非常大的数字。 在Docker外部,它是一个非常低的数字,像'2'一样。 我认为这种方法也取决于正在使用的文件系统。

示例

在Docker内部:

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

在 Docker 之外

$ 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。 - bo0k
2
ls -di / 相同吗?似乎在不同的平台上inode号码不可靠。 - yurenchen
这是我找到的唯一可行的方法,用于区分Xen domU主机和其Docker容器。 - Michael Altfield
2
stat -c %i is simpler than ls -ali / | sed '2!d' |awk {'print $1'} - Neil Mayhew
chroot内部,'/'的inode号码会返回不同的值,但这并不意味着我在一个容器内部。 - elmazzun

3
我们需要排除在容器中运行的进程,但是我们决定不仅检查docker控制组,而是将/proc/<pid>/ns/pid/proc/1/ns/pid中的init系统进行比较。例如:
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-scheduled Docker容器内部,readlink /proc/self/ns/pidreadlink /proc/1/ns/pid产生相同的输出。 - Stefan Majewsky
1
@StefanMajewsky 可以尝试使用 https://github.com/jessfraz/amicontained 来查看容器运行时启用了哪些功能。 - Greg Bray

3

根据Dan Walsh的评论,关于使用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

这只是告诉你正在运行在一个容器中,但不知道使用哪个运行时环境。
没有检查其他容器运行时环境,但https://opensource.com/article/18/2/understanding-selinux-labels-container-runtimes提供了更多信息,并建议这被广泛使用,也许也适用于rkt和lxc?

3

通过 /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
}

能否解释一下这段代码的作用?谢谢。 - Eric

0
只要我知道系统程序/脚本将在哪里运行,对我有效的方法是确认以 PID 1 运行的是 systemd(或等效物)。如果不是,则是一个容器。这对于任何 Linux 容器都应该成立,而不仅仅是 Docker。

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