Вместо вступления
Контейнерная виртуализация переживает настоящий бум именно сейчас. Увлечение 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. Смысл пользовательских контейнеров в ещё более “сильной изоляции” и безопасности, но они требуют немного больше усилий в установке.
Подготовка
Все действия производились на Cubieboard 2 под управлением Armbian и ядра 4.х.
Тестирование proof-of-concept проводилось на Андроид-стике с ядром 3.0.36 (на Linux, если что), так что решение заработает на любом ущербном китайском устройстве, даже если нет “последнего ядра”, лишь бы все нужные флаги в ядре присутствовали (поддержка cgroups, например). Ну за этим - в документацию. Там всё хорошо описано.
Для начала нам нужно будет создать ещё одну подсеть, в которой будут жить наши контейнеры. Шаги:
- Установим lxc, он “потащит” за собой нужные пакеты.
- Дальше - настроим сеть.
- Ещё нужно позаботиться о дисковом пространстве. Я использую LVM в качестве storage backend, но можно обойтись без него и файловая система “ляжет” в обычную директорию.
Сеть
Нам потребуется “поднять” bridge-интерфейс. При этом я не привязывал его к конкретному сетевому адаптеру, чтобы работало даже на устройствах, где кроме беспроводного интерфейса ничего нет.
Этот трюк почерпнут из Archlinux Wiki - дело в том, что полноценный бридж на беспроводном адаптере не создать, зато можно настроить masquerading и получить доступ к интернету из контейнера. Может оно и к лучшему.
Вот примерно так выглядит /etc/network/interfaces:
1 | #empty bridge |
Итак, сеть вида 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 | 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 | Downloading the image index |
Напомню, что весь процесс у нас происходит на процессоре ARM (CubieBoard2, Orange Pi), поэтому архитектуру мы будем использовать armhf.
Кстати, об образах - в LXC 2.0.x их немного больше и доступен Alpine Linux - отличный легковесный дистр, как будто созданный для контейниризации. LXC 2.0 доступен из бэкпортов Jessie, его можно установить вместо дефолтного 1.0. Но концепция озвученная ранее - “только дефолтные пакеты на базовой системе” и общая лень не дали мне этого сделать.
Вообще, лучше выбирать какой-нибудь известный дистрибутив. Я использвал ubunty trusty для armhf - это всё-таки LTS, хоть и старый, да и под настройку всего полно туториалов.
После ответа на вопросы начнется закачка, или, если это не первый контейнер - будет использована закэшированная копия.
После окончания процесса получаем примерно такое сообщение:
1 | You just created an Ubuntu container (release=trusty, arch=armhf, variant=default) |
Не торопитесь запуcкать контейнер. Для начала его нужно настроить - создать пароль root’а по крайней мере, да и сеть тоже не будет лишней.
Установка пароля root в контейнере
Вот тут начинается самое главное. Конечно, можно воспользоваться советом системы и запустить lxc-attach, но у меня лично на старом тестовом ядре не заработал “как надо”. Так что предлагаю смонтировать lvm в директорию, и сделать туда chroot перед запуском, чтобы установить пароль root и настроить сеть.
Вот настройка настройка сети для использования статического адреса в контейнере с Ubuntu 14.04:
1 | auto eth0 |
Команды, которые нужно выполнить для chroot в контейнер и настройки сети
1 | mount /dev/vg0/applications /mnt |
Подготовка конфигурационного файла
Вторым шагом перед стартом контейнера - его нужно донастроить. Конфигурационный файл в нашем случае - /var/lib/lxc/applications/config
1 | #Distribution configuration |
Возможно, для начала, секцию 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.
Итак, если все хорошо, то будет доступна сеть и можно ставить нужные приложения!