Создание LXC-контейнеров с общей файловой базой

    Применение легковесных контейнеров LXC в настоящий момент довольно ограничено главным образом по причине их «сырости». Применять их в production – удел настоящих джедаев. Под production в данном случае подразумевается непосредственное предоставление услуг клиентам.

    Однако для простого разделения сервисов и контроля ресурсов такие контейнеры вполне подходят с некоторыми допущениями. Например, мы полагаем, что root в контейнере равен root в целевой системе.

    В статье будет показано, как можно быстро создавать легковестные контейнеры на локальном диске с общими файлами без использования LVM-снапшотов.



    Кратко о сути контейнеров LXC



    LXC является средством для реализации виртуальных контейнеров в ядре Linux. По сути своей LXC – это просто набор userspace утилит, которые эксплуатируют реализованные в ядре возможности. Как таковое понятие LXC в ядре Linux отсутствует.

    Двумя основные составляющими контейнера являются пространства имен (namespaces) и контрольные группы (cgroups). Первые обеспечивают изоляцию процессов контейнера друг от друга, а вторые отвечают за ограничение ресурсов, выделенных контейнеру.

    К настоящему моменту, действующими пространствами имен являются:

    • pid – пространство имен идентификаторов процессов
    • mount – пространство имен смонтированных файловых систем
    • network – позволяет создавать изолированные сетевые стеки внутри контейнеров
    • utsname – обеспечивает изоляцию структуры utsname. В первую очередь используется для установки разных hostname
    • ipc – пространство имен SysV IPC. Разделяемая память, семафоры и очереди сообщению будут иметь разные id.
    • user – пространство имен uid/gid


    К слову сказать, последний namespace обещают окончательно допилить к версии ядра 3.9

    Этих пространств вполне достаточно, чтобы контейнеры чувствовали себя независимыми.

    Изначально все процессы в системе используют общие пространства имен. Создавая новый процесс, мы можем указать ядру склонировать нужные нам пространства имен для этого процесса. Это достигается путем указания специальных флагов CLONE_NEW* вызова clone(). Указывая этот флаг для определенного пространства имен при создании процесса, мы добиваемся того, что процесс будет создан в своем собственном пространстве имен. Именно так и работают утилиты LXC, создавая новый контейнер.

    Отделить пространство имен существующего процесса можно с помощью вызова unshare(). Целиком заменить одно пространство имен процесса на другое можно с помощью setns(), но для этого вызова требуется поддержка новых ядер (>3.0).

    Именно setns() используется для того, чтобы «впрыгнуть» в контейнер.

    Контрольные группы, как и пространтсва имен, реализованы в ядре. В пространстве пользователя их использование доступно LXC с помощью интерфейса специальной файловой системы cgroup. LXC-утилиты создают каталог в этой файловой системе с именем контейнера, а затем записывают pid процессов в файлы контрольных групп. Поэтому имя контейнера по сути есть имя контрольной группы.

    Подготавливаем систему к созданию контейнеров LXC



    Пропустим этот шаг, поскольку о нем много где рассказано. Например, здесь. Суть конфигурации заключается в сборке ядра с нужными опциями и установке userspace утилит.

    К счастью, многие ядра современных дистрибутивов уже собраны с этими опциями, поэтому пересборка скорее всего не понадобится.

    Если Вы привыкли использовать libvirt для управления виртуализацией, то есть хорошая новость – libvirt полностью поддерживает LXC. В статье о нем рассказано не будет, чтобы быть «ближе к телу».

    Создаем основу файловой системы для контейнеров



    Обычно делают так: создают некое базовое устройство LVM, а уже на его основе создают отдельные снапшоты для файловых систем каждого контейнера. Таким образом, это позволяет экономить дисковое пространство за счет того, что снапшот занимает место только на величину измененных блоков.
    Вместо lvm, как вариант, возможно, использование файловой системы поддерживающей снапшоты, например btrfs.

    Но у этого метода есть существенный недостаток: дисковые операции на запись с lvm-снапшотами крайне медленные.

    Поэтому для определенных задач можно использовать следующий способ:

    • Создаем базовый образ контейнера
    • Выделяем из него общую неизменяемую часть
    • Создаем символические ссылки из образа на эту часть
    • При создании контейнера монтируем эту часть внутрь контейнера


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

    $ mkdir -p /lxc/base
    $ mount /dev/mapper/lxc /lxc/base
    $ cat /.exclude
    /dev/*
    /mnt/*
    /tmp/*
    /proc/*
    /sys/*
    /usr/src/*
    /lxc
    $ rsync --exclude-from=/.exclude -avz / /lxc/base/
    $ DEV="/lxc/base/dev"
    $ mknod -m 666 ${DEV}/null c 1 3
    $ mknod -m 666 ${DEV}/zero c 1 5
    $ mknod -m 666 ${DEV}/random c 1 8
    $ mknod -m 666 ${DEV}/urandom c 1 9
    $ mkdir -m 755 ${DEV}/pts
    $ mkdir -m 1777 ${DEV}/shm
    $ mknod -m 666 ${DEV}/tty c 5 0
    $ mknod -m 600 ${DEV}/console c 5 1
    $ mknod -m 666 ${DEV}/full c 1 7
    $ mknod -m 600 ${DEV}/initctl p
    $ mknod -m 666 ${DEV}/ptmx c 5 2
    


    После окончания копирования, приступим к созданию неизменяемой части. Назовем ее common:

    $ cd /lxc/base
    $ mkdir common
    $ mv bin lib lib64 sbin usr common/
    $ ln -s common/bin
    $ ln -s common/sbin
    $ ln -s common/lib
    $ ln -s common/lib64
    $ ln -s common/usr
    $ chroot /lxc/base
    $ > /etc/fstab
    


    После этого удаляем start_udev из /etc/rc.sysinit, отключаем ненужные сервисы, и по своему усмотрению проводим дополнительные настройки. Убираем hostname из конфигурационных файлов, чтобы он не переопределялся при старте контейнера.

    Смонтируем файловую систему cgroup, с помощью которой будет происходить ограничение ресурсов контейнера. Данный процесс будет происходить путем создания директории с именем контейнера внутри файловой системы. Директорию будут создавать (и удалять) утилиты LXC.

    $ mount -t cgroup -o cpuset,memory,cpu,devices,net_cls none /cgroup
    


    Мы явно указываем контроллеры, которые хотим монтировать, поскольку по-умолчанию в дистрибутивах Centos6/RHEL6 монтиоруется контроллер blkio, который не поддерживает вложенные иерархии, необходимые для работы LXC. В Ubuntu/Debian с этим проблем нет.

    Также полезной может оказаться утилита cgclear из состава libcgroup, которая не просто отмонтирует контрольные группы, но и уничтожает их на уровне ядра. Это поможет предотвратить ошибку -EBUSY при повторном монтировании отдельных контроллеров.

    Теперь создадим сетевой мост, в который будут подключаться все контейнеры. Будьте внимательны, при выполнении операции пропадает сеть.

    $ brctl addbr br0
    $ brctl addif br0 eth0
    $ ifconfig eth0 0.0.0.0
    $ ifconfig br0 10.0.0.15 netmask 255.255.255.0
    $ route add default gw 10.0.0.1
    


    Все новые виртуальные интерфейсы контейнеров будут включаться в этот новый мост.

    Не забудем отразить все изменения в стартовых конфигурационных файлах дистрибутива.

    Создаем контейнер LXC



    После подготовки базового образа системы мы можем приступить непосредственно к созданию первого контейнера в системе. Назовем его просто lxc-container.

    Процедура создания контейнера включает в себя три простых этапа:

    • Создание файла fstab контейнера
    • Подготовка файловой системы контейнера
    • Создание конфигурационного файла контейнера


    Настроим fstab для нашего контейнера:

    $ cat > /lxc/lxc-container.fstab << EOF
    devpts /lxc/lxc-container/dev/pts devpts defaults 0 0
    proc /lxc/lxc-container/proc proc defaults 0 0
    sysfs /lxc/lxc-container/sys sysfs defaults 0 0
    EOF
    


    Теперь подготовим файловую систему для нашего первого контейнера lxc-container, используя ранее созданную неизменяемую часть базового образа.

    $ mkdir /lxc/lxc-container && cd /lxc/lxc-container
    $ rsync --exclude=/dev/* --exclude=/common/* -avz /lxc/base/ .
    $ mount --bind /lxc/base/dev /lxc/lxc-container/dev
    $ mount --bind /lxc/base/common /lxc/lxc-container/common
    $ mount -o remount,ro /lxc/lxc-container/common
    


    Последние две строчки не получается объединить в одну. Ну и ладно.
    Как видно, здесь выявляется главный недостаток (или главное преимущество) описываемого метода. Базовая часть файловой системы внутри контейнера – read-only.

    И наконец самое главное – конфигурационный файл контейнера. В указанном примере мы полагаем, что lxc-утилиты установлены в корень системы

    $ mkdir -p /var/lib/lxc/lxc-container
    $ cat > /var/lib/lxc/lxc-container/config << EOF
    # hostname нашего контейнера
    lxc.utsname = lxc-name0
     
    # количество псевдо tty 
    lxc.tty = 2
     
    # пути к файловой системе и fstab
    lxc.rootfs = /lxc/lxc-container
    lxc.rootfs.mount = /lxc/lxc-container
    lxc.mount = /lxc/lxc-container.fstab
     
    # настройки виртуального интерфейса
    lxc.network.type = veth
    lxc.network.name = eth0
    lxc.network.link = br0
    lxc.network.flags = up
    lxc.network.mtu = 1500
    lxc.network.ipv4 = 10.0.0.16/24
     
    # настройка прав доступа к устройствам в /dev
    lxc.cgroup.memory.limit_in_bytes = 128M
    lxc.cgroup.memory.memsw.limit_in_bytes = 256M
    lxc.cgroup.cpuset.cpus = 
    lxc.cgroup.devices.deny = a
    lxc.cgroup.devices.allow = c 1:3 rwm
    lxc.cgroup.devices.allow = c 1:5 rwm
    lxc.cgroup.devices.allow = c 5:1 rwm
    lxc.cgroup.devices.allow = c 5:0 rwm
    lxc.cgroup.devices.allow = c 4:0 rwm
    lxc.cgroup.devices.allow = c 4:1 rwm
    lxc.cgroup.devices.allow = c 1:9 rwm
    lxc.cgroup.devices.allow = c 1:8 rwm
    lxc.cgroup.devices.allow = c 136:* rwm
    lxc.cgroup.devices.allow = c 5:2 rwm
    lxc.cgroup.devices.allow = c 254:0 rwm
    EOF
    


    Обратите внимание, мы запретили доступ ко всем устройствам, кроме явно указанных, а также ограничили память и своп. Несмотря на это действующее ограничение утилиты free, top внутри контейнера будут показывать полную физическую память.

    Не забудем отразить все изменения в стартовых конфигурационных файлах дистрибутива, иначе они все потеряются после перезагрузки!

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



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

    $ lxc-start --name lxc-container
    


    Подключаемся к контейнеру LXC



    В LXC существует проблема с прыжком в контейнер из физического сервера.

    lxc-attach, предназначенная для этого, работает только с патченным ядром. Патчи реализуют функционал для определенных пространств имен (а именно, mount-namespace и pid-namespace). Сами патчи можно скачать по ссылке.

    Функционал прыжка реализуется специальным системным вызовом setns(), который привязывает сторонний процесс к существующим пространствам имен.

    Заменить прыжок в контейнер может lxc-console, которая подключается к одной из виртуальных консолей контейнера

    $ lxc-console --name lxc-container -t 2
    


    И перед нами консоль контейнера /dev/tty2

    CentOS release 6.3 (Final)
    Kernel 2.6.32 on an x86_64
    
    lxc-container login: root 
    Password:
    Last login: Fri Nov 23 14:28:43 on tty2
    $ hostname
    lxc-container
    $ tty
    /dev/tty2
    $ ls -l /dev/tty2
    crw--w---- 1 root tty 136, 3 Nov 26 14:25 /dev/tty2
    


    Устройство /dev/tty2 имеет major-номер 136, и не является «настоящим tty». Это устройство обслуживается драйвером псевдотерминала, мастер которого, читается на физическом сервере, а слейв – на контейнере. То есть наш /dev/tty2 является обычным устройством /dev/pts/3

    И, конечно, можно подключаться по ssh:

    $ ssh root@lxc-container
    


    Эксплуатация LXC



    Это очень интересная, но отдельна тема обсуждения. Здесь можно отметить, что часть задач по администрированию контейнеров берут на себя утилиты LXC, но можно вполне обойтись и без них. Так, например, можно посмотреть список процессов в системе с разбиением на контейнеры:

    $ ps ax -o pid,command,cgroup
    


    cgroup в данном случае совпадает с именем контейнера

    Комментарии 16

      0
      lxc-console лочит lvm тома до перезагрузки, кстати.
        0
        Применение легковесных контейнеров LXC в настоящий момент довольно ограничено главным образом по причине их «сырости».

        Я не админ и навыки у меня так себе, но у меня уже больше 6 месяцев работает сервер с lxc контейнерами и стабильно.
        В сервере стоит диск на 3ТБ, есть 8Гб памяти и 2 lxc машины, одна для TimeMachine (afp 3.0), вторая для Torrent'ов и файлопомойки (sbm, transmission-bt).
        root разделы создаются обычным способом, IP адреса берутся из DHCP роута, место для бекапов и файлов создается из образов qemu-img и подключенные через mount к lxc, а сами образы лежат на 3ТБ.
        Все работает шустро, по гигабитной локальной сети скорость бекапа TimeMachine до ~50мб/сек.

        До этого работало все с Proxmox, он конечно клевый, но слишком сложный для таких простых задач.
          0
          Ещё вроде как Heroku использует LXC во всю. Но они по сути и есть джедаи.
            0
            У меня похожая конфигурация — домашний мини-сервер с 6 Гб памяти (набрано с предыдущих ноутбуков), 2 диска по 2Тб, одна lxc машина выделена под торрент-клиент, 2 lxc машины с инсталляциями SAP ERP 6 для тренировки на кошках ;-), на хосте через NFS+SMB раздаются файлы для ноутбуков и медиа-плейера. Прочие решения для виртуализации (Proxmox, KVM и т.д.) не подошли, так как бегает это все на MiniITX с Intel Atom на борту, без поддержки апп. виртуализации. Построено все на Oracle Linux 6.4.
              +1
              О! Кто-то его использует!

              Мне вот на работе попался древний Dell PowerEdge 2650, на который наставлено 12 Гб памяти (больше в него не влезет), стоит Gentoo и в нём Virtualbox с вебинтерфейсом (т.к. процы Xeon MP не знают, что бывает аппаратная виртуализация) и LXC. Всё это успешно работает в течение вот уже двух лет.

              Что удивительно — на этой машине схема сетевого подключения сложная: LACP объединяет две сетевухи, дальше bond0 нарезается на VLANы и уже виланы связываются с виртуалками, причём модуль bridge вообще не используется — либо подвязывается виртуалбоксовый сетевой модуль, либо macvlan от lxc. И всё это работает!
              0
              А в чем сложность проксмокса? поставил за пять минут и управляешь через веб интерфейс для тупых юзеров.
                0
                Я не админ и навыки у меня так себе, но у меня уже больше 6 месяцев работает сервер с lxc контейнерами и стабильно.

                Ну это не показатель, а частный случай. Docker, к примеру не рекомендует использовать LXC с ядрами ниже 3.8 ввиду нестабильной работы.
                  0
                  У меня с Ubuntu 8.04 уже начались эксперименты с LXC.
                  Практически с начала живу в «контейнерах». Даже десктоп в контейнере, хотя были некоторые неудобства, но постепенно все улучшается, что даже никакого дискомфорта.

                  А легкие контейнеры использую уже давно, правда из-за соображений безопасности у меня отдельный образ, который монтируется через aufs.

                  За много лет накопил знаний и граблей.
                  0
                  Одно время была мысль для той же цели использовать aufs. Интересно, это еще хуже, чем снапшоты lvm, или хорошая идея?
                    0
                    docker.io/ как раз aufs использует. Вышло неплохо.
                    0
                    Очень забавная особенность
                    Например, мы полагаем, что root в контейнере равен root в целевой системе.
                      0
                      Тоже не совсем понятно в чем профит от использования root целевой системы в контейнере.
                      Можете прокомментировать?
                        0
                        Речь о безопасности. При проектировании системы создается аксиома, что если у пользователя есть root доступ к контейнеру, то значит у него есть доступ и ко всей системе. И давая кому-то root доступ в определенный контейнер следует это иметь в виду.
                          0
                          Тоже не совсем понятно в чем профит от использования root целевой системы в контейнере.

                          Так как начал копаться в LXC, добавлю от себя:
                          root в контейнере имеет очень много прав в LXC. К примеру он может:
                          — перезагрузить хост. В Убунту это защищается Apparmor, а в Debian последней — нет.
                          — изменить время на хосте (Debian) и, соответственно, на всех контейнерах.
                          — читать логи ядра хоста и, соответствено, всех контейнеров
                          0
                          $ mount -t cgroup -o cpuset,memory,cpu,devices,net_cls none /cgroup
                          lxc.cgroup.memory.limit_in_bytes = 128M
                          lxc.cgroup.memory.memsw.limit_in_bytes = 256M

                          Автор, а у Вас работает ограничение памяти? Вроде пишут что для этого нужно загрузить ядро с параметрами cgroup_enable=memory swapaccount=1? Или в CenOS иначе.
                            +1
                            Стефан Грабер (Stéphane Graber), в предверии выхода 20 февраля 2014 года релиза LXC 1.0, опубликовал цикл статей о Linux Containers.
                            Рассмотрены:
                            * Первый Ubuntu контейнер.
                            * Второй контейнер.
                            * Продвинутое использование контейнера.
                            * Более углублённое использование контейнера.
                            * Хранилище контейнеров.
                            * Безопасность.
                            * Непривилегированные контейнеры.
                            * Скрипты и API.
                            * GUI в контейнере.
                            * Решение проблем и отладка.
                            Оригинал www.stgraber.org/2013/12/20/lxc-1-0-blog-post-series/
                            Перевод vasilisc.com/lxc-1-0-blog-post-series

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое