• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Digging Into Runtimes – runc

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
Все знают о Docker, но мало кто знает об используемых им технологиях. В этом посте мы проанализируем одну из самых фундаментальных и мощных технологий, скрытых за Docker — RUNC
1653324258065.png

Краткая история контейнеров: от бардака к стандартам и правильной архитектуре​


Контейнерные технологии появились впервые после изобретения контрольных групп и пространств имен . Два из первых известных проектов пытаются объединить их для достижения изолированных технологических сред. LXC и LMCTFY . Последний, в типичном Google пытались обеспечить стабильную абстракцию от Linux внутренности через интуитивно понятный API. В 2013 году была разработана технология Docker , построенная на основе LXC . Команда Docker представила понятие контейнера, «упаковки» (называемой изображениями) и «переносимость» этих изображений между разными машинами. Другими словами, Докер пытался создать независимый программный компонент, следующий философии Unix (минимализм, модульность, интероперабельность). В 2014 году создали программу под названием libcontainer. Его целью было создание процессов в изолированных средах и управление их жизненным циклом. Также в 2014 г. Kubernetes был анонсирован на ДокерКон и именно тогда в мире контейнеров начало происходить множество вещей. Тогда OCI (Open Container Initiative) была создана в ответ на потребность в стандартизации и структурированном управлении. OCI закончился двумя спецификациями - Спецификация времени выполнения ( runtime-spec ) и спецификацию образа ( image-spec ). Первый определил подробный API, которому должны следовать разработчики исполнительных программ. Проект libcontainer был передан в OCI, и была создана первая стандартизированная среда выполнения, следующая runtime-spec, - runc. Она представляет собой полностью совместимый API поверх libcontainer, позволяющий пользователям напрямую порождать контейнеры и управлять ими.
Сегодня среды выполнения контейнеров часто делятся на две категории: низкоуровневые (runc, gVisor, Firecracker) и высокоуровневые (containerd, Docker, CRI-O, podman).

Runc — это стандартизированная среда выполнения для создания и запуска контейнеров на Linux согласно OCI спецификации. Разница заключается в объеме спецификации OCI и дополнительных возможностях.

Существуют и другие более высокоуровневые среды выполнения, такие как Docker и Containerd , которые реализуют эту спецификацию поверх runc . Таким образом, они устраняют несколько недостатков, связанных с использованием только runc , а именно: целостность образа, журнал истории пакетов и эффективное использование корневой файловой системы контейнера. Будьте готовы, мы мы собираемся изучить эти более оцененные среды выполнения!

1653325258147.png


1653325271039.png


runc — среда выполнения Open Container Initiative runc — это клиент командной строки для запуска приложений, упакованных в соответствии с Open Container Initiative (OCI) и является совместимой реализацией спецификации Open Container Initiative Runc.

Как упоминалось выше, runc — это OCI среда выполнения, совместимая программный компонент, отвечающий за создание, настройку и управление изолированными процессами Linux, также называемыми контейнерами. Формально runc — это клиентская оболочка вокруг libcontainer . Поскольку runc следует OCI для сред выполнения контейнеров, он требует двух частей информации:
  • Конфигурация OCI — файл в формате JSON, содержащий информацию о процессах контейнера, такую как пространства имен, возможности, переменные среды и т. д.
  • Каталог корневой файловой системы — каталог корневой файловой системы, который будет использоваться процессом-контейнером (chroot).

Давайте теперь посмотрим, как мы можем использовать runc и вышеупомянутые Компоненты спецификации.


Создание конфигурации OCI​

Runc поставляется с функцией создания OCI конфигурация:


Код:
cryptonite@host:~$ runc spec && cat config.json
{
    "ociVersion": "1.0.2-dev",
    "process": {
        "terminal": true,
        "user": {
            "uid": 0,
            "gid": 0
        },
        "args": [
            "sh"
        ],
        "env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "TERM=xterm"
        ],
        "cwd": "/",
        "capabilities": {
            "bounding": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            ],
            "effective": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            ],
            "inheritable": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            ],
            "permitted": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            ],
            "ambient": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            ]
        },
        "rlimits": [
            {
                "type": "RLIMIT_NOFILE",
                "hard": 1024,
                "soft": 1024
            }
        ],
        "noNewPrivileges": true
    },
    "root": {
        "path": "rootfs",
        "readonly": true
    },
    "hostname": "runc",
    "mounts": [
        {
            "destination": "/proc",
            "type": "proc",
            "source": "proc"
        },
        {
            "destination": "/dev",
            "type": "tmpfs",
            "source": "tmpfs",
            "options": [
                "nosuid",
                "strictatime",
                "mode=755",
                "size=65536k"
            ]
        },
        {
            "destination": "/sys/fs/cgroup",
            "type": "cgroup",
            "source": "cgroup",
            "options": [
                "nosuid",
                "noexec",
                "nodev",
                "relatime",
                "ro"
            ]
        }
    ],
    "linux": {
        "resources": {
            "devices": [
                {
                    "allow": false,
                    "access": "rwm"
                }
            ]
        },
        "namespaces": [
            {
                "type": "pid"
            },
            {
                "type": "network"
            },
            {
                "type": "ipc"
            },
            {
                "type": "uts"
            },
            {
                "type": "mount"
            }
        ],
        "maskedPaths": [
            "/proc/acpi",
            "/proc/asound",
            "/proc/kcore",
            "/proc/keys",
            "/proc/latency_stats",
            "/proc/timer_list",
            "/proc/timer_stats",
            "/proc/sched_debug",
            "/sys/firmware",
            "/proc/scsi"
        ],
        "readonlyPaths": [
            "/proc/bus",
            "/proc/fs",
            "/proc/irq",
            "/proc/sys",
            "/proc/sysrq-trigger"
        ]
    }
}

Из приведенного выше фрагмента кода видно, что config.json файл содержит информацию о:



Всей вышеописанной конфигурации почти полностью достаточно для libcontainer для создания изолированного Linux (контейнера). Там для создания контейнера не хватает одной вещи - корневого каталога процесса. Давайте загрузим его и сделаем доступным для runc .

Код:
# download an alpine fs
cryptonite@host:~$  wget http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.1-x86_64.tar.gz
...
cryptonite@host:~$ mkdir rootfs && tar -xzf \
    alpine-minirootfs-3.10.1-x86_64.tar.gz -C rootfs
cryptonite@host:~$ ls rootfs
bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
Мы загрузили корневую файловую систему Alpine Linux — недостающая часть OCI спецификация времени выполнения . Теперь мы можем создать контейнерный процесс, выполняющий sh shell.

Код:
cryptonite@host:~$ runc create --help
NAME:
   runc create - create a container
...
DESCRIPTION:
   The create command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "config.json" and a root
filesystem.

Хорошо, давайте соберем все вместе в этот пакет и создадим нового контейнерa! Мы будем различать два типа контейнеров: - root - контейнеры, работающие под UID=0. - без root - контейнеры, работающие под UID, отличным от 0.

Создание корневого контейнера​

Чтобы иметь возможность создать root-контейнер (процесс, работающий под UID=0), надо изменить владельца корневой файловой системы, которая была только что загружен (если пользователь еще не root).

Код:
cryptonite@host:~$ sudo su -
# move the config.json file and the rootfs
# to the bundle directory
root@host:~ # mkdir bundle && mv config.json ./bundle && mv rootfs ./bundle;
# change the ownership to root
root@host:~ #chown -R $(id -u) bundle

Почти все готово. Нам осталось позаботиться об одной последней детали: так как мы хотели бы отсоединить наш корневой контейнер от runc и его файловые дескрипторы для взаимодействия с ним, мы должны создать TTY сокет и подключаемся к нему с двух сторон (со стороны контейнера и со стороны нашего Терминал). мы собираемся использовать recvtty который является частью официального runc.

Код:
cryptonite@host:~$ go install github.com/opencontainers/runc/contrib/cmd/recvtty@latest
# create the tty socket and wait connection
# from the pseudo-terminal in the container
cryptonite@host:~$ recvtty tty.sock
Теперь мы переключаемся на другой терминал, чтобы создать контейнер через runc .
Код:
# in another terminal
cryptonite@host:~$ sudo runc create -b bundle --console-socket $(pwd)/tty.sock container-crypt0n1t3
cryptonite@host:~$ sudo runc list
ID                     PID         STATUS      BUNDLE     CREATED                          OWNER
container-crypt0n1t3   86087       created     ~/bundle   2022-03-15T15:46:41.562034388Z   root
cryptonite@host:~$ ps aux | grep 86087
root       86087  0.0  0.0 1086508 11640 pts/0   Ssl+ 16:46   0:00 runc init

Наш контейнер теперь создан, но он не запускает определенный sh, настроенные в JSON . Это потому, что runc init по-прежнему поддерживает процесс, который должен загрузить файл sh программа в пространстве имен (изолированной) среде. Давайте проверим, что есть происходит внутри runc init .

Код:
# inspect namespaces of runc init process
cryptonite@host:~$ sudo ls -al /proc/86087/ns
total 0
dr-x--x--x 2 root root 0 mars  15 16:57 .
dr-xr-xr-x 9 root root 0 mars  15 16:46 ..
lrwxrwxrwx 1 root root 0 mars  15 16:57 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 ipc -> 'ipc:[4026532681]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 mnt -> 'mnt:[4026532663]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 net -> 'net:[4026532685]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 pid -> 'pid:[4026532682]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 pid_for_children -> 'pid:[4026532682]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 mars  15 16:57 uts -> 'uts:[4026532676]'
# inspect current shell namespaces
cryptonite@host:~$ sudo ls -al /proc/$$/ns
total 0
dr-x--x--x 2 cryptonite cryptonite 0 mars  15 16:57 .
dr-xr-xr-x 9 cryptonite cryptonite 0 mars  15 16:16 ..
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 net -> 'net:[4026532008]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 time -> 'time:[4026531834]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  15 16:57 uts -> 'uts:[4026531838]

Мы можем подтвердить, что, за исключением пользователя и времени пространств имен init runc использует другой набор пространств имен, чем наша обычная оболочка. Но почему он не запустил наш процесс?

Время еще раз сказать, что runc — довольно низкоуровневая среда выполнения и не обрабатывает множество конфигураций по сравнению с другими более высокоуровневые среды выполнения. Например, он не настраивает сеть,. интерфейс для изолированного процесса. Для дальнейшей настройки процесс-контейнер, runc помещает его в защитное средство (runc-init) где можно добавить дополнительные конфигурации. Чтобы проиллюстрировать более практический способ, почему это сдерживание может быть полезным, мы собираемся настройть сетевое пространство имен для нашего детского контейнера.

Настройка runc-init с сетевым интерфейсом​

Код:
# enter the container network namespace
cryptonite@host:~$ sudo nsenter --target 86087 --net
root@qb:~ # ifconfig -a
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# no network interface ;(

Видим, что действительно интерфейсов в сетевом пространстве имён нет, runc init он от мира. Давайте исправим это!

Код:
# create a virtual pair and turn one of the sides on
cryptonite@host:~$ sudo ip link add veth0 type veth peer name ceth0
cryptonite@host:~$ sudo ip link set veth0 up
# assign an IP range
cryptonite@host:~$ sudo ip addr add 172.12.0.11/24 dev veth0
# and put it in the net namespace of runc init
cryptonite@host:~$ sudo ip link set ceth0 netns /proc/86087/ns/net

Теперь мы можем еще раз проверить, что происходит в сетевом пространстве имен.
Код:
cryptonite@host:~$ sudo nsenter --target 86087 --net
root@host:~ # ifconfig -a
ceth0: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether 8a:4f:1c:61:74:f4  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# and there it is !
# let's configure it to be functional
root@host:~ # ip link set lo up
root@host:~ # ip link set ceth0 up
root@host:~ # ip addr add 172.12.0.12/24 dev ceth0
root@host:~ # ping -c 1 172.12.0.11
PING 172.12.0.11 (172.12.0.11) 56(84) bytes of data.
64 bytes from 172.12.0.11: icmp_seq=1 ttl=64 time=0.180 ms
--- 172.12.0.11 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.180/0.180/0.180/0.000 ms

Теперь мы настроили виртуальный интерфейс и подключили сеть пространства имен процесса инициализации runc с пространством имен хост-сети. Эта конфигурация не является обязательной, и процесс контейнера выполняется оболочка sh может быть запущена без подключения к хосту. Однако эта конфигурация может быть полезна для других процессов доставки сетевые функциональные возможности. Программа sh по-прежнему не работает внутри процесс по желанию. После завершения настройки можно, наконец, создать контейнер, в котором запущена предопределенная программа.

Запуск root-контейнера​

Код:
cryptonite@host:~$ sudo runc start container-crypt0n1t3
cryptonite@host:~$ ps aux | grep 86087
root       86087  0.0  0.0   1632   892 pts/0    Ss+  16:46   0:00 /bin/sh
cryptonite@host:~$ ps aux | grep 86087
sudo runc list
ID                     PID         STATUS      BUNDLE     CREATED                          OWNER
container-crypt0n1t3   86087       running     ~/bundle   2022-03-15T15:46:41.562034388Z   root

Контейнер, наконец, работает под тем же PID, но изменил свой исполняемый файл /bin/sh . Между тем, в терминале, содержащем recvtty shell - ура! Мы можем взаимодействовать с нашим недавно созданным контейнером.


Код:
cryptonite@host:~$ recvtty tty.sock
# the shell drops here
/ # ls
ls
bin    etc    lib    mnt    proc   run    srv    tmp    var
dev    home   media  opt    root   sbin   sys    usr
/ # ifconfig -a
ifconfig -a
ceth0     Link encap:Ethernet  HWaddr 8A:4F:1C:61:74:F4
          inet addr:172.12.0.12  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::884f:1cff:fe61:74f4/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:43 errors:0 dropped:0 overruns:0 frame:0
          TX packets:16 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:6259 (6.1 KiB)  TX bytes:1188 (1.1 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
/ # id
id
uid=0(root) gid=0(root)
/ # ps aux
ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
   19 root      0:00 ps aux

Для тех, кто знаком с другими контейнерными технологиями, этот терминал знакомый ;). Хорошо runc действительно крут, но что еще мы можем с ним сделать? Ну, почти все, что касается управления жизнью этого контейнер.

Доступное для записи хранилище внутри контейнера​

По умолчанию при создании контейнера runc монтирует корневую файловую систему, доступную только для чтения. Это проблематично, потому что наш процесс не может использовать файловую систему, в которой он «chrooted».

Код:
# in recvtty terminal
/ # touch hello
touch hello
touch: hello: Read-only file system
/ # mount
mount
/dev/mapper/vgubuntu-root on / type ext4 (ro,relatime,errors=remount-ro)
...
# ;((

Чтобы изменить это, можно изменить config.json .

Код:
root": {
        "path": "rootfs",
        "readonly": false
    },

Код:
cryptonite@host:~$ sudo runc create -b bundle --console-socket $(pwd)/tty.sock wcontainer-crypt0n1t3
cryptonite@host:~$ sudo runc start wcontainer-crypt0n1t3

# in the recvtty shell
/ # touch hello
touch hello
/ # ls
ls
bin     etc     home    media   opt     rootfs  sbin    sys     usr
dev     hello   lib     mnt     proc    run     srv     tmp     var

Это решение слишком общее. Отсюда лучше определить более конкретные правила для различных подкаталогов в корневом файле система.

Приостановка и возобновление контейнера​

Код:
# pause and inspect the process state
cryptonite@host:~$ sudo runc  pause container-crypt0n1t3
cryptonite@host:~$ sudo sudo runc list
ID                     PID         STATUS      BUNDLE     CREATED                         OWNER
container-crypt0n1t3   86087       paused      ~/bundle   2022-03-15T15:46:41.562034388Z   root
# investigate the system state  of the process
cryptonite@host:~$ ps aux | grep 86087
root       86087  0.0  0.0   1632   892 pts/0    Ds+  16:46   0:00 /bin/sh

Из приведенного выше фрагмента кода видно, что после приостановки контейнера системный процесс переводится в состояние Ds+ . Это состояние переводится как непрерывный сон (состояние для ввода/вывода). В этом состоянии процесс не получает почти никаких сигналов ОС и не может быть отлажен (след). Возобновим его и снова проверим его состояние.

Код:
cryptonite@host:~$ sudo sudo runc resume container-crypt0n1t3
cryptonite@host:~$ ps aux | grep 86087
root       86087  0.0  0.0   1632   892 pts/0    Ss+  16:46   0:00 /bin/sh
Видно, что после возобновления процесс изменил свое состояние на Ss+ ( прерываемый сон ). В этом состоянии он снова может получать сигналы и отлаживаться. Давайте глубже исследуем, как сон и "возвращение" выполняются на уровне ядра.

Код:
cryptonite@host:~$ strace sudo runc  pause container-crypt0n1t3
...
rt_sigaction(SIGRTMIN, {sa_handler=0x7f198de71bf0, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f198de7f3c0}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {sa_handler=0x7f198de71c90, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0x7f198de7f3c0}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
...
cryptonite@host:~ strace sudo runc resume container-crypt0n1t3
...
rt_sigaction(SIGRTMIN, {sa_handler=0x7fcb0f28cbf0, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7fcb0f29a3c0}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {sa_handler=0x7fcb0f28cc90, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0x7fcb0f29a3c0}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0

Из приведенных выше фрагментов кода видно, что пауза контейнера реализована с использованием сигналов Linux ( SIGRTMIN , SIGRT_1 ) и предопределенных обработчиков.

Проверить текущее состояние контейнера​

Runc также позволяет проверить состояние запущенного контейнера на лету. Он показывает лимит использования различных ресурсов ОС (ЦП использование на ядро, ОЗУ и ошибки страниц, ввод-вывод, подкачка, сетевая карта) в время исполнения контейнера:

Код:
# stopped and deleted old container - started with a new default one
cryptonite@host:~$ sudo runc events container-crypt0n1t3
{"type":"stats","id":"container-crypt0n1t3","data":{"cpu":{"usage":{
"total":22797516,"percpu":[1836602,9757585,1855859,494447,2339327,3934238,
139074,2440384],"percpu_kernel":[0,0,391015,0,0,0,0,0],
"percpu_user":[1836602,9757585,1464844,494447,2339327,3934238,139074,2440384],
"kernel":0,"user":0},"throttling":{}},"cpuset":{"cpus":[0,1,2,3,4,5,6,7],
"cpu_exclusive":0,"mems":[0],"mem_hardwall":0,"mem_exclusive":0,
"memory_migrate":0,"memory_spread_page":0,"memory_spread_slab":0,
"memory_pressure":0,"sched_load_balance":1,"sched_relax_domain_level":-1},
"memory":{"usage":{"limit":9223372036854771712,"usage":348160,"max":3080192,
"failcnt":0},"swap":{"limit":9223372036854771712,"usage":348160,"max":3080192,
"failcnt":0},"kernel":{"limit":9223372036854771712,"usage":208896,"max":512000,
"failcnt":0},"kernelTCP":{"limit":9223372036854771712,"failcnt":0},"raw":
{"active_anon":4096,"active_file":0,"cache":0,"dirty":0,
"hierarchical_memory_limit":9223372036854771712,"hierarchical_memsw_limit":9223372036854771712,
"inactive_anon":135168,"inactive_file":0,"mapped_file":0,
"pgfault":1063,"pgmajfault":0,"pgpgin":657,"pgpgout":623,"rss":139264,
"rss_huge":0,"shmem":0,"swap":0,
"total_active_anon":4096,"total_active_file":0,"total_cache":0,
"total_dirty":0,"total_inactive_anon":135168,"total_inactive_file":0,
"total_mapped_file":0,"total_pgfault":1063,"total_pgmajfault":0,"total_pgpgin":657,
"total_pgpgout":623,"total_rss":139264,"total_rss_huge":0,"total_shmem":0,"total_swap":0,
"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0}},
"pids":{"current":1},"blkio":{},"hugetlb":{"1GB":{"failcnt":0},"2MB":{"failcnt":0}},
"intel_rdt":{},"network_interfaces":null}}

Некоторые из приведенных выше ограничений представляют собой жесткие ограничения , установленные cgroups в Linux

Код:
cryptonite@host:~$ cat /sys/fs/cgroup/memory/user.slice/user-1000.slice/user@1000.service/container-crypt0n1t3/memory.limit_in_bytes
9223372036854771712
# same memory limit
cryptonite@host:~$ cat /sys/fs/cgroup/cpuset/container-crypt0n1t3/cpuset.cpus
0-7
# same number of cpus used

Эта функция дает действительно точное представление о том, что происходит с процессом с точки зрения потребляемых ресурсов в режиме реального времени. Этот журнал может быть действительно полезно для судебно-криминалистической экспертизы. По умолчанию runc выводит глобальную информацию о каждом контейнере при создание в файле /var/run/runc/state.json .

Контрольно-пропускной пункт контейнера​

Контрольные точки — еще одна интересная особенность runc . Это позволяет вам сделать снимок текущего состояния контейнера (в оперативной памяти) и сохранить его в виде набора файлов. Это состояние включает дескрипторы открытых файлов, содержимое памяти (страницы в RAM) регистры, точки монтирования и т.д. Позднее процесс можно возобновить из этого сохраненного состояния. Это может быть очень полезно, когда один хочет переместить контейнер с одного хоста на другой, не теряя его внутреннее состояние (живая миграция). Эта функция также может быть полезна для возврата процесса в стабильное состояние (отладка). Runc делает контрольные точки с помощью criu. Однако последний не поставляется из коробки с runc и должен быть установлен отдельно и добавлен в /usr/local/sbin для корректной работы. Проиллюстрировать контрольную точку, можно остановив процесс принтера и возобновить его после. Файл config.json будет содержать следующее:

Код:
"args": [
    "/bin/sh", "-c", "i=0; while true; do echo $i;i=$(expr $i + 1); sleep 1; done"
        ],

Здесь мы продемонстрируем еще один способ запуска контейнера, пропуская этап настройки (runc init). Давайте запустим его.

Код:
cryptonite@host:~$ sudo runc run -b bundle -d --console-socket $(pwd)/tty.sock container-printer

# in the recvtty shell
recvtty tty.sock
0
1
2
3
4

Код:
cryptonite@host:~$ sudo runc checkpoint --image-path $(pwd)/image-checkpoint \
 container-printer
 # inspect what was produced by criu
cryptonite@host:~$ ls image-checkpoint
cgroup.img        fs-1.img            pagemap-176.img          tmpfs-dev-73.tar.gz.img
core-176.img      ids-176.img         pagemap-1.img            tmpfs-dev-74.tar.gz.img
core-1.img        ids-1.img           pages-1.img              tmpfs-dev-75.tar.gz.img
descriptors.json  inventory.img       pages-2.img              tmpfs-dev-76.tar.gz.img
fdinfo-2.img      ipcns-var-10.img    pstree.img               tmpfs-dev-86.tar.gz.img
fdinfo-3.img      mm-176.img          seccomp.img              tty-info.img
files.img         mm-1.img            tmpfs-dev-69.tar.gz.img  utsns-11.img
fs-176.img        mountpoints-12.img  tmpfs-dev-71.tar.gz.img

Из приведенного выше фрагмента видно, что контрольная точка представляет собой набор файлов формата img (файл изображения CRIU v1.1) . Содержимое из этих файлов плохо читаются и представляют собой смесь бинарных и тестовых содержание. Давайте теперь возобновим наш процесс принтера, который во время остановка была на 84.

Код:
cryptonite@host:~$ sudo runc restore --detach --image-path $( pwd ) /image-checkpoint \
 -b bundle --console-socket $( pwd ) /tty.sock container-printer-restore

Вернемся к recvtty .

Код:
recvtty tty.sock
85
86
87
88
89
90
91
92

Процесс возобновился в прежнем состоянии, как будто ничего не произошло. Мы создали точно такую же копию контейнера (файловые дескрипторы, процессы и т. д.), но в предыдущем сохраненном состоянии.
  • Примечание : есть интересная опция –leave-running которая не останавливает процесс. Кроме того, после остановки контейнера его нельзя запустить снова.

Выполнение нового процесса в существующем контейнере​

Runc предлагает возможность запуска нового процесса внутри контейнер. Это означает создание нового процесса и применение тот же набор механизмов изоляции, что и у другого процесса, поэтому они в том же «контейнере».

Код:
cryptonite@host:~$ sudo runc exec container-crypt0n1t3-restore  sleep 120

# in a new terminal
# by default runc allocates a pseudo tty and connects it with the exec terminal
cryptonite@host:~$ sudo runc list
ID                             PID         STATUS      BUNDLE     CREATED                          OWNER
container-crypt0n1t3           0           stopped     ~/bundle   2022-03-16T08:44:37.440444742Z   root
container-crypt0n1t3-restore   13712       running     ~/bundle   2022-03-16T10:24:03.925419212Z   root

cryptonite@host:~$ sudo runc ps container-crypt0n1t3-restore
UID          PID    PPID  C STIME TTY          TIME CMD
root       13712    2004  0 11:24 pts/0    00:00:00 /bin/sh
root       14405   14393  0 11:36 ?        00:00:00 sleep 120
# the sleep process is part of the crypt0n1t3-container

Давайте посмотрим, что происходит с точки зрения контейнера.

Код:
 # ps aux
ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
   47 root      0:00 sleep 120
   53 root      0:00 ps aux
# the shell process sees the sleep process

# check the new process namespaces
/ # ls /proc/47/ns -al
ls /proc/47/ns -al
total 0
dr-x--x--x    2 root     root             0 Mar 16 10:36 .
dr-xr-xr-x    9 root     root             0 Mar 16 10:36 ..
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 cgroup -> cgroup:[4026531835]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 ipc -> ipc:[4026532667]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 mnt -> mnt:[4026532665]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 net -> net:[4026532670]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 pid -> pid:[4026532668]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 pid_for_children -> pid:[4026532668]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 time -> time:[4026531834]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 time_for_children -> time:[4026531834]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 user -> user:[4026531837]
lrwxrwxrwx    1 root     root             0 Mar 16 10:36 uts -> uts:[4026532666]
# check own namespaces
/ # ls /proc/self/ns -al
ls /proc/self/ns -al
total 0
dr-x--x--x    2 root     root             0 Mar 16 10:35 .
dr-xr-xr-x    9 root     root             0 Mar 16 10:35 ..
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 cgroup -> cgroup:[4026531835]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 ipc -> ipc:[4026532667]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 mnt -> mnt:[4026532665]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 net -> net:[4026532670]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 pid -> pid:[4026532668]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 pid_for_children -> pid:[4026532668]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 time -> time:[4026531834]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 time_for_children -> time:[4026531834]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 user -> user:[4026531837]
lrwxrwxrwx    1 root     root             0 Mar 16 10:35 uts -> uts:[4026532666]

Новый процесс ожидания контейнера действительно унаследовал то же самое пространства имен как процесс оболочки уже внутри контейнера.

Hooks

Runc позволяет вам выполнять команды в порядке относительно жизненный цикл контейнера. Эта функция была разработана для облегчения настройки и очистки контейнерной среды. Существует несколько типов Hooks которые мы обсудим отдельно.


CreateRuntime​

вызываются Hook CreateRuntime после среды контейнера( пространства имен, группы контроля, возможности ). Однако процесс, выполняющий хук, не заключен в эту среду , поэтому он имеет доступ ко всем ресурсам в текущем контексте. Процесс не chroot , и его текущий рабочий каталог является пакетом каталог. Путь к исполняемому файлу также определяется во время выполнения. Полезны Хуки CreateRuntime для начальной конфигурации контейнера (например: настроить сетевое пространство имен).


CreateContainer

Хуки CreateContainer вызываются после CreateRuntime . Эти хуки выполняются в пространстве имен контейнера (после nsenter), но путь к исполняемому файлу разрешается в пространстве имен среды выполнения. Процесс вошел в набор пространств имен, но еще не «chroot». Однако, его текущим рабочим каталогом является каталог контейнера rootfs. Эта функциональность runc может быть полезна для сообщения пользователю состояние конфигурации среды.

StartContainer

вызываются Хуки StartContainer до указанного пользователем программа выполняется как часть операции запуска. Это может быть использовано для добавления дополнительных функций относительно контекста выполнения (например: загрузить дополнительную библиотеку).
Исполняемый путь разрешается и выполняется в контейнере пространства имен.

PostStart

Хук Poststart вызывается после выполнения указанного пользователем процесса, но до возврата операции запуска. Например, этот хук может уведомить пользователя о порождении процесса контейнера. Путь исполняемого файла разрешается, и он выполняется в пространстве имен среды выполнения.

PostStop

PostStop вызываются после удаления контейнера (или выхода процесса), но до возврата операции удаления. Примерами таких крючков являются функции очистки или отладки. Путь к исполняемому файлу разрешается, и он выполняется в пространстве имен среды выполнения.

Синтаксис для определения хуков в config.json следующий:

Код:
"hooks":{
        "createRuntime": [
            {

                "path": "/bin/bash",
                "args": ["/bin/bash", "-c", "../scripts-hooks/runtimeCreate.sh"]
            }
        ],
        "createContainer": [
            {

                "path": "/bin/bash",
                "args": ["/bin/bash", "-c", "./home/tmpfssc/containerCreate.sh"]

            }
        ],
        "poststart": [
               {

                "path": "/bin/bash",
                "args": ["/bin/bash", "-c", "../scripts-hooks/postStart.sh"]

               }
        ],
        "startContainer": [
                {

                "path": "/bin/sh",
                "args": ["/bin/sh", "-c", "/home/tmpfssc/startContainer.sh"]

                }
        ],
        "poststop": [
               {

                "path": "/bin/bash",
                "args": ["/bin/bash", "-c",  "./scripts-hooks/postStop.sh"]

               }
        ]
    },

Был разработан небольшой POC для целей понимания этой функции. .Вот основные его элементы:
  • runtimeCreate.sh — инициализирует сетевое пространство имен;
  • containerCreate.sh — тестирует приведенную выше конфигурацию;
  • postStop.sh — запускает http -сервер на хосте после завершения инициализации;
  • postStop.sh — очищает сетевое пространство имен и останавливает http сервер.
Каждый из этих сценариев выводит информацию о своей среде.
Информацию об определенных хуках работающего контейнера можно найти в /run/runc/state.json (настраиваемый путь с --root ).

Обновление лимита ресурсов контейнера​

Среда выполнения также позволяет на лету изменять ограничения ресурсов в cgroups. Это может быть очень полезно для масштабирования и улучшения производительности, но и для ухудшения производительности/отказа в обслуживании программ, совместно использующих или зависящих иерархически от набора контрольных групп текущий процесс. По умолчанию runc создает root-подгруппу в /sys/fs/cgroup/user.slice/ . Для этого мы собираемся запустите в контейнере следующую программу:

Код:
"args": [
    "/bin/sh", "-c", "i=0; while true; do echo $i;i=$(expr $i + 1); sleep 1; done"
    ],

Код:
cryptonite@host:~ $runc update --help
...
 --blkio-weight value        Specifies per cgroup weight, range is from 10 to 1000 (default: 0)
   --cpu-period value          CPU CFS period to be used for hardcapping (in usecs). 0 to use system default
   --cpu-quota value           CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period
   --cpu-share value           CPU shares (relative weight vs. other containers)
   --cpu-rt-period value       CPU realtime period to be used for hardcapping (in usecs). 0 to use system default
   --cpu-rt-runtime value      CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period
   --cpuset-cpus value         CPU(s) to use
   --cpuset-mems value         Memory node(s) to use
   --memory value              Memory limit (in bytes)
   --memory-reservation value  Memory reservation or soft_limit (in bytes)
   --memory-swap value         Total memory usage (memory + swap); set '-1' to enable unlimited swap
   --pids-limit value          Maximum number of pids allowed in the container (default: 0)
...

Давайте запустим контейнер и обновим его аппаратные ограничения.

Код:
cryptonite@host:~ $sudo runc run -b bundle -d --console-socket $(pwd)/tty.sock container-spammer

# on the recvtty side
cryptonite@host:~ $recvtty tty.sock
0
1
2
3
Теперь мы введем новое ограничение по потреблению оперативной памяти. Этот предел относится к группе cgroup памяти



Код:
# after some adjustments to the process current memory usage
# define an upper bound to the RAM usage to 300kB
cryptonite@host:~ $sudo runc  update --memory 300000  container-spammer
Если мы посмотрим на наш виртуальный tty , процесс внезапно зависнет.
Код:
...
73
74
75
Код:
cryptonite@host:~ $sudo runc list
ID                  PID         STATUS      BUNDLE     CREATED                    OWNER
container-spammer   0           stopped     ~/bundle   2022-03-17T10:05:16.623692849Z   root
# stopped?
cryptonite@host:~ $sudo tail  /var/log/kern.log
...
Mar 17 11:06:32 qb kernel: [ 3772.833645] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=container-spammer,mems_allowed=0,oom_memcg=/user.slice/user-1000.slice/user@1000.service/container-spammer,task_memcg=/user.slice/user-1000.slice/user@1000.service/container-spammer,task=sh,pid=13681,uid=0
Mar 17 11:06:32 qb kernel: [ 3772.833684] Memory cgroup out of memory: Killed process 13681 (sh) total-vm:1624kB, anon-rss:0kB, file-rss:880kB, shmem-rss:0kB, UID:0 pgtables:36kB oom_score_adj:0

Обновив лимит памяти cgroup контейнера, мы фактически вернул немного памяти назад. Обновление контрольной группы ЦП также может увеличивать или снизить производительность контейнера. Cgroups — это швейцарский армейский нож. но требуется тщательная настройка.

Создание безрутевого контейнера​

До сих пор все манипуляции, которые мы демонстрировали, проводились на контейнере, процессы, работающие от имени root на хосте. Но что произойдет, если мы захотим усилить безопасность и запускать процессы контейнера с другим UID отличным от нуля? Отдельно хотелось бы, чтобы системные пользователи без привилегий root могли безопасно запускать контейнеры. В этой статье мы ссылаемся на термин «контейнер без корневого каталога» как на контейнер, который использует пространство имен пользователя. По умолчанию runc создает контейнер с UID пользователя, запускающего команду. Конфигурация OCI по умолчанию также генерируется без пространства имен пользователя, поэтому она относится к UID 0 на хосте.
Код:
.
cryptonite@host:~ $runc create -b bundle --console-socket $(pwd)/tty.sock rootless-crypt0n1t3
ERRO[0000] rootless container requires user namespaces

1653329899842.png

Runc поставляется со средствами для создания файлов конфигурации без рута.

Код:
cryptonite@host:~$ runc spec --rootless
# inspect what is different with the previous root config
cryptonite@host:~$ diff  config.json ./bundle/config.json
...
135,148c135,142
<       "uidMappings": [
<           {
<               "containerID": 0,
<               "hostID": 1000,
<               "size": 1
<           }
<       ],
<       "gidMappings": [
<           {
<               "containerID": 0,
<               "hostID": 1000,
<               "size": 1
<           }
<       ],
...

Мы видим, что runc добавил в файл два поля, указывающих, кому переназначить пользователя процесса внутри контейнера. По умолчанию он сопоставляет его с uid пользователя, выполняющего команду. Давайте теперь создадим новый контейнер с новой спецификацией среды выполнения.

Код:
# first change the ownership of the bundle files
cryptonite@host:~$ sudo chown -R $(id -u) bundle
# overwrite the old specification
cryptonite@host:~$ mv config.json bundle/config.json
cryptonite@host:~$ runc create -b bundle --console-socket $(pwd)/tty.sock rootless-crypt0n1t3
# no error - yay; let's run it
cryptonite@host:~$ runc start rootless-crypt0n1t3

Код:
/ # id
id
uid=0(root) gid=0(root) groups=65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),0(root)
/ # ls -al
ls -al
total 64
drwx------   19 root     root          4096 Jul 11  2019 .
drwx------   19 root     root          4096 Jul 11  2019 ..
drwxr-xr-x    2 root     root          4096 Jul 11  2019 bin
drwxr-xr-x    5 root     root           360 Mar 16 11:54 dev
drwxr-xr-x   15 root     root          4096 Jul 11  2019 etc
drwxr-xr-x    2 root     root          4096 Jul 11  2019 home
drwxr-xr-x    5 root     root          4096 Jul 11  2019 lib
drwxr-xr-x    5 root     root          4096 Jul 11  2019 media
drwxr-xr-x    2 root     root          4096 Jul 11  2019 mnt
drwxr-xr-x    2 root     root          4096 Jul 11  2019 opt
dr-xr-xr-x  389 nobody   nobody           0 Mar 16 11:54 proc
drwx------    2 root     root          4096 Mar 16 10:25 root
drwxr-xr-x    2 root     root          4096 Jul 11  2019 run
drwxr-xr-x    2 root     root          4096 Jul 11  2019 sbin
drwxr-xr-x    2 root     root          4096 Jul 11  2019 srv
dr-xr-xr-x   13 nobody   nobody           0 Mar 16 08:35 sys
drwxrwxr-x    2 root     root          4096 Jul 11  2019 tmp
drwxr-xr-x    7 root     root          4096 Jul 11  2019 usr
drwxr-xr-x   11 root     root          4096 Jul 11  2019 var

Давайте проверим пространства имен и идентификаторы пользователей из обоих "точки зрения".

Код:
# check within the container
/ # ls -al /proc/self/ns
ls -al /proc/self/ns
total 0
dr-x--x--x    2 root     root             0 Mar 16 11:59 .
dr-xr-xr-x    9 root     root             0 Mar 16 11:59 ..
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 cgroup -> cgroup:[4026531835]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 ipc -> ipc:[4026532672]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 mnt -> mnt:[4026532669]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 net -> net:[4026532008]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 pid -> pid:[4026532673]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 pid_for_children -> pid:[4026532673]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 time -> time:[4026531834]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 time_for_children -> time:[4026531834]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 user -> user:[4026532663]
lrwxrwxrwx    1 root     root             0 Mar 16 11:59 uts -> uts:[4026532671]

# check from the root user namespace
cryptonite@host:~$ ls /proc/$$/ns -al
total 0
dr-x--x--x 2 cryptonite cryptonite 0 mars  16 13:02 .
dr-xr-xr-x 9 cryptonite cryptonite 0 mars  16 11:36 ..
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 net -> 'net:[4026532008]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 time -> 'time:[4026531834]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 user -> 'user:[4026531837]'
lrwxrwxrwx 1 cryptonite cryptonite 0 mars  16 13:02 uts -> 'uts:[4026531838]'

# check uid of the process in the container (owner field)
cryptonite@host:~$ runc list
ID                    PID         STATUS      BUNDLE                                                         CREATED                          OWNER
rootless-crypt0n1t3   19104       running     /home/cryptonite/docker-security/internals/playground/bundle   2022-03-16T11:54:10.930816557Z   cryptonite

# double check
cryptonite@host:~$ ps aux | grep 19104
crypton+   19104  0.0  0.0   1632  1124 ?        Ss+  12:54   0:00 sh

Несколько слов о безопасности​


Runc — это мощный инструмент, обычно используемый более высокоуровневыми средами выполнения. Так это потому, что сам по себе runc является довольно низкоуровневой средой выполнения. Это не включает множество функций повышения безопасности из коробки, таких как seccomp, SELinux и AppArmor. Тем не менее, инструмент имеет нативную поддержку для вышеуказанных улучшений безопасности, но они не включены в конфигурации по умолчанию.

Код:
# no AppArmor
cryptonite@host:~$ cat /proc/19104/attr/current
unconfined
# no Seccomp
cryptonite@host:~$ cat /proc/19104/status | grep Seccomp            Seccomp:    0
Seccomp_filters:    0

Перевод статьи https://blog.quarkslab.com/digging-into-runtimes-runc.html
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх