如何确定进程是不是在 lxc/Docker 内部运行?

Posted

技术标签:

【中文标题】如何确定进程是不是在 lxc/Docker 内部运行?【英文标题】:How to determine if a process runs inside lxc/Docker?如何确定进程是否在 lxc/Docker 内部运行? 【发布时间】:2013-11-29 09:13:43 【问题描述】:

有没有办法确定一个进程(脚本)是否在 lxc 容器(〜 Docker 运行时)内运行?我知道有些程序能够检测它们是否在虚拟机中运行,lxc/docker 有类似的东西吗?

【问题讨论】:

这可能看起来很迂腐,但最好重新表述您的问题以描述您遇到的问题并询问如何解决它 - 否则,该问题更有可能被关闭.在许多情况下,很难做出这种改变,但在你的情况下,如果你愿意的话,简单地改写并不难。 在容器内发出此命令时有一个有趣的响应:正常运行时间 【参考方案1】:

Docker 在容器内目录树的根目录下创建一个.dockerenv 文件。 你可以运行这个脚本来验证

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

更多: Ubuntu 实际上有一个 bash 脚本:/bin/running-in-container,它可以返回调用它的容器类型。可能会有所帮助。 不过不知道其他主要发行版。

【讨论】:

在 Debian 上 /bin/running-in-containerupstart 提供。随着向 systemd 的过渡,它可能会消失。我希望不会 - 这听起来很有用! “在目录树的顶部”,这是什么意思?那在哪里? 其他人指出检查.dockerenv是not recommended 注意:仅当运行时是 docker 守护进程时,对 .dockerenv 的测试才有效。如果您使用的是 podman 或其他东西,则会失败。 Ubuntu 18.0.4 没有/bin/running-in-container【参考方案2】:

最可靠的方法是查看/proc/1/cgroup。它会告诉你初始化进程的控制组,当你不是在容器中时,所有层次结构都是/。当您一个容器中时,您将看到锚点的名称。对于 LXC/Docker 容器,它将分别类似于 /lxc/<containerid>/docker/<containerid>

【讨论】:

docker 现在在这些路径中使用 docker 而不是 lxc 不适用于 lxd/lxc 容器,但 ***.com/a/20010626/170230 可以。 在更高版本的 systemd 中,您似乎无法对所有 cgroup 使用 / 依赖进程 1;在我的 Debian 9 系统(systemd 232)上,十个 cgroup(3:cpuset4:perf_event7:freezer)中只有三个是 root;其余的都在/init.scope 之下。也就是说,我认为在该文件中搜索 :/docker/ 可能是目前最可靠的启发式方法。 grep 'docker\|lxc' /proc/1/cgroup 在 Docker 18.09 上为我工作。 不适合我。使用 LXC 特权容器托管 Ubuntu 19.04,来宾 Ubuntu 18.04。 /proc/1/cgroup 不包含 lxc 字符串。【参考方案3】:

在新的 ubuntu 16.04 系统上,新的 systemd 和 lxc 2.0

sudo grep -qa container=lxc /proc/1/environ

【讨论】:

这适用于我在 Ubuntu 焦点 20.04 上。以上的答案都没有。 谢谢!它适用于lxc!你能解释一下为什么需要'-a'吗? grep -q container=lxc /proc/1/environ还不够吗? /proc/$$/environ 用空字节分隔环境变量。如果没有-a,则适用手册页中的这段话: > 默认情况下,TYPE 是二进制,并且 grep 在发现空输入二进制数据后抑制输出【参考方案4】:

在 bash 脚本中检查 docker/lxc 的简洁方法是:

#!/bin/bash
if grep -sq 'docker\|lxc' /proc/1/cgroup; then
   echo I'm running on docker.
fi

【讨论】:

感谢@DanielGriscom,看起来好多了。 当我的容器在 kubernetes 中运行时,这不起作用。【参考方案5】:

检查是否在 Docker 中运行的便捷 Python 函数:

def in_docker():
    """ Returns: True if running in a Docker container, else False """
    with open('/proc/1/cgroup', 'rt') as ifh:
        return 'docker' in ifh.read()

【讨论】:

重要提示!当容器在 Kubernetes 中运行时,这似乎不起作用。相反,用 'kubepod' 代替 'docker' 替换最后一行。 (或者,放入一个“或”语句来检查两者;)) 我猜是kubepods【参考方案6】:

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

这有助于区分您是否在容器中。

【讨论】:

根据操作系统,“init”可能需要替换为“systemd”。有关 systemd 的更多信息here。 是的,但重点不是init进程的名字,而是进程号。 这似乎只适用于 Docker。在 LXC 容器中它返回 Systemd PID 1 它现在也在 docker 中返回 1。它通常是sh 而不是init,但它几乎可以是任何一个。 在docker下,已经不是这样了——bash-5.0# cat /proc/1/sched bash (1, #threads: 1)【参考方案7】:

最简单的方法是检查环境。如果你有 container=lxc 变量,你就在一个容器中。

否则,如果你是root,你可以尝试执行mknodmount操作,如果失败,你很可能在一个能力下降的容器中。

【讨论】:

这个不仅适用于 docker(我没有检查),更重要的是适用于 lxd/lxc 容器(检查),其中/proc/1/cgroup 不允许您检测到。跨度> 你能用代码而不是伪代码来编辑答案吗? "container=lxc" ? 不合适。你的意思是像 if [[ "lxc" = "$container" ]] 吗? 我的意思是......这很奇怪,通常环境变量都是大写的,所以在这里寻找一些精确度 docker run alpine env 没有给出任何看起来像那个变量的东西【参考方案8】:

我的回答仅适用于 Node.js 进程,但可能与一些偶然发现此问题并寻找 Node.js 特定答案的访问者相关。

我遇到了同样的问题,我依赖 /proc/self/cgroup 创建了一个 npm 包 仅用于此目的 - 检测 Node.js 进程是否在 Docker 容器内运行。

containerized npm module 将在 Node.js 中帮助您。它目前尚未在 Io.js 中进行测试,但也可以在那里工作。

【讨论】:

感谢这个模块,似乎有几个待解决的开放修复 - 你还在维护这个吗?【参考方案9】:

这是一个老问题,但非常好。 :)

我编写了一些我们在裸机、VM 和 docker 容器上运行的自动化脚本,其逻辑分支基于脚本在哪个平台上执行。在我的情况下,我有权同时创建容器和 docker 映像,所以这个解决方案只有在你控制整个堆栈的情况下才有效:

Dockerfile 片段:

FROM ubuntu:18.04

ENV PLATFORM="docker"

RUN apt update; \
...

然后,该脚本可以检查 $PLATFORM 的值以获得每个平台上的预期结果:

#!/bin/bash

# Check for executor specification in environment
case $PLATFORM in
  docker)
    # If running in Docker, do this stuff
    echo "Running containerized, proceeding..."
    ;;
  virtual)
    # If running in a VM, do different stuff
    echo "Running on a VM, loading VM stuff..."
    modprobe some-kernel-module
    ;;
  *)
    echo "Unknown executor specified! Exiting..."
    exit 1
    ;;
esac

为了简洁,我在上面的代码中省略了baremetal。

【讨论】:

【参考方案10】:

我已将 JJC 的答案翻译成 ruby​​

def in_docker
  File.open('/proc/1/cgroup', 'rt') do |f|
    contents = f.read
    return contents =~ /docker/i || contents =~ /kubepod/i
  end
rescue StandardError => e
  p 'Local development'
  p e
  false
end

【讨论】:

【参考方案11】:

在 Python 中检查上述所有解决方案:

import os

def in_container():
    proc_1 = r'/proc/1/sched'

    if os.path.exists(proc_1):
        with open(proc_1, 'r') as fp:
            out = fp.read()
    else:
        out = ''

    checks = [
        'docker' in out,
        '/lxc/' in out,
        out.split(' ')[0] not in ('systemd', 'init',),
        os.path.exists('./dockerenv'),
        os.path.exists('/.dockerinit'),
        os.getenv('container') is not None
    ]
    return any(checks)


if __name__ == '__main__':
    print(in_container())

概念证明:

$ docker run --rm -it --mount type=bind,source=$PWD/incontainer.py,target=/tmp/script.py python:3 python /tmp/script.py
True

【讨论】:

这在基于 Mac 的 docker 容器上对我不起作用。返回空。 Docker 版本 2.1.0.1 (37199)。 这个做了:def is_non_docker(): return os.path.exists('/proc/1/cgroup') 按照此处接受的答案***.com/questions/20010199/… 你获得了无用的猫奖。以及子流程一的无用使用。 是的,这是一个全新级别的不必要cat!不错的一个:-D 你是对的,我会更新它的答案,即使它仍然不是包罗万象。 @JanHudec【参考方案12】:

本SO问答:"Find out if the OS is running in a virtual environment";虽然与 OP 的问题不同,但它确实回答了查找您所在容器的常见情况(如果有的话)。

特别是,安装并阅读这个似乎运行良好的 bash 脚本的代码:

virt-what

sudo apt install virt-what

【讨论】:

不适用于 Ubuntu 16.04 上的 virt-what 版本 1.14-1。需要补丁。 有趣的是,在 windows 上的 docker 内部,virt-what 报告 hyperv,就像我的 WSL2 bash shell 一样。 这在 Ubuntu 20.04 中确实有效,它在 /proc/1/environ 中搜索“lxc”【参考方案13】:

golang代码获取pid container_id,可以获取map container_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

【讨论】:

【参考方案14】:

Docker 每天都在发展,所以我们不能确定他们将来是否会保留.dockerenv .dockerinit

在大多数 Linux 风格中,init 是第一个启动的进程。但在容器的情况下,情况并非如此。

#!/bin/bash
if ps -p1|grep -q init;then  
  echo "non-docker" 
else 
  echo "docker" 
fi

【讨论】:

@RomanTrofimov LXC/Docker 也没有。多么有趣的评论。 它在 centos 7 中也不起作用。当我在我的主机上运行时,它会显示 docker。看起来 systemd 正在以进程 id 1 运行 @VenkateswaraRao - 这必须在容器内运行。目的是确定您是否在 docker 容器内。 @GovindKailas:问题在于,这假设正常的 PID 是init,这在基于systemdlaunchd 的系统上是不正确的...... @SamThomas:launchd、upstart、Solaris SMF、systemd、Sys V 风格的初始化、BSD 风格的初始化(尽管这两个和其他一些可能称他们的 PID 1 init)、OpenRC、initng、runit . See here。大多数现代基于 Linux 的系统将使用 systemd,一些较旧的系统,upstart....所有现代 OS X 系统都将使用 launchd【参考方案15】:

这是 Ruby 中的一个解决方案,

# Usage: DockerHelper.running_in_docker?
module DockerHelper
  extend self

  def running_in_docker?
    !!(File.read("/proc/1/cgroup") =~ %r[^\d+:\w+:/docker/]) # !! => true/false
  rescue Errno::ENOENT
    false
  end
end

如果你喜欢用你的代码进行测试,here's a spec in the gist。

【讨论】:

【参考方案16】:

在 docker 容器中,条目 /proc/self/cgroup 被挂载到主机上的 cgroups。

例如在容器中

# awk -F: '/cpuset/' /proc/self/cgroup
3:cpuset:/docker/22bd0c154fb4e0d1b6c748faf1f1a12116acc21ce287618a115ad2bea41256b3

而在主机上也是如此

$ awk -F: '/cpuset/' /proc/self/cgroup
3:cpuset:/

在 shell 中使用某些东西进行低调测试

is_running_in_container() 
  awk -F: '/cpuset/ && $3 ~ /^\/$/ c=1  END  exit c ' /proc/self/cgroup


if is_running_in_container; then
  echo "Aye!! I'm in a container"
else 
  echo "Nay!! I'm not in a container"
fi

【讨论】:

两者都返回 1。【参考方案17】:

也许这可以解决问题:

if [ -z $(docker ps -q) ]; then
    echo "There is not process currently running"
else
    echo "There are processes running"
fi

这就是你想要的吗?希望对你有帮助=)

【讨论】:

显然没有docker 二进制文件可从容器内部获得。 嗯,这在控制容器具有docker 并访问主机的 docker 套接字的情况下(例如 gitlab docker-in-docker)会失败。 是的,你是对的,当然没有^^。我在阅读该问题时得到了错误的解释。谢谢你,Shalomb。

以上是关于如何确定进程是不是在 lxc/Docker 内部运行?的主要内容,如果未能解决你的问题,请参考以下文章

LXC---Docker的“前身”

Docker

是否可以将 USB 设备暴露给 LXC/Docker 容器?

如何确定内部类是不是实现了接口?

7 Cgroup

内联是不是确定内部链接?