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

[ 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] : 我也注意到了 确定进程是否在 lxc/Docker 内运行,但它似乎在这种情况下不工作,我的容器的 /proc/1/cgroup的内容是:

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

No/lxc/Container erid

108049 次浏览

检查是否在 Docker 容器内,可以通过 /proc/1/cgroup进行。正如 这篇文章建议的那样,你可以做到以下几点:

在一个码头集装箱外面,/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:/

托马斯的代码解决方案:

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

注意

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

关于阅读的补充说明

Docker 在容器的目录树的顶部创建 .dockerenv.dockerinit(在1.11版中删除)文件,因此您可能想检查它们是否存在。

这样应该行得通。

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

我们需要排除在容器中运行的进程,但是我们决定将 /proc/<pid>/ns/pid/proc/1/ns/pid的 init 系统进行比较,而不仅仅检查 docker cgroup。例如:

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 在不同的命名空间中运行。

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

对我有效的方法是检查“/”的 inode 号 在码头内部,这是一个非常高的数字。 在码头外面,有一个很低的数字,比如“2”。 我认为这种方法还取决于所使用的 FileSystem。

例子

码头内部:

# 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

根据 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?

通过/proc/% s/cgroup 检查 docker 中进程的 golang 代码包括 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
}

使用环境变量

在我看来,我更喜欢在 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行。例如,您可以看到 node:12.20.1上的 I FROM,它包含许多额外的东西(例如,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,您可以轻松地在那里定义 envar。例如,您的 docker-compose.yml文件可以如下所示:

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

2022年在 macOS 上需要这个能力,只有 作者:@at0S仍然可以从所有其他选项中工作。

  • 除非另外配置,否则 /proc/1/cgroup在容器中只有根目录
  • /proc/1/sched显示了相同的容器内进程编号。名称不同(bash) ,但不是很便携。
  • 如果您自己配置容器,那么环境变量可以工作,但是没有一个默认的环境变量有帮助

我确实发现了其他答案中没有列出的一个选项: /proc/1/mounts包含一个 overlay文件系统,其路径中有“ docker”。

对我来说,只要我知道系统程序/脚本将运行在上面,就可以确认使用 PID 1运行的是否是 systemd(或等效的)。如果不是,那就是个容器。 这对于任何 Linux 容器都应该是正确的,而不仅仅是 docker。