Коротко о главном
LXC — технология контейнерной виртуализации, как и OpenVZ. Накладные расходы на создание контейнеров очень невелики, и ничто не мешает нам запускать их сотнями или тысячами. Что невозможно при использовании честной и полноценной виртуализации, такой, как KVM. С другой стороны, запустить в контейнере ОС, отличную от Linux, мы не можем. На самом деле, не совсем корректно называть LXC отдельной технологией. Все работает сразу на нескольких фичах ядра Linux. Как минимум, это cgroups и namespaces.
Control Groups, они же cgroups, позволяют организовывать процессы в группы и для каждой группы задавать лимиты. Например, лимиты на использование CPU, объем используемой памяти и дисковый ввод-вывод.
Namespaces бывают разные — PID namespace, IPC namespace, UTS namespace, user namespace, mount namespace, network namespace. Неймспейсы изолируют друг от другая процессы таким образом, что процессы в одной группе не могут видеть ресурсы другой группы. Например, PID namespace предоставляет уникальные идентификаторы процессов в рамках группы. Внутри одной группы может быть процесс с pid’ом 1 и внутри второй группы тоже может быть процесс с pid’ом 1, хотя это два совершенно разных процесса, которые друг о другие ничего не знают. Притом, все процессы все также имеют уникальные id в рамках ОС. Просто, если смотреть на процессы из группы, то эти id отображаются в другие.
В отличие от Docker, который заточен на создание PaaS, в LXC нет никаких слоеных неизменяемых файловых систем. Контейнеры очень похожи на VDS’ы, как и в OpenVZ. Но в отличие от OpenVZ, в LXC далеко не все есть из коробки. Как мы скоро убедимся, для ограничения места на диске, используемого контейнером, приходится прибегать к дополнительным ухищрениям. Повторюсь, связано это с тем, что LXC не совсем одна полноценная технология. С другой стороны, LXC не нуждается в пропатченном ядре. Все необходимое уже и так есть в ванильном ядре Linux. Как следствие, LXC превосходно работает на самой обычной Ubuntu, чем OpenVZ похвастаться не может.
Примечание: Кстати, в заметке Начало работы с Vagrant и зачем он вообще нужен рассказывается, как работать с LXC через Vagrant. Использование LXC через Vagrant кому-то может показаться удобнее, чем работа с LXC напрямую.
Установка
Как было отмечено выше, все необходимое уже есть в ядре. Тем не менее, для хождения в ядро понадобятся некоторые дополнительные утилиты. Также не повредит обновить до последней версии кое-какие, казалось бы, не особо связанные с LXC пакеты.
Итак:
sudo apt-get update
sudo apt-get install lxc lxc-templates systemd-services cgroup-bin \
bridge-utils debootstrap
Очень важно реально сделать update, чтобы установились наиболее свежие версии указанных пакетов. И, не поверите, но реально лучше сразу сделать reboot
. Иначе, работая с LXC, вы в какой-то момент можете получить странную ошибку вроде такой:
call to cgmanager_create_sync failed: invalid request
… и будете потом через Google выяснять, что же пошло не так.
Свои данные LXC хранит в следующих местах:
- /var/lib/lxc — тут лежат контейнеры, их настройки, ФС и так далее;
- /etc/lxc — прочие настройки;
- /etc/default/lxc-net — тут можно поменять настройки сети;
- /var/cache/lxc — кэш шаблонов;
- /var/lib/lxcsnaps — сюда пишутся снапшоты;
- /var/log/lxc — логи;
Теперь рассмотрим команды для управления контейнерами.
Основные команды
Создание контейнера с именем test-container из шаблона Ubuntu:
sudo lxc-create -n test-container -t ubuntu
Увидим что-то вроде:
##
# The default user is 'ubuntu' with password 'ubuntu'!
# Use the 'sudo' command to run tasks as root in the container.
##
Посмотреть список доступных шаблонов можно так:
ls /usr/share/lxc/templates/
Список скачанных шаблонов:
sudo ls /var/cache/lxc/
Удалить шаблон можно просто удалив соответствующий ему каталог.
Запуск контейнера в бэкграунде:
sudo lxc-start -d -n test-container
Для отладки — запуск с логированием:
sudo lxc-start -l debug -o 1.log -d -n test-container
Список контейнеров:
sudo lxc-ls -f
Подробная информация о контейнере:
sudo lxc-info -n test-container
Заходим в контейнер:
sudo lxc-console -n test-container
В нашем случае логин и пароль — ubuntu. Для выхода из контейнера жмем Ctr+A, затем Q.
Остановка контейнера:
sudo lxc-stop -n test-container
Создание клона контейнера (test-container должен быть остановлен):
sudo lxc-clone -o test-container -n test-container-clone
Удалить контейнер:
sudo lxc-destroy -n test-container
Заморозить/разморозить контейнер:
sudo lxc-freeze -n test-container
sudo lxc-unfreeze -n test-container
Создать снапшот (контейнер должен быть остановлен):
sudo lxc-snapshot -n test-container
Список снапшотов:
sudo lxc-snapshot -n test-container -L
Восстановление из снапшота:
sudo lxc-snapshot -n test-container -r snap0
Если нужно пошарить каталог, проще всего сделать это при помощи sshfs:
sshfs ubuntu@10.110.0.10: ./shared-dir
Пока ничего сложного.
Автозапуск
Чтобы контейнер запускался при старте системы, в конфиг контейнера (в нашем случае это файл /var/lib/lxc/test-container/config) дописываем:
lxc.start.auto = 1 # enabled
lxc.start.delay = 15 # delay in seconds
lxc.start.order = 50 # higher value means starts earlier
Если все было сделано верно, команда sudo lxc-ls -f
покажет YES в колонке autostart.
Ограничение на использование ресурсов
Попробуем ограничить количество памяти, которое может отъедать контейнер.
Останавливаем контейнер:
sudo lxc-stop -n test-container
Затем правим /var/lib/lxc/test-container/config:
lxc.cgroup.memory.limit_in_bytes = 256M
Говорим:
sudo lxc-start -d -n test-container -l debug -o test.log
cat test.log | grep -i memory
Должны увидеть что-то вроде:
lxc-start 1449237148.829 DEBUG lxc_cgmanager - cgmanager.c:
cgm_setup_limits:1385 - cgroup 'memory.limit_in_bytes' set to '256M'
Проверяем, что настройки применились:
sudo lxc-cgroup -n test-container memory.limit_in_bytes
Можно менять лимиты на лету, но они потеряются с рестартом:
sudo lxc-cgroup -n test-container memory.limit_in_bytes 100M
Также можно следить, сколько ресурсов потребляет контейнер прямо сейчас:
cat /sys/fs/cgroup/memory/lxc/test-container/memory.usage_in_bytes
Вот еще интересные параметры:
- cpu.shares — сколько единиц процессорного времени отдавать контейнеру, если у одного 2000 единиц, а у второго 1000, второй получит в два раза меньше времени;
- cpuset.cpus — какие ядра может использовать контейнер, номера начиная с нуля, например
0,1
или0-3
; - memory.memsw.limit_in_bytes — ограничение на память и своп в сумме, если своп вообще есть;
- blkio.weight — как cpu.shared, только про дисковый ввод-вывод;
Тут больше параметров — есть, к примеру, про сеть.
Ограничение на использование дискового пространства
Эту задачу можно решить несколькими способами. Например, многие советуют использовать LVM. И хотя LVM, бесспорно, крутая штука, использовать его для решения такой простой задачи мне лично кажется оверкилом. Предлагаю пойти более простым путем.
Допустим, мы хотим создать контейнер с именем new-container, который будет занимать на диске не более 10 Гб.
sudo truncate -s 10G /var/lib/lxc/new-container.img
sudo mkfs.ext4 -F /var/lib/lxc/new-container.img
sudo mkdir /var/lib/lxc/new-container
sudo mount -t ext4 -o loop /var/lib/lxc/new-container.img \
/var/lib/lxc/new-container
Теперь создаем контейнер, как обычно:
sudo lxc-create -t ubuntu -n new-container
Заходим внутрь контейнера, и через df
видим, что отъесть от диска он может только 10 Гб.
Чтобы не приходилось каждый раз монтировать раздел руками, в /etc/fstab дописываем:
/var/lib/lxc/new-container.img /var/lib/lxc/new-container ext4 loop 0 0
Вы можете заметить, что количество loop-устройств в системе ограничено. В Ubuntu, например, их по умолчанию только 8. Если попытаться создать много контейнеров описанным выше образом, вы получите ошибку:
mount: could not find any free loop device
Эту проблему можно решить следующим скриптом:
#!/bin/sh
for i in $(seq 0 64); do
[ -b /dev/loop$i ] || (mknod -m 660 /dev/loop$i b 7 $i && \
chown root:disk /dev/loop$i)
done
Чтобы loop-устройства создавались при загрузке системы, создаем файл /etc/init.d/more-loop-devices следующего содержания:
#!/bin/bash
### BEGIN INIT INFO
# Provides: more-loop-devices
# Required-Start: $syslog
# Required-Stop: $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: More Loop Devices
# Description: More Loop Devices
### END INIT INFO
PATH=/sbin:/usr/sbin:/bin:/usr/bin
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
. /lib/lsb/init-functions
case "$1" in
start)
for i in $(seq 0 64); do
[ -b /dev/loop$i ] || (mknod -m 660 /dev/loop$i b 7 $i && \
chown root:disk /dev/loop$i)
done
;;
*)
echo "Usage: more-loop-devices start" >&2
exit 1
;;
esac
Говорим:
sudo chmod a+x /etc/init.d/more-loop-devices
sudo update-rc.d more-loop-devices defaults
В этот же скрипт при необходимости можно прописать и монтирование. Честно говоря, мне пока не нужно было так много контейнеров. Поэтому я не проверял, действительно ли в этом есть необходимость, или же достаточно записи в fstab.
Настройка bridged сети
Ранее в заметке Контейнерная виртуализация при помощи OpenVZ мы научились настраивать bridged сеть под CentOS. Настало время узнать, как то же самое делается в Debian и Ubuntu.
Правим /etc/network/interfaces — часть про eth0 убираем, вместо нее пишем:
auto eth0
iface eth0 inet manual
Далее, если у вас DHCP:
auto br0
iface br0 inet dhcp
bridge_ports eth0
bridge_stp off
bridge_fd 0
bridge_maxwait 0
Если же у вас используются статические адреса:
auto br0
iface br0 inet static
address 192.168.0.123
network 192.168.0.0
netmask 255.255.255.0
broadcast 192.168.0.255
gateway 192.168.0.1
bridge_ports eth0
bridge_stp off
bridge_fd 0
bridge_maxwait 0
Перезагружаемся.
Если все было сделано правильно, в ifconfig’е увидим интерфейс br0.
Далее останавливаем контейнер и правим /var/lib/lxc/(container)/config:
lxc.network.link = br0
Если хотим присвоить контейнеру статический IP, также дописываем:
lxc.network.ipv4.gateway = 192.168.0.1
lxc.network.hwaddr = 00:16:3e:6b:c7:5b
lxc.network.ipv4 = 192.168.0.124/24
Запускаем контейнер. Если все было сделано правильно, в контейнер можно будет ходить из локалки и sudo lxc-ls -f
покажет у него соответствующий IP.
Резервное копирование и восстановление
Как уже отмечалось, все лежит в /var/lib/lxc. Поэтому просто нужно аккуратно заархивировать все данные оттуда. Тут каждый использует, что ему больше нравится. Для примера рассмотрим решение задачи при помощи tar.
Останавливаем контейнер, под рутом делаем:
cd /var/lib/lxc/test-container
tar -cvzf backup.tgz ./
Резервная копия готова! Для восстановления же говорим:
mkdir -p /var/lib/lxc/test-container
cd /var/lib/lxc/test-container
scp example.ru:path/to/backup.tgz ./
tar -xvzf backup.tgz
rm backup.tgz
Теперь контейнер виден в lxc-ls
и может быть запущен через lxc-start
.
Если описанным образом вы создаете копию контейнера (вам чем-то не подошел lxc-clone) или при восстановлении контейнера решили его переименовать, придется чуть-чуть подправить файл config. Там просто нужно поменять пути и изменить имя контейнера, плюс я бы сменил на всякий случай MAC.
В общем-то, это все. Ну разве что у tar’а еще флаг --numeric-owner
рекомендуют использовать.
Если же вы ограничиваете место на диске, которое может занимать контейнер, как это было описано выше, то резервное копирование и восстановление делается еще проще.
Непривилегированные контейнеры
Оказывается, что контейнерами можно управлять и под самыми обыкновенными пользователями, не под рутом. Как минимум, это намного безопаснее.
При этом каталоги немного меняются:
- ~/.local/share/lxc — тут лежат контейнеры, их настройки, ФС и так далее;
- ~/.config/lxc — прочие настройки;
- ~/.cache/lxc — кэш шаблонов;
- ~/.local/share/lxcsnaps — сюда пишутся снапшоты;
Первым делом говорим:
sudo usermod --add-subuids 100000-165536 eax
sudo usermod --add-subgids 100000-165536 eax
sudo chmod +x /home/eax
… где eax — имя вашего пользователя в системе.
Только что мы выделили 65536 uid’ов и gid’ов пользователю eax. В контейнере будут использоваться айдишники от 0 до 65535, которые будут отображаться в айдишники от 100000 до 165535 в хост-системе.
Далее:
mkdir -p ~/.config/lxc
Копируем /etc/lxc/default.conf в ~/.config/lxc/default.conf и дописываем в конце:
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
В файл /etc/lxc/lxc-usernet пишем:
# USERNAME TYPE BRIDGE COUNT
eax veth lxcbr0 100
Создаем контейнер (как видите, без sudo):
lxc-create -t download -n test-container -- -d ubuntu \
-r trusty -a amd64
Заметьте, как это работает. Мы указали тип контейнера download, которому передали в качестве аргумента название дистрибутива, релиза и архитектуры CPU. Все эти три параметра обязательны при создании непривилегированных контейнеров.
Теперь запускаем контейнер, как обычно:
lxc-start -d -n test-container
В случае возникновения проблем:
lxc-start -d -n test-container --logfile t.log --logpriority debug
Дополнение: Чтобы непривилегированные контейнеры заработали, может потребоваться перезагрузка. Если вы используете encfs, команды sudo и ping могут не работать. Это баг. Здесь рассказывается, как примерно можно его обойти. Идея в том, чтобы при помощи симлинков положить контейнеры и конфиги за пределы encfs.
Шаблоны непривилегированных контейнеров немного отличаются от тех, что мы использовали раньше. В частности, никакого SSH по умолчанию в них не крутится. Но это легко исправить. Заходим в контейнер под рутом при помощи lxc-attach:
lxc-attach -n test-container
… и говорим:
apt-get install ssh
passwd ubuntu
Ну вот, а в остальном все как обычно.
Заключение
Не могу не отметить ряд косяков в LXC:
- У LXC довольно высокий порог вхождения и вообще все как-то неудобно. Я просто хочу, чтобы контейнер не отжирал больше 1 Гб памяти. Я не хочу ничего знать ни о каких cgroups и читать документацию по ним на сайте RedHat! Про то, как приходится плясать для банального ограничения места на диске, я вообще молчу. OpenVZ в этом плане намного, НАМНОГО лучше;
- В LXC все очень сыро и плохо документировано. Я собирал представленную в этом посте информацию по крупицам, наверное, около месяца, попутно гугля по поводу багов, на которые я натыкался в процессе. Вам, уважаемые читатели, в этом плане будет намного проще;
- На мой взгляд, интерфейс продуман плохо. Например, первое время я постоянно забывал флаг
-d
уlxc-start
. Правда, потом я уже как-то то ли привык, то ли запомнил. Да и если контейнеры прописаны на автозагрузку, такой проблемы нет; - Внутри контейнера мы видим занятую и свободную память хост-системы, а не контейнера. В результате, из контейнера не ясно, кто же отожрал всю память. К тому же, некоторые программы могут работать из-за этого некорректно;
- Неизбежны накладные расходы в случае ограничения используемого места на диске;
- Плохие сообщения об ошибках. Шаг влево, шаг вправо — и тут же получаем что-то в стиле «No such file or directory — execlp»;
Тем не менее, если вы хотите запихнуть что-то в контейнер под Ubuntu, и при этом вы не пишите PaaS’ов, LXC может быть для вас неплохим вариантом. Особенно теперь, когда вы знаете о большинстве подводных граблей этой технологии.
Больше полезной информации вы найдете на сайте linuxcontainers.org, а также в списке рассылки lxc-users@. Если вас интересует, как запускать GUI-приложения под LXC, обратите внимание на заметку Осилил запуск GUI-приложений в Docker. Она без единого изменения применима и к LXC.
Свежие комментарии