Здарова щеглы! Сегодня будем разбираться с этой вашей контенирезацией с другой стороны – с переда, докера с заднего прохода нам уже хватило. Людям с неустойчивой психикой, лёгкой анальной воспламеняемостью, диванным экспертам, сильным админам локалхоста и мамкиным клауд архитекторам – срочно жать CTRL + W !!1adin Всех прошедших естественный отбор и эволюционировавших до прямоходящих – милости просим под кат.
Предупреждая бурление говн хочу сказать, что любой инструмент хорош для конкретной задачи, потому что можно и отвёрткой гвозди заколачивать, но зачем? Так уж получилось, что мы так или иначе относимся к бизнесу, и не мы диктуем ему условия, а он нам. А это значит, что если 10 разрабов придут к хэду и скажут, что “мы не будем разбивать наше монолитное приложение потому что …” то с вероятностью 99% их не уволят, но заставят админов тратить человекочасы на поиски решения задачи. Что ж, этим мы с вами, уважаемые господа эксперты, и займёмся!
Для тестов я выбрал:
- Ubuntu 18.04 так как она крайняя на данный момент LTS + LXC потому что и то и другое
подчиняется аксиоме Эскобарамэйнтэйнит Canonical. - Centos 8 + systemd просто потому что с centos’ами постоянно вожусь + это крайняя версия
Я хотел уместить это в одной статье, но по мере разбора и усиливающейся анальной боли – пришлось уместить в две. Сразу предупрежу, в этой и следующей статье не будет ничего про docker, потому что я хочу рассмотреть контейнеры для группы приложений, монолитного сервиса, который по каким-то причинам нельзя поделить на процессы, поэтому если вы вдруг какой-нибудь docker-евангелист, любезно просим Вас, мусье, не тратить боле время и пройти на выход!
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
Агонь!!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.
Оставалось одно – усиленно гуглировать в попытках найти ответы и я нашёл один старенький пост со свежими комментами которые, увы, не обнадеживают. Так же есть офигенное объяснение на тему “почему всё так сложно” и несколько трюков которые, к сожалению, нам не подходят.
Подведём итоги
Плюсы:
- Полноценная virtual environments (VEs) виртуализация
- Является куском от systemd
- Может как chroot в директорию, так и полноценные образы машин
- Удобно конфигурить стандартными конфигами systemd
Минусы:
- Нет поддержки cgroupfs/lxcfs/etc, а значит узнать в контейнере meminfo, uptime и т.п. контейнера – не получится, и я даже не нашёл как это можно закостылить
- Нет поддержки более одного бриджа. Чтоб выполнить такой финт ушами приходится сочинять “инженерные решения” (+ в centos 8 нет поддержки networkd что делает это ещё геморойнее)
- Фишки со снапшотами завязаны на btrfs поддержки которого нет в redhat дистрах
- Слабая поддержка, отсутствие активного комьюнити и как следствие, отсутствие best practice. Единственный способ с ними связаться – маиллист рассылка на developers и как вы думаете сколько мне пришло ответов? Ноль. Там вообще тишина
С чем-то я бы даже готов был смириться, но отсутствие таких фундаментальных вещй как подмена /proc/ чтоб можно было видеть базовые вещи типа потребления памяти или аптайма – ну это уже Эребор. Вердикт – В ПОМОЙКУ! Впрочем, не стоит отчаиваться, потому что в следующем обзоре рассмотрим LXC! Надеюсь оно-то в 2к20 увидит в приятную сторону.
И где этот замечательный разбор на LXC? Год уже ждем, всеми интернетами!
зачот! пешы исчо!