Docker – full functional systemd with docker container: полнофункциональный docker контейнер с systemd

В одной из предыдущих публикаций я показал как собрать образ с нуля для контейнера на основе пакетной базы 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! Имею почти ОС))) Однако о безопасности и стабильности не сказано ни слова. Так же ни слова о том что эти процессы могут создать перегрузки без должного контроля лимитов памяти и процессора. Тем не менее делаем как я и наслаждаемся еще однои вариантом изоляции разработчиков – этих парней надо держать на правильном поводке))

Scroll to top