LXC-контейнеры для домашнего сервера (часть первая)

2016-08-11

LXC на домашнем сервере

Вместо вступления

Контейнерная виртуализация переживает настоящий бум именно сейчас. Увлечение Docker с целью или без оной не оставило равнодушным ни одного более-менее адекватного “ойтишнега”, который хочет быть “в тренде” и тщетно прокачивает скиллы в надежде на светлое будущее. LXC (Linux Containers) плетутся (или бегут ?) где-то рядом.

Дальнейший текст - лишь результат эксперимента, проведенного на домашнем сервере под управлением Linux. Он не претендует на полноту и точность, и, совершенно точно, можно сделать лучше. Но всем, вобщем-то пофигу.

В чём проблема, бро ?

Есть домашний сервер, который работает качалкой торрентов, хостингом OwnCloud, TOR-middlebox’ом и помимо этого тащит на себе TiddlyWiki@node.js и пару других чахлых web-приложений.
Задача - изолировать приложения друг от друга и от хост-системы. Зачем ? Ответов несколько:

  • Поддерживать хост (bare metal) систему в рабочем предсказуемом состоянии не загаживая её кучей зависимостей, а тем более - собранных из исходников пакетов. Место на диске считаем условно бесконечным, учитывая цену за гигабайт.
  • Пусть каждое приложение работает в окружении, которое рекомендовано производителем. То есть, если кто-то любит Lighttpd, а для OwnCloud готовые решения хорошо документированы при использовании nginx, ради бога - пусть это будет nginx.
  • Настроить бэкап так, чтобы инфраструктуру можно было восстановить “обратно” не тратя времени на повторную настройку и сборку граблей.
  • Потому что хочется (это, кстати, самый веский аргумент)

Как будем разруливать ?

Довольно очевидным способом, а именно создадим набор контейнеров, каждый из которых будет иметь собственный внутренний IP-адрес, выделенный кусочек LVM, и сильно популярный дистрибутив, дабы не искать выход из тупика на говнофорумах расейских пользователей типа LOR, а пользоваться уже накопленными и систематизированными знаниями в кормушках типа LinuxQuestions и StackOverflow. Как уже говорилось - на сегодня у нас 2 мощнейших кандидата - Docker и LXC. Для моих задач больше подходит LXC - Docker всё-таки больше подходит под deploy и “одно приложение-один контейнер”. Ну и заморочки с persitent тоже время отожрут немалое, а толку от них в домашнем хозяйстве не очень много.

Небольшое замечание - я использовал привилегированные контейнеры, т.е. работающие от root. Смысл пользовательских контейнеров в ещё более “сильной изоляции” и безопасности, но они требуют немного больше усилий в установке.

Docker faggots

Подготовка

Все действия производились на Cubieboard 2 под управлением Armbian и ядра 4.х.
Тестирование proof-of-concept проводилось на Андроид-стике с ядром 3.0.36 (на Linux, если что), так что решение заработает на любом ущербном китайском устройстве, даже если нет “последнего ядра”, лишь бы все нужные флаги в ядре присутствовали (поддержка cgroups, например). Ну за этим - в документацию. Там всё хорошо описано.

Для начала нам нужно будет создать ещё одну подсеть, в которой будут жить наши контейнеры. Шаги:

  • Установим lxc, он “потащит” за собой нужные пакеты.
  • Дальше - настроим сеть.
  • Ещё нужно позаботиться о дисковом пространстве. Я использую LVM в качестве storage backend, но можно обойтись без него и файловая система “ляжет” в обычную директорию.

Сеть

Нам потребуется “поднять” bridge-интерфейс. При этом я не привязывал его к конкретному сетевому адаптеру, чтобы работало даже на устройствах, где кроме беспроводного интерфейса ничего нет.

Этот трюк почерпнут из Archlinux Wiki - дело в том, что полноценный бридж на беспроводном адаптере не создать, зато можно настроить masquerading и получить доступ к интернету из контейнера. Может оно и к лучшему.

Вот примерно так выглядит /etc/network/interfaces:

1
2
3
4
5
6
7
#empty bridge
auto br0
iface br0 inet static
address 192.168.242.1
netmask 255.255.255.0
network 192.168.242.0
bridge_ports none

Итак, сеть вида 192.168.242.x будет отдана под контейнеры.

На сетевой карте, которая “смотрит” в интернет нужно включить masquerading с помощью iptables, ну и не забыть бы forwarding в настройках ядра.
Делается примерно так, правда нужно сохранить настройки, чтобы переживать перезагрузку (способов много):

iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE /sbin/sysctl -w net.ipv4.ip_forward=1

Ещё я использую пакет dnsmasq для обеспечения доступа к DNS своих контейнеров. После установки нужно будет добавить в конфигурационный файл /etc/dnsmasq.conf такие строчки:

1
2
interface=br0
no-dhcp-interface=br0

Мы говорим, что DNS доступен на интерфейсе br0, и мы используем статические IP для контейнера.

Диск

Как уже говорилось - в случае использования LVM должно быть достаточное количество дискового пространства. По-умолчанию контейнер выделяет себе гигабайт. Как показала практика - это не очень много :)

Если LVM не используется - вы ограничены только свободным местом на основном диске.

На самом деле - гонять контейнер из директории идейка “так себе”, особенно, если корневая файловая система живет на microSD. В этом случае надо хорошо подумать об оптимизации флеш-карты. Да и LVM оказался побыстрее.

Первый контейнер

В принципе, можно создавать первый контейнер. Делается это командой lxc-create, в моем случае это выглядит вот так:

sudo lxc-create -t download -n applications -B lvm --vgname vg0 --fssize 2G --lvname applications

Рассмотрим подробнее:

  • Мы будем использовать LVM для storage backend.
  • Мы будем скачивать готовый rootfs с сайта LXC (кстати, он иногда любит упасть). Выберем его позднее в интерактивном режиме выполнения lxc-create.
  • Контейнер будет называться “applications”
  • Он будет создан в Volume Group vg0 (/dev/vg0)
  • Логический том будет иметь то же имя (applications)
  • Мы выделяем под него 2 гигабайта места

В случае не-использования lvm - решительно убираем опции -B, --vgname, --fssize, --lvname.

После запуска нам предлагают кучу дистрибутивов на выбор:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Downloading the image index

---
DIST RELEASE ARCH VARIANT BUILD`
---
centos 6 amd64 default 20160811_02:16
centos 6 i386 default 20160811_02:16
centos 7 amd64 default 20160811_02:16
debian jessie amd64 default 20160810_22:42
debian jessie arm64 default 20160811_03:58
.....
ubuntu yakkety armhf default 20160811_03:49
ubuntu yakkety i386 default 20160811_03:49
ubuntu yakkety powerpc default 20160811_03:49
ubuntu yakkety ppc64el default 20160811_03:49
ubuntu yakkety s390x default 20160811_03:49

Напомню, что весь процесс у нас происходит на процессоре ARM (CubieBoard2, Orange Pi), поэтому архитектуру мы будем использовать armhf.

Кстати, об образах - в LXC 2.0.x их немного больше и доступен Alpine Linux - отличный легковесный дистр, как будто созданный для контейниризации. LXC 2.0 доступен из бэкпортов Jessie, его можно установить вместо дефолтного 1.0. Но концепция озвученная ранее - “только дефолтные пакеты на базовой системе” и общая лень не дали мне этого сделать.

Вообще, лучше выбирать какой-нибудь известный дистрибутив. Я использвал ubunty trusty для armhf - это всё-таки LTS, хоть и старый, да и под настройку всего полно туториалов.

После ответа на вопросы начнется закачка, или, если это не первый контейнер - будет использована закэшированная копия.

После окончания процесса получаем примерно такое сообщение:

1
2
3
4
5
You just created an Ubuntu container (release=trusty, arch=armhf, variant=default)
To enable sshd, run: apt-get install openssh-server
For security reason, container images ship without user accounts
and without a root password.
Use lxc-attach or chroot directly into the rootfs to set a root password or create user accounts.

Не торопитесь запуcкать контейнер. Для начала его нужно настроить - создать пароль root’а по крайней мере, да и сеть тоже не будет лишней.

Установка пароля root в контейнере

Вот тут начинается самое главное. Конечно, можно воспользоваться советом системы и запустить lxc-attach, но у меня лично на старом тестовом ядре не заработал “как надо”. Так что предлагаю смонтировать lvm в директорию, и сделать туда chroot перед запуском, чтобы установить пароль root и настроить сеть.

Вот настройка настройка сети для использования статического адреса в контейнере с Ubuntu 14.04:

1
2
3
4
5
6
auto eth0
iface eth0 inet static
address 192.168.242.120
netmask 255.255.255.0
gateway 192.168.242.1
dns-nameservers 192.168.242.1

Команды, которые нужно выполнить для chroot в контейнер и настройки сети

1
2
3
4
5
6
7
mount /dev/vg0/applications /mnt
chroot /mnt
passwd
vi /etc/network/interfaces
(....тут настраиваем сеть....)
exit
umount /mnt

Подготовка конфигурационного файла

Вторым шагом перед стартом контейнера - его нужно донастроить. Конфигурационный файл в нашем случае - /var/lib/lxc/applications/config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#Distribution configuration
lxc.include = /usr/share/lxc/config/ubuntu.common.conf
#Container specific configuration
lxc.rootfs = /dev/vg0/applications
lxc.utsname = applications
lxc.arch=armhf
lxc.autodev=1
lxc.kmsg=0
#Network configuration
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.hwaddr = f8:7a:15:0c:f6:4f
lxc.network.script.up = /usr/local/bin/lxc_applications.sh
lxc.mount.entry = /var/lvm/data/downloads var/downloads none bind 0 0
#Autostart
lxc.start.auto = 1
lxc.start.order = 0
lxc.start.delay = 10

Возможно, для начала, секцию Autostart можно выкинуть (и включить, когда все уже настроено).

В конфигурации сети нужно указать тип сети (veth), линк (br0), и мак-адрес. Его можно сгенерить с помощью массы онлайн сервисов. Напомню, что сам IP адрес “прописан” в самом контейнере - как на “настоящей” системе.

Еще присутствует некий скрипт, который будет выполняться на хосте при старте контейнера - но об этом во второй части заметки, равно как и подключение “внешней” директории через директиву lxc.mount.entry.

Запуск контейнера

Для первого старта:

lxc-start -n applications

Если всё хорошо - мы увидим стартующие сервисы и сможем попасть внутрь контейнера, используя пароль root.

Если что-то пошло не так - проверяем, что мак-адрес валиден (можно просто перегенерить) и что мы действительно установили пароль. Ну или пользуемся lxc-attach, если ядро позволяет.

В LXC 1.0 - контейнер по-умолчанию стартует в foreground mode, в 2.0 - в background.

Чтобы запустить 1.0 контейнер в background mode, нужно использовать опцию “-d” команды lxc-start.

Итак, если все хорошо, то будет доступна сеть и можно ставить нужные приложения!

To Be Continued…

Окончание этой санта-барбары


В массы

В трубу