Все знают о Docker, но мало кто знает об используемых им технологиях. В этом посте мы проанализируем одну из самых фундаментальных и мощных технологий, скрытых за Docker — RUNC
Контейнерные технологии появились впервые после изобретения контрольных групп и пространств имен . Два из первых известных проектов пытаются объединить их для достижения изолированных технологических сред. 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 , а именно: целостность образа, журнал истории пакетов и эффективное использование корневой файловой системы контейнера. Будьте готовы, мы мы собираемся изучить эти более оцененные среды выполнения!
Как упоминалось выше, runc — это OCI среда выполнения, совместимая программный компонент, отвечающий за создание, настройку и управление изолированными процессами Linux, также называемыми контейнерами. Формально runc — это клиентская оболочка вокруг libcontainer . Поскольку runc следует OCI для сред выполнения контейнеров, он требует двух частей информации:
Давайте теперь посмотрим, как мы можем использовать runc и вышеупомянутые Компоненты спецификации.
Из приведенного выше фрагмента кода видно, что config.json файл содержит информацию о:
Всей вышеописанной конфигурации почти полностью достаточно для libcontainer для создания изолированного Linux (контейнера). Там для создания контейнера не хватает одной вещи - корневого каталога процесса. Давайте загрузим его и сделаем доступным для runc .
Мы загрузили корневую файловую систему Alpine Linux — недостающая часть OCI спецификация времени выполнения . Теперь мы можем создать контейнерный процесс, выполняющий sh shell.
Хорошо, давайте соберем все вместе в этот пакет и создадим нового контейнерa! Мы будем различать два типа контейнеров: - root - контейнеры, работающие под UID=0. - без root - контейнеры, работающие под UID, отличным от 0.
Почти все готово. Нам осталось позаботиться об одной последней детали: так как мы хотели бы отсоединить наш корневой контейнер от runc и его файловые дескрипторы для взаимодействия с ним, мы должны создать TTY сокет и подключаемся к нему с двух сторон (со стороны контейнера и со стороны нашего Терминал). мы собираемся использовать recvtty который является частью официального runc.
Теперь мы переключаемся на другой терминал, чтобы создать контейнер через runc .
Наш контейнер теперь создан, но он не запускает определенный sh, настроенные в JSON . Это потому, что runc init по-прежнему поддерживает процесс, который должен загрузить файл sh программа в пространстве имен (изолированной) среде. Давайте проверим, что есть происходит внутри runc init .
Мы можем подтвердить, что, за исключением пользователя и времени пространств имен init runc использует другой набор пространств имен, чем наша обычная оболочка. Но почему он не запустил наш процесс?
Время еще раз сказать, что runc — довольно низкоуровневая среда выполнения и не обрабатывает множество конфигураций по сравнению с другими более высокоуровневые среды выполнения. Например, он не настраивает сеть,. интерфейс для изолированного процесса. Для дальнейшей настройки процесс-контейнер, runc помещает его в защитное средство (runc-init) где можно добавить дополнительные конфигурации. Чтобы проиллюстрировать более практический способ, почему это сдерживание может быть полезным, мы собираемся настройть сетевое пространство имен для нашего детского контейнера.
Видим, что действительно интерфейсов в сетевом пространстве имён нет, runc init он от мира. Давайте исправим это!
Теперь мы можем еще раз проверить, что происходит в сетевом пространстве имен.
Теперь мы настроили виртуальный интерфейс и подключили сеть пространства имен процесса инициализации runc с пространством имен хост-сети. Эта конфигурация не является обязательной, и процесс контейнера выполняется оболочка sh может быть запущена без подключения к хосту. Однако эта конфигурация может быть полезна для других процессов доставки сетевые функциональные возможности. Программа sh по-прежнему не работает внутри процесс по желанию. После завершения настройки можно, наконец, создать контейнер, в котором запущена предопределенная программа.
Контейнер, наконец, работает под тем же PID, но изменил свой исполняемый файл /bin/sh . Между тем, в терминале, содержащем recvtty shell - ура! Мы можем взаимодействовать с нашим недавно созданным контейнером.
Для тех, кто знаком с другими контейнерными технологиями, этот терминал знакомый
. Хорошо runc действительно крут, но что еще мы можем с ним сделать? Ну, почти все, что касается управления жизнью этого контейнер.
Чтобы изменить это, можно изменить config.json .
Это решение слишком общее. Отсюда лучше определить более конкретные правила для различных подкаталогов в корневом файле система.
Из приведенного выше фрагмента кода видно, что после приостановки контейнера системный процесс переводится в состояние Ds+ . Это состояние переводится как непрерывный сон (состояние для ввода/вывода). В этом состоянии процесс не получает почти никаких сигналов ОС и не может быть отлажен (след). Возобновим его и снова проверим его состояние.
Видно, что после возобновления процесс изменил свое состояние на Ss+ ( прерываемый сон ). В этом состоянии он снова может получать сигналы и отлаживаться. Давайте глубже исследуем, как сон и "возвращение" выполняются на уровне ядра.
Из приведенных выше фрагментов кода видно, что пауза контейнера реализована с использованием сигналов Linux ( SIGRTMIN , SIGRT_1 ) и предопределенных обработчиков.
Некоторые из приведенных выше ограничений представляют собой жесткие ограничения , установленные cgroups в Linux
Эта функция дает действительно точное представление о том, что происходит с процессом с точки зрения потребляемых ресурсов в режиме реального времени. Этот журнал может быть действительно полезно для судебно-криминалистической экспертизы. По умолчанию runc выводит глобальную информацию о каждом контейнере при создание в файле /var/run/runc/state.json .
Здесь мы продемонстрируем еще один способ запуска контейнера, пропуская этап настройки (runc init). Давайте запустим его.
Из приведенного выше фрагмента видно, что контрольная точка представляет собой набор файлов формата img (файл изображения CRIU v1.1) . Содержимое из этих файлов плохо читаются и представляют собой смесь бинарных и тестовых содержание. Давайте теперь возобновим наш процесс принтера, который во время остановка была на 84.
Вернемся к recvtty .
Процесс возобновился в прежнем состоянии, как будто ничего не произошло. Мы создали точно такую же копию контейнера (файловые дескрипторы, процессы и т. д.), но в предыдущем сохраненном состоянии.
Давайте посмотрим, что происходит с точки зрения контейнера.
Новый процесс ожидания контейнера действительно унаследовал то же самое пространства имен как процесс оболочки уже внутри контейнера.
Исполняемый путь разрешается и выполняется в контейнере пространства имен.
PostStart
Хук Poststart вызывается после выполнения указанного пользователем процесса, но до возврата операции запуска. Например, этот хук может уведомить пользователя о порождении процесса контейнера. Путь исполняемого файла разрешается, и он выполняется в пространстве имен среды выполнения.
PostStop
PostStop вызываются после удаления контейнера (или выхода процесса), но до возврата операции удаления. Примерами таких крючков являются функции очистки или отладки. Путь к исполняемому файлу разрешается, и он выполняется в пространстве имен среды выполнения.
Синтаксис для определения хуков в config.json следующий:
Был разработан небольшой POC для целей понимания этой функции. .Вот основные его элементы:
Информацию об определенных хуках работающего контейнера можно найти в /run/runc/state.json (настраиваемый путь с --root ).
Давайте запустим контейнер и обновим его аппаратные ограничения.
Теперь мы введем новое ограничение по потреблению оперативной памяти. Этот предел относится к группе cgroup памяти
Если мы посмотрим на наш виртуальный tty , процесс внезапно зависнет.
Обновив лимит памяти cgroup контейнера, мы фактически вернул немного памяти назад. Обновление контрольной группы ЦП также может увеличивать или снизить производительность контейнера. Cgroups — это швейцарский армейский нож. но требуется тщательная настройка.
Runc поставляется со средствами для создания файлов конфигурации без рута.
Мы видим, что runc добавил в файл два поля, указывающих, кому переназначить пользователя процесса внутри контейнера. По умолчанию он сопоставляет его с uid пользователя, выполняющего команду. Давайте теперь создадим новый контейнер с новой спецификацией среды выполнения.
Давайте проверим пространства имен и идентификаторы пользователей из обоих "точки зрения".
Runc — это мощный инструмент, обычно используемый более высокоуровневыми средами выполнения. Так это потому, что сам по себе runc является довольно низкоуровневой средой выполнения. Это не включает множество функций повышения безопасности из коробки, таких как seccomp, SELinux и AppArmor. Тем не менее, инструмент имеет нативную поддержку для вышеуказанных улучшений безопасности, но они не включены в конфигурации по умолчанию.
Перевод статьи https://blog.quarkslab.com/digging-into-runtimes-runc.html
Краткая история контейнеров: от бардака к стандартам и правильной архитектуре
Контейнерные технологии появились впервые после изобретения контрольных групп и пространств имен . Два из первых известных проектов пытаются объединить их для достижения изолированных технологических сред. 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 , а именно: целостность образа, журнал истории пакетов и эффективное использование корневой файловой системы контейнера. Будьте готовы, мы мы собираемся изучить эти более оцененные среды выполнения!
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
Код:
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
Код:
# 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 монтирует корневую файловую систему, доступную только для чтения. Это проблематично, потому что наш процесс не может использовать файловую систему, в которой он «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
Код:
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
Код:
# 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
Код:
...
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
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