Как проверить, запущен ли процесс внутри контейнера докеров

89

[Обновлено1] У меня есть оболочка, которая изменяет параметры ядра TCP в некоторых функциях, но теперь мне нужно запустить эту оболочку в контейнере Docker, это означает, что оболочке необходимо знать, что она работает внутри контейнера, и прекратить настройку ядра.

Я не уверен, как этого добиться, вот содержимое /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

Гарриз
источник
Не очень четкий вопрос. Зачем тебе это нужно?
Хенк Лангевельд
2
Дубликат stackoverflow.com/questions/20010199/…
Рыбка Йоханнеса Зиемке
@fish no / lxc / <containerid> в моем случае, см. обновление
harryz
1
Параметры ядра @HenkLangeveld доступны только для чтения в контейнере Docker, поэтому мне нужно знать, работает ли моя оболочка внутри контейнеров, и отключить функции ядра в моей оболочке. см. обновление.
harryz
Некоторые шаги в скрипте пытаются изменить параметры ядра, и их нужно пропустить при запуске в Docker. Очистить.
Хенк Лангевельд

Ответы:

70

Чтобы проверить внутри контейнера Docker, находитесь ли вы внутри контейнера 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:/
Томас Уриг
источник
Ответ @Founder чище
Скотт
5
не совсем верно, что «За пределами контейнера докеров все записи в / 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
Это в основном работает только в Linux, но не в Darwin или других BSD, которые даже не используют procfs.
Кристиан
@Christian Docker / LXC - это только Linux, так что все в порядке, правда :)?
Роберт Лакруа
@RobertLacroix, значит, вы говорите, что если вы не найдете procfs, вы не в Docker? Что ж, это достаточно справедливо, я думаю ...
Кристиан,
112

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
at0S
источник
1
Если, конечно, вы или кто-то другой не создали /.dockerinitна вашем хосте (возможно, случайно), в этом случае это будет неправильно вне контейнера.
sosiouxme
18
Если кто-то еще сделал это в /, то они root, и у вас есть проблемы похуже, чем знать, находитесь ли вы в докере или нет.
Дэви
15
Остерегайтесь полагаться на /.dockerenvдолгосрочную перспективу. Он не предназначен для использования таким образом .
ReactiveRaven
fwiw, Подман не создает /.dockerenv. Он действительно создает, /run/.containerenvно по аналогичной логике звучит как детали реализации, на которые нельзя полагаться. См. Github.com/containers/libpod/issues/3586 для некоторых альтернатив, специфичных для podman.
Бени Чернявский-Паскин
24

Мы используем расписание процесса (/ 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
Основатель
источник
это на самом деле довольно ценная информация. спасибо
Fabian Lange
4
В зависимости от ОС, возможно, потребуется заменить «init» на «systemd». Больше информации о systemd здесь .
BrianV
2
Как упоминал @BrianV, для меня это тоже не работает.
Шубхам Чаудхари
5
В контейнере Docker, запущенном в кластере k8s, 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.
jpkotta
21

Решение Томаса в виде кода:

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

Запись

Параметр readс фиктивной переменной - это простая идиома для вывода? . Это компактный метод превращения, возможно, подробного текста grepили awkв тест шаблона.

Дополнительное примечание к прочтению

Хенк Лангевельд
источник
10
За исключением ... это не удастся в некоторых средах, потому что, например, 3:cpu,cpuacct:/system.slice/docker-1ce79a0dec4a2084d54acf187a1e177e0339dc90d0218b48b4456576ecaf291e.scopeне будет соответствовать. Проще grep -q docker /proc/1/cgroup; код результата от этого также должен быть достаточным.
larsks 08
2
readможет работать bash, но в наиболее часто используемой dashоболочке вы должны использовать либо read dummy(или аналогичный), либо конструкцию наподобие[ -n "$(command)" ]
Дэниел Алдер
@DanielAlder Хороший улов, Дэниел. Буду обновлять текст.
Хенк Лангевельд,
1
Ранее утверждалось, что любая оболочка, совместимая с Bourne, поддерживает простой readбез имени переменной. Это верно только для bash и ksh93. Opengroup только указывает read varи не упоминает readповедение без хотя бы одной переменной. В баше и ksh93 , если не вар не задан, чтение использует переменные оболочки REPLY.
Хенк Лангевельд,
1
Почему мы не можем просто использовать awk -F: '$3 ~ /docker/' /proc/self/cgroup | read? Работает на меня.
Шубхам Чаудхари
7

Для меня работает проверка номера 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
bo0k
такой же как ls -di /? кажется inode num ненадежным на другой платформе
yurenchen
это единственное, что помогло мне отличить хост Xen domU от его контейнера докеров
помогло
1

Нам нужно было исключить процессы, запущенные в контейнерах, но вместо проверки только контрольных групп докеров мы решили сравнить /proc/<pid>/ns/pidс системой инициализации в /proc/1/ns/pid. Пример:

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 работает в другом пространстве имен.

Грег Брей
источник
У меня не работает. Изнутри контейнера Docker, запланированного k8s, readlink /proc/self/ns/pidи readlink /proc/1/ns/pidпроизвести тот же вывод.
Стефан Маевски
1
@StefanMajewsky Можно попробовать использовать github.com/jessfraz/amiconolated, чтобы узнать, какие функции включены в среде выполнения контейнера.
Грег Брэй,
0

На основе комментария Дэна Уолша об использовании 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?

Бени Чернявский-Паскин
источник
0

код голанга

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
}
wcc526
источник
-1

Я создал небольшой скрипт на Python. Надеюсь, кто-то сочтет это полезным. :-)

#!/usr/bin/env python3
#@author Jorge III Altamirano Astorga 2018
import re
import math

total = None
meminfo = open('/proc/meminfo', 'r')
for line in meminfo:
    line = line.strip()
    if "MemTotal:" in line:
        line = re.sub("[^0-9]*", "", line)
        total = int(line)
meminfo.close()
print("Total memory: %d kB"%total)

procinfo = open('/proc/self/cgroup', 'r')
for line in procinfo: 
    line = line.strip()
    if re.match('.{1,5}:name=systemd:', line):
        dockerd = "/sys/fs/cgroup/memory" + \
            re.sub("^.{1,5}:name=systemd:", "", line) + \
            "/memory.stat"
        #print(dockerd)
        memstat = open(dockerd, 'r')
        for memline in memstat:
            memline = memline.strip()
            if re.match("hierarchical_memory_limit", memline):
                memline = re.sub("[^0-9]*", \
                    "", memline)  
                total = math.floor(int(memline) / 2**10)
        memstat.close()
procinfo.close()
print("Total available memory to the container: %d kB"%total)
Хорхе Альтамирано
источник
это круто, но как это помогает определить, в контейнере вы или нет?
user528025 01
FileNotFoundError: [Errno 2] No such file or directory: '/sys/fs/cgroup/memory/docker/<docker_id>/memory.stat'
Скрудж МакДак