January 16, 2012

LXC - виртуализация без виртуализации

Таблица снизу это обзор текущих систем виртуализации с позиции того, насколько большую часть хост системы виртуальные машины используют напрямую:

                               Матрешка виртуализации

    ==========================================================================================
    Используется          Название              Примеры:
    совместно
    с хостом
    ==========================================================================================
    ничего                Эмуляция              QEMU, Boсsh
    ------------------------------------------------------------------------------------------
    CPU                   Виртуализация         KVM, VmWare, XEN, Hyper-V
    ------------------------------------------------------------------------------------------
    Аппаратура            Intel VT-d, SR-IOV    Может использоваться совместно 
                                                с системой виртуализации (kvm)
    ------------------------------------------------------------------------------------------
    Драйвера              Паравиртуализация     XEN, VirtIO, VMWare tools
    ------------------------------------------------------------------------------------------
    Ядро OS               Контейнеры            LXC, Solaris Zones, OpenVZ, Linux VServer
    ==========================================================================================

В первой графе - слои системы "компьютер + ОС" - чем ниже, тем более высокий уровень (что-то типа уровней ISO для сетевого стека ). Средняя графа - название модели виртуализации. Третья графа - типичные примеры. Чем ниже, тем больше компонентов гостевые системы используют от хост-системы напрямую, тем меньше нагрузка на гипервизор и тем выше скорость работы.

Я немного расскажу о контейнерах вообще и LXC (LinuX Containers) в частности. Контейнеры (или виртуализация уровня операционной системы)- это группы процессов, изолированные от остальной системы, возможно с наложенными ограничениями, и имеющие доступ только к некоторой части ресурсов. Процессы из контейнера "видят" и могут напрямую взаимодействовать только с процессами из того же контейнера, им доступна только часть аппаратуры, а корень файловой системы контейнера с помощью chroot сдвинут в глубь файловой системы хоста (например, в /var/lib/lxc/my_container_1). Виртуализация всех необходимых подсистем ядра (таблицы монтирования, PID, маршруты IP, etc) позволяет контейнеру выглядеть как "нормальная" виртуалка.

Какие же плюсы и минусы есть у контейнера по сравнению с "полноценной" vm? Начнем с минусов - все контейнеры и хост система делять одну копию ядра и из этого вытекают очевидные недостатки:

  • Безопасность - компрометация ОС в контейнере равноценна компрометации и хост ОС
  • Ограниченость вариантов окружения - только та же ОС, только та же версия ядра, даже запустить другой дистрибутив - может быть проблемой, поскольку часто дистрибутивы имеют модифицированные версии ядра, а родные утилиты и glibc могут полагаться на наличие в ядре определенных изменений.
  • Поддержка миграции, точек восстановления и сброса состояния на диск отсутствует в некоторых контейнерах, поскольку требует совсем другого типа взаимодействия с приложениями.

Ко всему этому LXC добавляет проблемы с user id. root в контейнере имеет UID == 0, т.е. совпадает с root в хост системе. В некоторых случаях это создает проблемы безопасности, о чем я скажу еще раз ниже.

Теперь о плюсах:

  • Не нужна аппаратная поддержка виртуализации (вообще никакая). Т.е. все это работает "из коробки" на любом процессоре ( а объем работы для портирования кода на новую архитектуру несравнимо меньше объема работы по портированию туда-же гипервизора типа KVM/XEN, KVM вот уже тянут на ARM 3 года). В частности это означает, что контейнеры работаю внутри виртуальных машин. Все, кто хотели попробовать Linux виртуализацию, но не хотели ставить Linux "на голое железо" - LXC это для вас; LXC будет работать в Linux'е, установленном в vmWare.
  • Возможна вложенная виртуализация. Даже, например, LXC в КVM/XEN в LXC и т.д. (если процессор "умеет" KVM). Главное, что нативная виртуализация не должна встречаться более одного раза (если она не поддерживает вложенность).
  • Быстрый (доли секунды) запуск. Фактически запускаются только необходимые для работы контейнера приложения. Ядро, драйвера, инициализация железа, etc - это все не нужно. Так же быстро он и гасится.
  • Нет потерь производительности CPU и дисковых операций - почти все, что доступно в контейнере, работает со скоростью хост системы. Несмотря на некоторые потери по скорости в сетевом стеке, связанные с избыточным копированием данных и невозможностью tcp offload для veth, в целом все несравнимо лучше, чем в xen/kvm (правда из тестов не ясно - использовалась ли там virtio, по идее kvm c ним не должен так сильно проседать по скорости). Где-то там в ядре есть, конечно, какие-то проверки или индексации массивов по номеру контейнера, но это все мелочь. То-же относится и к вложенным контейнерам.
  • Не нужно выделять оперативную память под ядро ОС и всё прилагающееся (видеопамять, дисковые буферы, etc). Из "лишних" потребителей остаются init и другие стартовые сервисы.
  • Нет дополнительных виртуальных устройств(таймеры, монитор, другое), вызывающих постоянное пробуждение гостевых драйверов, съедающих несколько процентов CPU на пустом месте.
  • Поддерживается запуск в контейнере отдельных приложений, а не полной системы - "chroot на стероидах" (по меньшей мере это умеет LXC).

По итогу контейнеры позволяют поднять на весьма стандартном десктопе несколько сотен виртуальных машин c ssh и apache, сохраняя достаточную производительность.

Чем же особенно интересен LXC? Как можно понять из приведенного описания для реализации контейнера нужно внести в ядро OC очень значительные изменения. Именно поэтому долгое время в базовом ядре (vanilla kernel) контейнеров не было. И это не смотря на то, что OpenVZ/VServer появились не менее 6 лет назад и очень широко использовались (и использутся) VPS провайдерами. Все они, хотя и являются на сегодня более полноценной реализацией контейнеров чем LXC, не используют другие подсистемы ядра, а изобретают свои велосипеды (хотя благодаря LXC ситуация и у них улучшается). Видимо именно высокий уровень повторного использования кода привел к тому что LXC находится в основной ветке ядра уже достаточно давно (2.6.29+).

LXC является относительно небольшой надстройкой над cgroups, namespaces и capabilities. В итоге процессы из контейнера доступны для управления из хост системы всеми стандартными средствами и утилитами. С пользовательской позиции LXC несравнимо удобнее интеграцией в основное ядро линукс и (как результат) доступностью "из коробки" во всех дистрибутивах и(почти) всех ядрах без мороки с патчами, компиляцией и перезагрузками.

Из минусов конткретно LXC - его нужно аккуратно настраивать, иначе в него могут попасть "лишние" устройства и тогда он "распорядится" ими по своему усмотрению. Остались еще проблемы в безопасности, связанные с тем, что не все участки ядра Linux переведены на capabilities, и местами остались сравнения вида 0 == uid. Из-за этого криво настроенный контейнер при старте может погасить XServer или выключить звук (udev постарается). Так-же есть проблема безопасности с sysfs. Из некритичного - dmesg общий с хостом и прочие мелочи. Также иногда разобраться "чего ему надо" требует больше времени, чем следовало бы.

Но все это временное - LXC серьезно продвигается Canonical, уже есть поддержка в openstack, на сайтах всех основных дистрибутивов есть примеры по созданию контейнеров.

Немного про технологии, лежащие в основе LXC. cgroups позволяет ограничить ресурсы, доступные процессу - процессорные ядра, процессорное время, ОЗУ, нагрузку на сеть, диски и другое; namespaces виртуализирует системные ресурсы - таблицу монтирования, PID, средства межпроцессного взаимодействия, сетевые интерфейсы, таблицы маршрутизации и прочее; capabilities - это система ролевого доступа, которая позволяет разделить абсолютные административные привилегии пользователя root на отдельные части (например: право использовать RAW сокеты, право загружать модули и т.д. ) и выдавать только те права, которые реально нужно приложению. Все эти возможности/ограничения наследуются дочерними процессами, а доступные через cgroups ресурсы делятся между ними.


Практика

О создании контейнеров можно подробно прочитать в сети, отдельно стоит отметить эту статью - она будет особенно полезна windows пользователям, остальные могут смело пролистывать процесс создания Linux VM в VirtualBox. Ссылки из сети - 1, 2, debian, 3, запуск X, ubuntu. Так что подробно останавливаться на установке не буду (все примеры для ubuntu 11.10):

    # oneiric.cfg
    lxc.utsname = test
    lxc.network.type = veth
    lxc.network.flags = up
    lxc.network.link = virbr0 # воспользуемся default сетью из libvirt
                              # для этого libvirt должна быть установлена
                              # см посты "облако на коленке" 
    lxc.network.hwaddr =  00:44:01:61:78:22
    lxc.network.ipv4 = 192.168.122.190/24
    lxc.network.name = test_vm
Без подсветки синтаксиса
# # lxc-create использует debootstrap для создания минималистического
# # образа ОС в /var/lib/lxc/test/rootfs

# lxc-create -n test -f oneiric.cfg -t ubuntu -- --release oneiric 
debootstrap is /usr/sbin/debootstrap
Checking cache download in /var/cache/lxc/oneiric/rootfs-amd64 ... 
Copy /var/cache/lxc/oneiric/rootfs-amd64 to /var/lib/lxc/test/rootfs ... 
Copying rootfs to /var/lib/lxc/test/rootfs ...Please change root-password !
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  lxcguest 
# # ........
Setting up lxcguest (0.7.5-0ubuntu8) ...
'ubuntu' template installed
'test' created

# ls /var/lib/lxc/test/rootfs/
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  selinux  srv  sys  tmp  usr  var

Мы получили в /var/lib/lxc/test/rootfs корень файловой системы нашего контейнера. В принципе можно сделать туда chroot и провести все приготовления для старта. lxc-create по умолчанию помещает все контейнеры в /var/lib/lxc, из соображений удобства примеров мы их там и оставим.

Также в /var/lib/lxc/test есть два полезных файла - config и fstab. Первый содержит конфигурацию контейнера, а второй - описание точек монтирования. Часть конфурации скопирована из oneiric.cfg, а часть заполнена lxc-create параметрами по умолчанию:

Показать код

Отдельно интересна группа с пробросом устройств - модифицируя ее можно выдавать контейнеры разнообразные возможности. Например для того чтобы разрешить использование kvm из контейнера нужно "пропустить" в него /dev/kvm

Без подсветки синтаксиса
# ls -l /dev/kvm
crw-rw----+ 1 root kvm 10, 232 2012-01-11 21:14 /dev/kvm

# #/dev/kvm это символьное устройство с id (10, 232)
# mknod /var/lib/lxc/test/rootfs/dev/kvm c 10 232

Теперь дописываем в config строку

    lxc.cgroup.devices.allow = c 10:232 rwm

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

Не забываем убрать из контейнера запуск udev. Устройства в нем будут управляться хостовым udev демоном, а локальный будет только пакостить. В ubuntu для этого делаем rm %CONTAINER_ROOT%/etc/init/udev.conf (привет, upstart).

Запускаем контейнер и смотрим, что вышло:

Показать код

В итоге мы получили изолированную виртуальную машину. Ради интереса - по информации от free весь контейнер вместе с залогиненным ssh клиентом занимает "аж" 4М ОЗУ. Останавливается контейнер через 'lxc-stop'/'lxc-destroy'.


Хранение образов

О файловой системе: LXC критикуют за отсутствие дисковых квот для контейнеров, но эта проблема легко решается с помощью LVM, а возможность overcommita обеспечивает thin provisioning, который появился в 3.2 ядре. А вот возможность копирования при записи, которая очень полезна для VM, требует дополнительных телодвижений. Хочется то же, что qcow2 формат дает виртуальным машинам kvm/xen - вместо полного образа на каждую VM - один большой базовый образ с установленной системой и множество маленьких diff'ов, по одному на виртуалку.

  • использовать qcow2 через qemu-nbd
  • модифицируемые снимки а btrfs
  • модифицируемые снимки в LVM2
  • наслаиваемые файловые системы - aufs

Первый вариант достаточно простой для тех, кто уже использовал qcow2 в kvm/xen, но неудобен по производительности. Второй и третий вариант похожи - и lvm2 и btrfs умеют создавать модифицируемые снимки, описывающие состояние файловой системы на некоторый момент времени. На каждую виртуальную машину можно делать отдельный снимок с базового образа. При этом все изменения будут записываться в снимок, а оригинальный образ модифицироваться не будет. В случае с LVM также можно модифицировать оригинальный образ, не затрагивая снимки.

Без подсветки синтаксиса
# lvcreate -s -n disk-snapshot /dev/vg0/disk -L 5G

Эта команда создает снимок /dev/vg0/disk-snapshot с LVM раздела /dev/vg0/disk, где 5Гб места зарезервировано под хранение измененных, по отношению к /dev/vg0/disk, блоков. В дальнейшем количество свободного месте можно изменить командой lvextend. Если на /dev/vg0/disk была установлена система, то, после монтирования, /dev/vg0/disk-snapshot может использоваться для старта виртуалки.

Еще один вариант - наслаиваемые файловые системы - aufs и другие, я рассмотрю только aufs. Она позволяет примонтировать в одну точку несколько файловых систем, называемых ветками. В отличии от стандартного поведения, когда смонтированная позднее файловая система полностью закрывает смонтированную ранее, aufs позволяет "видеть" нижние ветки, если файлы из них не были перекрыты файлами с такими-же именами в более поздних ветках. При этом чтение будет производиться из самой верхней ветки, имеющей данный файл, а запись - в зависимости от настроек при монтировании. Если ветка, в которой найден необходимый файл, защищена от записи, то файл будет скопирован в первую вверх ветвь, в которую можно писать. Фактически это cow на уровне файлов, а не блоков. Из очевидные минусов - даже небольшие изменения объемного файла приведут к его полному дублированию.

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

Без подсветки синтаксиса
# mkdir /tmp/rw
# mount -t aufs -o br=/tmp/rw=rw:/home/user=ro none /tmp/aufs

Эта команда монтирует папки /home/user и /tmp/rw в папку /tmp/aufs. /tmp/user монтируется в режиме "только для чтения", так в /tmp/aufs будет видна домашняя папка, но все изменения будут попадать в /tmp/rw. P.S. aufs не включена в основную ветку ядра, и модулей для 3.1 в ubuntu еще нет, так что пользователям последней ubuntu этот вариант попробовать не удастся.


libvirt

Формально libvirt поддерживает LXC, но работоспособность этого решения и полнота поддержки оставляет желать лучшего. Libvirt не использует стандартные утилиты LXC, а создает контейнеры самостоятельно. В итоге на некоторых комбинациях ядер/libvirt запущенные контейнеры оказываются полностью не работоспособными (хотя lxc-start работает "на ура") Нашел у себя ошибку, так что остался без примера:

    #/var/log/libvirt/lxc/test.log
    ......
    01:48:38.544: 28327: error : lxcFdForward:289 : read of fd 9 failed: Input/output error
    ... и тут еще сотни тысяч таких записей .......

Фикс для этой ошибки вроде как был внесен еще год назад, но она раз за разом проявляется снова. Также контейнер, запущенный с помощью lxc-start при попытке последующих запусков из libvirt вообще не создает сетевые интерфейсы. libvirt полуофициально не поддерживает старт с /sbin/init - необходимо использовать собственные скрипты; нет поддержки нормального проброса устройств, установки адреса и параметров на сетевой адаптер и др. Все это, естественно, следует читать как "у меня не получилось", хотя масса не отвеченных вопросов говорит, что не только у меня.

Пример конфигурации для запуска lxc из libvirt:

Показать код

Записываем это в %CONTAINER_ROOT%/etc/init/mylxc.conf:

    # %CONTAINER_ROOT%/etc/init/mylxc.conf
    description     "startup ifconfig"
    start on filesystem or runlevel [2345]

    pre-start script
        # стартовый ip для адаптеров
        now_ip=192.168.122.190
        all_links=`ip link | grep '^[0-9][0-9]*:' | awk -F: '{print $2}' | grep -v lo`

        for eth in $all_links ; do
            ifconfig $eth $now_ip/24 up
            now_ip=`echo $now_ip | awk -F. '{print $1 "." $2  "." $3 "." $4 + 1}'` 
        done

    end script
    exec ps

Запуск контейнера через virsh:

Показать код

libvirt позволяет удобнее програмно контролировать контейнеры, чем lxc-xxx.

Без подсветки синтаксиса
#!/bin/env python
# -*- coding:utf8 -*-

import time
import socket
import libvirt

c = libvirt.open("lxc:///")
dom = open("lxc.xml").read()

t = time.time()
c.createXML(dom, 0)
print "Time 1", time.time() - t
try:
    while True:
        try:
            socket.socket().connect(("192.168.122.190", 22))
            print "SSH available after", time.time() - t, "seconds"    
            break
        except:
            # на самм деле будет спать больше
            time.sleep(0.001)

finally:
    vm = c.lookupByName('test11')
    vm.destroy()

Этот quick-and-dirty скрипт дает время старта контейнера - 0.1±0.02сек, полной загрузки до готовности ssh сервера - 0.9±0.05сек на core i7-2630QM @ 2.00GHz (ноутбучный процессор). Примерно в 1.5 раза меньше времени нужно для старта на Core i5-650 @ 3.20GHz.


footer

И напоследок о другой стороне контейнеров - запуск отдельных приложений в изолированных окружениях ( a-la FreeBSD jail). Практически бесплатность создания контейнера (порядка десятка системных вызовов) создает интересные возможности. Например можно вынести в контейнеры исполнение потенциально опасного кода, отдельных сервисов и проч. Уже есть реализация подобной идеи - arkose. Он позволяет запускать потенциально опасные приложения в отдельных контейнерах, например браузер.

Подведем итоги - по сумме характеристик LXC очень серьезный конкурент для классической виртуализации (KVM/XEN). Пока его промышленное применение сдерживается некоторым объемом недоработок (но перспективы весьма радужные), но для "домашнего" использования он уже вполне готов.

Ссылки:
          lxc.sourceforge.net
          tr.opensuse.org/OpenVZ_virtualization#Density
          eos.aristanetworks.com/2011/06/linux-namespaces-at-arista
          en.wikipedia.org/wiki/Cgroups
          linux.die.net/man/7/capabilities
          en.gentoo-wiki.com/wiki/LXC#MAJOR_Temporary_Problems_with_LXC_-_READ_THIS
          code.launchpad.net/~zulcss/nova/nova-lxc
          wiki.debian.org/LXC
          en.gentoo-wiki.com/wiki/LXC
          habrahabr.ru/blogs/virtualization/130522
          lxc.sourceforge.net/man/lxc.html
          lxc.teegra.net
          nigel.mcnie.name/blog/a-five-minute-guide-to-linux-containers-for-debian
          help.ubuntu.com/community/LXC
          www.markus-gattol.name/ws/linux_containers.html
          blog.mraw.org/2011/04/05/Running_X_from_LXC
          libvirt.org/drvlxc.html
          kernelnewbies.org/Linux_3.2#head-782dd8358611718f0d8468ee7034c760ba5a20d3
          koder-ua.blogspot.com/2012/01/libvirt-co-3.html
          www.iip.net.pl/sites/default/files/41/Benchmarking%20the%20performance%20of%20virtualized%20routers.pdf
          launchpad.net/arkose
          en.wikipedia.org/wiki/FreeBSD_jail
          lists.ubuntu.com/archives/ubuntu-server-bugs/2011-February/051503.html
          www.redhat.com/archives/libvir-list/2010-January/msg00218.html
          libvirt.org/drvlxc.html
          upstart.ubuntu.com
          en.wikipedia.org/wiki/TCP_offload_engine
          wiki.openvz.org/Virtual_Ethernet_device
          wiki.ncl.cs.columbia.edu/wiki/KVMARM:MainPage

Исходники этого и других постов со скриптами лежат тут - github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.

4 comments:

Anonymous said...

отличная статья - спасибо!

sirmax said...

ОпенСтек действительно работает?
По моим данным - нет.
Есть ли пример инсталляции?

еще замечание - я не нашел проблем с запуском CentOS 6.3 под Ubuntu 11.10, но конечно я не проводил полного тестирования софта.

Статья отличная.

Alexey Diyan said...

Очень хорошая статья, из нее узнал про существование Linux Containers и с чем их едят.

На работе пробовали сделать легковесную виртуализацию с lxc на шареном билд сервере, но к сожалению так и не сложилось.

Однако совсем недавно появился шум вокруг проекта Docker.io - это такой себе синтаксический сахар вокруг Linux Containers.

Довольно юзабельная штука. Я начал потихоньку ее использовать для обкатки различных проектов и для того чтобы тестировать deployment / server provisioning скрипты.

Интересно твое мнение о Docker.

periyannan said...

This article is very much helpful and i hope this will be an useful information for the needed one. Keep on updating these kinds of informative things...

evs full form
raw agent full form
full form of tbh in instagram
dbs bank full form
https full form
tft full form
pco full form
kra full form in hr
tbh full form in instagram story
epc full form