Пришло время научиться работать с Linux Containers, более известными под названием LXC. Далее мы будем рассматривать LXC в контексте Debian и Ubuntu, но написанное во многом будет применимо и для других дистрибутивов Linux. Мы коснемся не только основ использования LXC, но и узнаем, как настроить bridged сеть, ограничить количество ресурсов, используемых контейнером, и даже осилим unprivileged containers.

Коротко о главном

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.

Дополнение: Также вас могут заинтересовать статьи Мой первый опыт использования Proxmox VE и Управление VirtualBox из консоли с помощью vboxmanage .

EnglishRussianUkrainian