В одной из предыдущих публикаций я показал как собрать образ с нуля для контейнера на основе пакетной базы CentOS использую классический supermin инструмент.
Однако все такие контейнеры на базе некоторого исполняемого кода (как endpoint или cmd), берушего на себя функции начального процесса с PID 1, далеки от какой-либо эмуляции присутствия среды “контейнерной” ОС. Весь функционал пакета приложения может быть использован только если среда будет максимально похожа на ставшую уже стандартом де-факто systemd. Для этого нужен полноценный init (читай systemd) процесс, способный работать с сервисами в контейнерной изоляции docker как в обычной полной виртуализации или на обычном сервере. Почему это важно – понятно от сюда (https://docs.docker.com/config/containers/start-containers-automatically/).
Контенейры могут стартовать автоматически, а вот вся остальная логика запуска сервисов будет в руках менеджеров процессов.
Все это ни в коей мере не нужно если изолируется минимальный набор микро-приложения собственной сборки с интерпретатором и его зависимостями или тем более бинарник статической сборки. Изоляция кастомных микропрограмм по сути единственное важное и нужное в применении изоляций в docker или k3s.
В “родных” контейнерах изоляторах построенных на базе контейнеров systemd-nspawn все это заложено изначально, и такие контейнеры сразу запуcкают полноценный процесс init – systemd. Но так ли сложно обойти “костыли” архитектуры docker/k8s/k3s и запустить systemd под его изоляцией полноценно? Давайте проверим.
Углубившись слегка в понимание процесса загрузки ОС, в котором ядро передает управление некоему процессу init, мы можем понять, что любой такой процесс будет выполнять ряд логических процессов инициализации сервисов и среды из одной начальной точки до некоторого целевого состояния. И совсем не факт что мы не можем пропустить большую часть этих действий по своему усмотрению.
В systemd все это крутится вокруг сущностей целей, сервисов и юнитов. Значит, передавая в докер контейнер systemd как начальную входную точку, мы можем попытаться определить его цель по умолчанию на основе специфики обоих платформ. Хотя по всему видно, что они друг другу не друзья. И контейнеры на базе systemd-nspawn обладает не меньшей мощью и гибкостью.
Допустим, что как и в предыдущей публикации, мы уже знаем как создать базовую сущность supermin5 только с одним пакетом – yum.
Теперь попробуем создать это с двумя пакетами – systemd и yum:
$ supermin --verbose --prepare yum systemd -o centos8s.supermin.base.systemd
Просмотрев построенное файловое дерево конфигурационных файлов, уже видим, что появились новые пути в дереве каталогов где мы можем размещать файлы конфигурации systemd.
Далее создаем реальное дерево файлов на основе конфигурации в chroot с установкой всех пакетов:
$ supermin --verbose --build --include-packagelist --format chroot centos8s.supermin.base -o centos8s.build.chroot.systemd.stage1
Все готово к данному моменту для создания файла целевой загрузки systemd – default.target.
default.target – это все тот же юнит типа target. Соответственно, если мы определим в качестве зависимостей к таргету другие юниты, вы заставим systemd загружать иные сервисы и т.д.
А default.target будет конечной точкой состояния системы. Весь процесс инициализации systemd можно представить в виде дерева, где в корне расположен default.target. Systemd начинает строить дерево зависимостей сверху вниз, начиная с default.target, а вот запускаются сервисы в обратном пордяке, соблюдая зависимости по пути движения к конечному состоянию.
Переходим в структуру каталогов в свежесозданной инсталляции (centos8s.appliance.d/etc/systemd/system) и создаем файл с именем isolated.target и линк на него из default.target
$ cd centos8s.appliance.d/etc/systemd/system $ tree -L 1 . ├── basic.target.wants ├── bluetooth.target.wants ├── getty.target.wants ├── graphical.target.wants ├── multi-user.target.wants ├── remote-fs.target.wants ├── sockets.target.wants ├── sysinit.target.wants └── timers.target.wants $ cat isolated.target [Unit] Description = systemd in docker containers $ ln -sf isolated.target default.target $ tree -L 1 . ├── basic.target.wants ├── bluetooth.target.wants ├── default.target -> isolated.target ├── getty.target.wants ├── graphical.target.wants ├── isolated.target ├── multi-user.target.wants ├── remote-fs.target.wants ├── sockets.target.wants ├── sysinit.target.wants └── timers.target.wants
Все теперь мы можем упаковать это дерево установки в chroot в архив tar который будет представлять архив нового файлового образа для импорта в среду docker, не забывая добавить волшебную переменную для yum. И импортируем в локальные образы:
$ mkdir -p centos8s.appliance.d/etc/dnf/vars $ mkdir -p centos8s.appliance.d/etc/yum $ ln -sr centos8s.appliance.d/etc/dnf/vars -t centos8s.appliance.d/etc/yum $ echo 8 > centos8s.appliance.d/etc/yum/vars/releasever $ tar --numeric-owner -cpf centos8s_zero_systemd.tar -C centos8s.appliance.d . $ cat centos8s_zero_systemd.tar | docker import - local/centos8s-zero-systemd sha256:30c8fd5159c9694b5eb3d960431213b7ef831f501772e89efa3ae739a6c2e4ae $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE local/centos8s-zero-systemd latest 30c8fd5159c9 12 seconds ago 354MB ...
Теперь можем запустить на выполнение контейнер на базе этого файлового образа в интерактивном режиме, указав в качестве команды запуск оболочки bash:
$ docker run -it --restart unless-stopped local/centos8s-zero-systemd /bin/bash 5f74d3aa55cf9f1ebf62883968ee746f8c47262a0f671e582c1fc9a54b9ca10d
В контейнере доустанавливаем пакеты до минимального набора и готовим среду ssh сервера:
bash-4.4# ldconfig bash-4.4# yum clean all && yum makecache && yum -y install rpm rpm-build-libs rpm-libs python3-rpm yum cracklib cracklib-dicts binutils dbus-libs device-mapper device-mapper-libs file iproute iptables iptables-services iputils groff-base less libcroco libgomp libunistring vim rootfiles openssh-server bash-4.4# ssh-keygen -A ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519 bash-4.4# mkdir /var/run/sshd bash-4.4# echo 'root:12345678' | chpasswd bash-4.4# exit exit
Теперь как и в предыдущей статье экспортируем содержимое файловой системы контейнера в tar архив. И далее в новый образ.
$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 32b81a03314c local/centos8s-zero-systemd "/bin/bash" 12 minutes ago Up 2 minutes interesting_roentgen $ docker export 32b81a03314c > centos8s_minimal_systemd.tar $ cat centos8s_minimal_systemd.tar | docker import - local/centos8s-minimal-systemd $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE local/centos8s-minimal-systemd latest 8679c3c72188 10 seconds ago 880MB
Теперь для дальнейшего создания минимального контейнера запускающего systemd я создаю подходящий Dockerfile, который использует ENDPOINT для запуска первого процесса.
$ cat dockerfile_centos8s_minimal_systemd FROM local/centos8s-minimal-systemd ENV container=docker ENTRYPOINT ["/sbin/init"] CMD ["--log-level=info"] STOPSIGNAL SIGRTMIN+3
И создаем образ из базового образа и Dockerfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | [root@vms zero_systemd]# docker build --force-rm=true --file=dockerfile_centos7_zero_systemd --tag=local/centos7-minimal-systemd-prod . Sending build context to Docker daemon 1.235GB Step 1/5 : FROM local/centos7-minimal-systemd ---> c66daf12f37c Step 2/5 : ENV container=docker ---> Running in d7e2f5d563d3 Removing intermediate container d7e2f5d563d3 ---> dcb2fc59f035 Step 3/5 : ENTRYPOINT ["/sbin/init"] ---> Running in 606366a5264c Removing intermediate container 606366a5264c ---> b9a6cc813b69 Step 4/5 : CMD ["--log-level=info"] ---> Running in 1cb68d97e79a Removing intermediate container 1cb68d97e79a ---> 0ae99593674a Step 5/5 : STOPSIGNAL SIGRTMIN+3 ---> Running in c227cf4b927c Removing intermediate container c227cf4b927c ---> 85496fdaa8ac Successfully built 85496fdaa8ac Successfully tagged local/centos7-minimal-systemd-prod:latest [root@vms zero_systemd]# [root@vms zero_systemd]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE local/centos7-minimal-systemd-prod latest 85496fdaa8ac 21 seconds ago 667MB local/centos7-minimal-systemd latest c66daf12f37c About a minute ago 667MB [root@vms zero_systemd]# |
А теперь создаем контейнер монтирую спец директории внутрь контейнера:
1 2 3 4 | [root@vms zero_systemd]# docker run -d --name centos7-systemd-empty --mount type=bind,source=/sys/fs/cgroup,target=/sys/fs/cgroup --mount type=tmpfs, destination=/run --mount type=tmpfs,destination=/run/lock local/centos7-minimal-systemd-prod 0bcb1f6ff439bd33351ece078cc197113673c96add37820c7cb4766983f6ee48 [root@vms zero_systemd]# |
1 2 3 4 5 | [root@vms zero_systemd]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0bcb1f6ff439 local/centos7-minimal-systemd-prod "/sbin/init --log-le…" 11 seconds ago Up 10 seconds centos7 -systemd-empty [root@vms zero_systemd]# |
Ныряем в контейнер и видим резуьтат
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [root@vms zero_systemd]# docker exec -it 0bcb1f6ff439 /bin/bash [root@0bcb1f6ff439 /]# [root@0bcb1f6ff439 /]# top top - 19:25:37 up 76 days, 4:00, 0 users, load average: 0.02, 0.09, 0.11 Tasks: 7 total, 1 running, 6 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.2 us, 0.3 sy, 0.0 ni, 99.3 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 65633276 total, 10434748 free, 7242468 used, 47956060 buff/cache KiB Swap: 33521660 total, 33521660 free, 0 used. 57179284 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 43152 3288 2304 S 0.0 0.0 0:00.06 systemd 16 root 20 0 39084 5184 4912 S 0.0 0.0 0:00.01 systemd-journal 22 root 20 0 24256 1484 1240 S 0.0 0.0 0:00.00 systemd-logind 23 root 20 0 112920 4304 3280 S 0.0 0.0 0:00.00 sshd 24 dbus 20 0 58000 2260 1776 S 0.0 0.0 0:00.00 dbus-daemon 27 root 20 0 11828 1884 1492 S 0.0 0.0 0:00.01 bash 43 root 20 0 56204 1972 1436 R 0.0 0.0 0:00.00 top |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [root@vms zero_systemd]# ssh root@172.17.0.2 The authenticity of host '172.17.0.2 (172.17.0.2)' can't be established. ECDSA key fingerprint is SHA256:n15H3aNFHrPZ8kQYMLVawBkkJS1/Q4ZQelPPhQ4aSGg. ECDSA key fingerprint is MD5:c1:f8:a1:29:c8:06:a3:d7:15:87:dd:73:d6:29:3f:9b. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '172.17.0.2' (ECDSA) to the list of known hosts. root@172.17.0.2's password: Last login: Sat Feb 8 17:07:23 2020 [root@0bcb1f6ff439 ~]# ps -eFH UID PID PPID C SZ RSS PSR STIME TTY TIME CMD root 1 0 0 10788 3284 1 19:39 ? 00:00:00 /sbin/init --log-level=info root 16 1 0 9771 5200 6 19:39 ? 00:00:00 /usr/lib/systemd/systemd-journald dbus 21 1 0 14500 2084 4 19:39 ? 00:00:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-act root 23 1 0 28230 4308 2 19:39 ? 00:00:00 /usr/sbin/sshd -D root 29 23 0 38836 6188 3 19:40 ? 00:00:00 sshd: root@pts/0 root 31 29 0 28862 1972 0 19:40 pts/0 00:00:00 -bash root 46 31 0 38842 1828 4 19:40 pts/0 00:00:00 ps -eFH root 24 1 0 6595 1660 3 19:39 ? 00:00:00 /usr/lib/systemd/systemd-logind [root@0bcb1f6ff439 ~]# |
“Загнали” systemd в изоляцию, но он все равно выполнил все установленные сервисы и запустил в этой изоляции sshd! Имею почти ОС))) Однако о безопасности и стабильности не сказано ни слова. Так же ни слова о том что эти процессы могут создать перегрузки без должного контроля лимитов памяти и процессора. Тем не менее делаем как я и наслаждаемся еще однои вариантом изоляции разработчиков – этих парней надо держать на правильном поводке))