Container wars: nspawn!

2

Здарова щеглы! Сегодня будем разбираться с этой вашей контенирезацией с другой стороны – с переда, докера с заднего прохода нам уже хватило. Людям с неустойчивой психикой, лёгкой анальной воспламеняемостью, диванным экспертам, сильным админам локалхоста и мамкиным клауд архитекторам – срочно жать CTRL + W !!1adin Всех прошедших естественный отбор и эволюционировавших до прямоходящих – милости просим под кат.

2

Предупреждая бурление говн хочу сказать, что любой инструмент хорош для конкретной задачи, потому что можно и отвёрткой гвозди заколачивать, но зачем? Так уж получилось, что мы так или иначе относимся к бизнесу, и не мы диктуем ему условия, а он нам. А это значит, что если 10 разрабов придут к хэду и скажут, что “мы не будем разбивать наше монолитное приложение потому что …” то с вероятностью 99% их не уволят, но заставят админов тратить человекочасы на поиски решения задачи. Что ж, этим мы с вами, уважаемые господа эксперты, и займёмся!

Для тестов я выбрал:

  • Ubuntu 18.04 так как она крайняя на данный момент LTS + LXC потому что и то и другое подчиняется аксиоме Эскобара мэйнтэйнит Canonical.
  • Centos 8 + systemd просто потому что с centos’ами постоянно вожусь + это крайняя версия

Я хотел уместить это в одной статье, но по мере разбора и усиливающейся анальной боли – пришлось уместить в две. Сразу предупрежу, в этой и следующей статье не будет ничего про docker, потому что я хочу рассмотреть контейнеры для группы приложений, монолитного сервиса, который по каким-то причинам нельзя поделить на процессы, поэтому если вы вдруг какой-нибудь docker-евангелист, любезно просим Вас, мусье, не тратить боле время и пройти на выход!

2

Centos 8 + systemd-nspawn


Лолшто же такое nspawn? Это система управления изолированными пространствами имён, по сути chroot с автомаунтом псевдофайловых систем (/proc /sys) и поддержкой сетевых неймспейсов со всеми вытекающими. Очень удобная штука тащемто, когда есть монолитное приложение которое нельзя по каким-то причинам поделить по процессам и изолировать каждый из них. Ну да ладно, мы сюда не скучные вики-лекции пришли слушать, а позлорадствовать над лайф сторисами афтора. Запаситесь попкорном, мы начинаем.

Как ставить Centos 8 описывать не буду, вы ж у меня сильные админы, сами справитесь. После чтения пачноутов установки сталкиваемся с первым лайтовым полуогорчением – дохера умные ребята решили серьёзно подсесть на systemd но в то же время не стали включать в релиз systemd-networkd [пруф], действительно, нахера он нужен когда есть замечательный NetworkManager, раскудрить его через плетень, networkd, на секундочку, нихерово так помог бы с сетями для nspawn (но об этом позже)! Ладно хоть оставили совместимость с network-scripts, ну как оставили… написали костыли парсилки для NM. Второй апгрейд – отказ от iptables в пользу nftables, ну тут у меня претензий нет, хоть я и не освоил ещё их, благо ребята и об этом позаботились, снабдили костылями для конверта iptables > nfptables.

И так, ставим виновника торжества

dnf install systemd-container

Подрубаем br_netfilter чтоб фильтры работали в бриджах, подрубаем форвардинг, настраиваем собсно бриджи и ребутимся

echo br_netfilter > /etc/modules-load.d/br_netfilter.conf

cat << EOF > /etc/sysctl.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-arptables = 1
EOF

cat << EOF > /etc/sysconfig/network-scripts/ifcfg-br0 
DEVICE=br0
TYPE=Bridge
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.1
DNS=8.8.8.8
ONBOOT=yes
BOOTPROTO=static
#DELAY=0
STP=off
EOF

cat << EOF > /etc/sysconfig/network-scripts/ifcfg-br1 
DEVICE=br1
TYPE=Bridge
IPADDR=10.0.0.1
NETMASK=255.255.255.0
GATEWAY=10.0.0.1
DNS=1.1.1.1
ONBOOT=yes
BOOTPROTO=static
#DELAY=0
STP=off
EOF

reboot

Обеспечиваем бридж br0 интернетами, во втором они по задумке не нужны (но если хотите, я не против)

cat << EOF > /etc/sysconfig/iptables
# Generated by iptables-save v1.6.1 on Tue Feb 11 16:01:54 2020
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 192.168.0.0/24 ! -d 192.168.0.0/24 -j MASQUERADE
COMMIT
# Completed on Tue Feb 11 16:01:54 2020
# Generated by iptables-save v1.6.1 on Tue Feb 11 16:01:54 2020
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A FORWARD -o br0 -j ACCEPT
-A FORWARD -i br0 -j ACCEPT
COMMIT
# Completed on Tue Feb 11 16:01:54 2020
EOF

iptables-restore < /etc/sysconfig/iptables

Почти всё готово, мой юный нетерпеливый дружок потирающий потные ладошки. Теперь представим, что нам нужно запустить контейнер с centos7 (а почему бы и да), и сталкиваемся со следующим ахтунгом – так как nspawn работает подобно chroot, то и пакеты ставить туда надо подобно chroot, прям из системы, а значит если мы своими шаловливыми ручонками захотим запустить ubuntu инсайд, нам докучи надо будет ставить в систему, внезапно, apt . Впрочем, тут вроде как можно подрубать образы, об этом ниже. Только chroot, только хардкор!

wget http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-7 -O /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

cat << EOF > /etc/yum.repos.d/CentOS-7.repo
[centos7]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
EOF

dnf -y --disablerepo='*' --enablerepo=centos7 --releasever=7 --installroot=/var/lib/machines/c7-1 install systemd passwd yum vim-minimal centos-release systemd-networkd vim-enhanced bash bash-completion less psmisc openssh-server iputils iproute net-tools

И как завершающий штрих этой вакханалии позволяющий нам воровать, убивать, творить всякие непотребства с гусями заспавнить-таки наш контейнер

systemd-nspawn --machine=c7-1

Конгратулатионс! Ну, то есть, поздравляю (Щас бы в 2020 не знать энглиш, совсем крэйзи что ли). И что же мы можем делать внутри?! А, собсно, ничего, потому что сети у нас нет и контейнер кончится как только мы Press ^] three times. Вот тут-то и начинается самое интересное, но обо всём по-порядку. Сначала обеспечим наш контейнер сетью из br0. Заспавним его снова с аргументом –network-bridge=br0

systemd-nspawn --machine=c7-1 --network-bridge=br0

И внутри настроим единственный интерфейс

ip a a 192.168.0.2/24 dev host0
ip l s host0 up
ip r a default via 192.168.0.1 dev host0

Теперь можно попинать несчастный гугол ну или установить чёнить с помощью yum. Но это всё баловство, теперь нам надо заставить его запускаться самостоятельно, для этого у нас в системе есть заботливо написанный юнит /usr/lib/systemd/system/[email protected] но чтоб кастомайзить его не хардкодом, сделаем

mkdir /etc/systemd/nspawn

cat << EOF > /etc/systemd/nspawn/c7-1.nspawn
[Network]
VirtualEthernet=no
Bridge=br0
EOF

Теперь запустим контейнер в рантайме и через passwd установим свой оч сложный пароль (123). Когда всё сделано, можно попробовать заспавнить нашего монстра через systemd

systemd-nspawn -D /var/lib/machines/c7-1

passwd

systemctl start [email protected]

И, о чудо, оно бьётся в предсмертных конвульсиях какбэ прося “УБЕЙ МЕНЯ”, но всё равно можно считать, что живое! Заглянуть к нему внутрь можно с помощью machinectl login c7-1

2

Агонь!!1 Подумаете, наверно, вы потому, что теперь-то у нас есть всратое пикачу с интернетами внутри и что ещё может быть нужно для счастья? И как бы я хотел согласиться, но увы, не могу. Постепенно мы подошли к первому ограничению которое подорвало мне пердачелло – сюда нельзя, ну вот просто нельзя добавить больше одного бриджа, ну не хотят пасаны чтоб было удобно, не нужно им это… ох, чёт снова очекало разгорается. Ладно, если б я остановился на этом, то наверно и небыло бы статьи, поэтому пришлось выдумывать костыли. Они написали что нельзя заджойнить больше одного бриджа, но не запретили делать это вручную, смекаешь? 😉 В общем сначала я тыкался в неймспейсы и создавал кучу говна пар интерфейсов в системе, умножая тем самымм энтропию вселенной, но потом-таки пересилил себя и пролистал мануал где заметил замечательную опцию VirtualEthernetExtra которая сама создаёт пару интерфейсов один из которых помещает в контейнер, которую мы с вами и будем абузить. Сначала я было подумал написать юнит который был бы в зависимостях у [email protected], запускался после него и закидывал второй конец пары в бридж (Привет networkd), но вспомнил что в systemd есть замечательная опция ExecStartPost которая так же нам пригодится. И так, ТЗ: Создать машину с именем c7-2 на centos7 с двумя интерфейсами в br0 и br1 – изи катка, понеслася!

cp /var/lib/machines/с7-1 /var/lib/machines/с7-2

cat << EOF > /usr/lib/systemd/system/nspawn-c7-2.service
# Copy of /usr/lib/systemd/system/[email protected]
[Unit]
Description=Container c7-2
Documentation=man:systemd-nspawn(1)
PartOf=machines.target
Before=machines.target
After=network.target systemd-resolved.service
RequiresMountsFor=/var/lib/machines

[Service]
ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth -U --settings=override --machine=c7-2
ExecStartPost=/sbin/ip link set veth-c7-2 master br1
ExecStartPost=/sbin/ip link set veth-c7-2 up
KillMode=mixed
Type=notify
RestartForceExitStatus=133
SuccessExitStatus=133
WatchdogSec=3min
Slice=machine.slice
Delegate=yes
TasksMax=16384

# Enforce a strict device policy, similar to the one nspawn configures when it
# allocates its own scope unit. Make sure to keep these policies in sync if you
# change them!
DevicePolicy=closed
DeviceAllow=/dev/net/tun rwm
DeviceAllow=char-pts rw

# nspawn itself needs access to /dev/loop-control and /dev/loop, to implement
# the --image= option. Add these here, too.
DeviceAllow=/dev/loop-control rw
DeviceAllow=block-loop rw
DeviceAllow=block-blkext rw

# nspawn can set up LUKS encrypted loopback files, in which case it needs
# access to /dev/mapper/control and the block devices /dev/mapper/*.
DeviceAllow=/dev/mapper/control rw
DeviceAllow=block-device-mapper rw

[Install]
WantedBy=machines.target
EOF

cat << EOF > /etc/systemd/nspawn/c7-2.nspawn
[Network]
VirtualEthernet=no
Bridge=br0
VirtualEthernetExtra=veth-c7-2:host1
EOF

systemctl start nspawn-c7-2.service

И затем внутри просто настраиваем интерфейсы.

ip a a 192.168.0.3/24 dev host0
ip l s host0 up
ip r a default via 192.168.0.1 dev host0

ip a a 10.0.0.3/24 dev host1
ip l s host1 up

Такими нехитрыми манипуляциями нам-таки удаётся получить контейнер с двумя интерфейсами, но что делать когда нужно 3, 5, 10 интерфейсов? Похоже что сосать бибу костылить ручное создание и прокидывание внутрь, хотя я до конца не уверен что это вообще возможно by design.


Теперь коснёмся вопроса тимплейтинга и ФС. Всё что мы сейчас делали, фактически, происходило в чруте в /var/lib/machines/с7-1 . Из этого можно сделать вывод, что для того, чтоб управлять местом, достаточно просто определить для каждой машины свою точку монтирования и это правда, но есть маленький нюанс. После того, как мы заведём фабрику машин встанет вопрос о снапшотах и тут нас поджидает ещё одно огорчение – работа со снапшотами тут организована на базе btrfs которая болталась у redhat в статусе Technology Preview и так из него и не выйдя – улетела в помойку (Deprecated). Так что нативно снапшотов нам не видать. Ну-у-у, зато мы можем запускать настоящие образы, например, в формате RAW. У systemd в репах, кстати, для создания образов есть хорошая утилита – mkosi , что ж, давайте попробуем замутить пару образов. Выполняем любезно подготовленные мной командочки

dnf install python3 dosfstools git

git clone https://github.com/systemd/mkosi

cd mkosi

cat << EOF > mkosi.default
[Distribution]
Distribution=centos
Release=7

[Output]
Format=gpt_ext4
Bootable=no
Output=с7-2.raw

[Packages]
Packages=
        systemd
        tar
        mc
        yum
        rpm
EOF

./mkosi --password 123

Результатом станет вполне себе нормальный образ centos7: ./mkosi.output/с7-2.raw который при желании можно запускать даже через qemu и конечно же с помощью виновника статьи (Держу в курсе: Пароль рута 123)

systemd-nspawn -bi mkosi.output/с7-2.raw

Больше примеров использования mkosi, в том числе по регулированию объёма диска, можно найти в этих ваших интернетах, не будем заострять на нём внимания в этой статье.


Что ж, как всё хорошее когда-то заканчивается, так и мы постепенно подошли к фишке nspawn которая сделала фаталити моему пукану, после которого я и собрался написать этот постмортрем моего монументального краха из-за задачи, которую я не смог решить, впрочем что с меня взять, я ж просто автомеханик. После успешного получения интернетов и возможностью управлять местом жёсткого диска машины встал вопрос – как разграничивать прочие ресурсы выделяемые машине (Memory, CPU, etc)? “ЛОЛ Изи” – ответил я, и вхерачил в свежесозданный юнит /usr/lib/systemd/system/nspawn-c7-2.service в директорию [Service] ограничения по памяти

MemoryHigh=100M
MemoryMax=100M
MemorySwapMax=1M

Не забыл зарелоадить изменения systemctl daemon-reload и подрубив аккаунтинг systemd чтоб видеть сколько и чего используют юниты (машины в том числе)

cat << EOF > /etc/systemd/system.conf
[Manager]
ShowStatus=yes
DefaultCPUAccounting=yes
DefaultBlockIOAccounting=yes
DefaultMemoryAccounting=yes
DefaultTasksAccounting=yes
EOF

После чего ребутнул хост. Не забудьте восстановить правила nftables (так как в нашем тестовом стенде они не восстанавливаются при ребуте): iptables-restore < /etc/sysconfig/iptables
Всё сделано, думал я, осталось запустить машину systemctl start nspawn-c7-2.service и залогиниться внутрь machinectl login c7-2 и каково же было моё удивление, негодование, отчаяние и попаболь, когда внутри машины я увидел

# free -m
              total        used        free      shared  buff/cache   available
Mem:            821         108         192           1         520         587
Swap:           819          36         783

При том что systemd честно говорит нам что ограничил гадине всё что можно

# systemctl status nspawn-c7-2.service
● nspawn-c7-2.service - Container c7-2
   Loaded: loaded (/usr/lib/systemd/system/nspawn-c7-2.service; disabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-02-15 12:04:39 EST; 2min 14s ago
...
   Memory: 44.1M (high: 100.0M max: 100.0M swap max: 1.0M)
...

Стало очевидно, что я нубас который ошибся с выбором деятельности, мой максимум – кубики друг на друга поставить, а не контейнеры разворачивать. Каким же я был дегром когда не учёл сложностей с CGroups.

2

Оставалось одно – усиленно гуглировать в попытках найти ответы и я нашёл один старенький пост со свежими комментами которые, увы, не обнадеживают. Так же есть офигенное объяснение на тему “почему всё так сложно” и несколько трюков которые, к сожалению, нам не подходят.

Подведём итоги


Плюсы:

  • Полноценная virtual environments (VEs) виртуализация
  • Является куском от systemd
  • Может как chroot в директорию, так и полноценные образы машин
  • Удобно конфигурить стандартными конфигами systemd

Минусы:

  • Нет поддержки cgroupfs/lxcfs/etc, а значит узнать в контейнере meminfo, uptime и т.п. контейнера – не получится, и я даже не нашёл как это можно закостылить
  • Нет поддержки более одного бриджа. Чтоб выполнить такой финт ушами приходится сочинять “инженерные решения” (+ в centos 8 нет поддержки networkd что делает это ещё геморойнее)
  • Фишки со снапшотами завязаны на btrfs поддержки которого нет в redhat дистрах
  • Слабая поддержка, отсутствие активного комьюнити и как следствие, отсутствие best practice. Единственный способ с ними связаться – маиллист рассылка на developers и как вы думаете сколько мне пришло ответов? Ноль. Там вообще тишина

С чем-то я бы даже готов был смириться, но отсутствие таких фундаментальных вещй как подмена /proc/ чтоб можно было видеть базовые вещи типа потребления памяти или аптайма – ну это уже Эребор. Вердикт – В ПОМОЙКУ! Впрочем, не стоит отчаиваться, потому что в следующем обзоре рассмотрим LXC! Надеюсь оно-то в 2к20 увидит в приятную сторону.

2

Leave a Reply

Your email address will not be published. Required fields are marked *