0

lxc

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

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.

Облачная платформа

Свежие комментарии

Подписка

Лучшие статьи

Рубрики

Популярное

ssh-tunnel
Previous Story

SSH – тоннели

docker
Next Story

Шпаргалка по командам Docker

Latest from Blog

How to set up WireGuard Client on Debian?

WireGuard is an extremely simple yet fast and modern VPN. Setting up the WireGuard VPN client on Debian is straightforward. In this tutorial, we will set up WireGuard VPN client on Debian

Перенос БД из MariaDB/MySQL в PostgreSQL с помощью pgLoader

Краткая инструкция об очень интересном и полезном инструменте pgLoader, который позволяет легко мигрировать базу данных MariaDB или MySQL в PostgreSQL. Для начала нам понадобится развернуть небольшой стенд, к примеру, на Debian GNU/Linux,
Go toTop