Архив метки: Виртуализация

Vinchin Backup & Recovery: резервное копирование oVirt и XenServer в Multi-Hypervisor среде

В последнее время западные вендоры один за другим объявляют о своём уходе с российского рынка. В связи с этим остро встают вопросы замены санкционного программного обеспечения. Одним из вариантов замены может выступать программное обеспечение китайских компаний. В статье пойдёт речь о программном комплексе для резервного копирования виртуальных сред от компании Vinchin — Vinchin Backup & Recovery.

Читать

Как подключить виртуальный жесткий диск (VHD) в Hyper-V без дополнительных программ

В Hyper-V есть полноценная служба управления образа­ми Image Management Service, которую можно вызвать из сценариев, программ и кода, чтобы выполнить опе­рации подключения и отключения. Компания Microsoft предоставляет сценарий для подключения VHD-файлов и другой сценарий — для их отключения. Мною подготовлены гораздо более простые сценарии, чем у компании Microsoft. Эти сценарии Windows Management Instrumentation (WMI) не столь полнофункциональны, как у Microsoft, но пригодны для выполнения задачи. Сохраните приведенный ниже сценарий подключения с именем vhdmount.vbs. Option Explicit

Dim objWMIService, objVHDService, strComputer, strVHDFile strComputer =».»

If Wscript.Arguments.Count < 1 Then

Wscript.Echo «Arguments required. For example:» & vbCrLf &

«cscript vhdmount.vbs disk.vhd»

Wscript.Quit (0)

End If

strVHDFile = Wscript.Arguments (0)

Set objWMIService = GetObject («winmgmts:\» & strComputer &

«rootvirtualization»)

Set objVHDService = objWMIService.ExecQuery («SELECT * FROM MsvmJmageManagementService»).ltemlndex (0) objVHDService.Mount (strVHDFile) Убедитесь, что при запуске команды файл VHD не используется виртуальной машиной. VHD можно подключить с помощью следующей команды:

D:projectsWBScri pts>cscript vhdmount.vbs d:virtualsdemo1demo1 .vhd

Затем нужно перевести диск в активный режим с помощью оснастки Disk Management консоли Microsoft Management Console (MMC). После этого диску будет назначен символ. VHD можно отключить с помощью следующего сцена­рия. Сохраните его с именем vhdunmount.vbs. Option Explicit

Dim objWMIService, objVHDService, strComputer, strVHDFile strComputer =».»

If Wscript.Arguments.Count < 1 Then

Wscript.Echo «Arguments required. For example:» & vbCrLf &

«cscript vhdmount.vbs disk.vhd»

Wscript.Quit (0)

End If

strVHDFile = Wscript.Arguments (0)

Set objWMIService = GetObject («winmgmts:\» & strComputer &

«rootvirtualization»)

Set objVHDService = objWMIService.ExecQuery («SELECT * FROM Msvm_lmageManagementService»).ltemlndex (0) objVHDService.Unmount (strVHDFile) Чтобы выполнить сценарий и отключить VHD, запусти­те следующую команду: D:projectsVBScripts>cscript vhdunmount.vbs d:virtualsdemo1demo1 .vhd

 

Джон Сэвилл



2019-08-19T16:10:05
Виртуализация

Список акронимов (сокращений) VMware








AAMAutomated Availability Manager (aka VMware HA since v 4.1)
ADMApplication Discover Manager
ALUAAsymmetric Logical Unit Access
APDAll Paths Down
APMApplication Performance Manager
CBMChargeback Manager
CBRCContent Based Read Cache
CFCloud Foundry Читать

libvirt & Co. Облако "на коленке". Часть 2 — Сети

Компоненты

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

bridges — сетевые мосты — программные аналоги свичей, позволяют соединить вместе несколько сетевых интерфейсов и передавать между ними пакеты, как если бы они были включены в один свич. Бриджи управляются с помощью команды brctl:

Без подсветки синтаксиса

$ brctl  show  # напечатать все бриджи с подключенными интерфейсами
bridge name bridge id STP enabled interfaces
virbr0 8000.000000000000 yes

# brctl add - добавить бридж
# brctl addif - включить eth в бридж
# brctl delif - отключить интерфейс
# brctl delbr - удалить бридж

$ brctl  show  # напечатать все бриджи с подключенными интерфейсами
bridge name bridge id STP enabled interfaces
virbr0 8000.000000000000 yes

# brctl add - добавить бридж
# brctl addif - включить eth в бридж
# brctl delif - отключить интерфейс
# brctl delbr - удалить бридж

Перед работой с бриджами лучше ознакомиться с документацией, они содержат некоторое количество нетривиальных моментов.

tun (tap) — виртуальные сетевые интерфейсы. В отличии от аппаратных привязаны к определенному процессу пользовательского режима, а не к сетевой карте. Родительский процесс может писать/читать данные из виртуального интерфейса имитируя работу сети. В остальном они не отличаются от обычных интерфейсов. С помощью tun/tap работают многие VNP программы, например openvpn, которая создает tun/tap, вычитывает из него данные, шифрует и переправляет по обычной сети на другой компьютер, где второй процесс openvpn принимает данные, расшифровывает и записывает в свой tun/tap, имитируя прямое сетевое соединение между удаленными компьютерами. Как и 95% всех сетевых возможностей linux tun/tap можно управлять с помошью утилиты ip. Пример использования tun из python можно найти тут kharkovpromenade. Tun используются для создания сетевых интерфейсов виртуальынх машин.

iptables — система управления сетевым трафиком в linux. Обеспечивает фильтрация и модификацию трафика, управление сетевыми соединениями, etc. Возможности iptables чрезвычайно обширные и описывать даже примерно я их не буду, приведу только команды, позволяющие увидеть все правила на компьютере:

Без подсветки синтаксиса

# iptables -t nat -S
# iptables -t filter -S
# iptables -t raw -S
# iptables -t mangle -S

# iptables -t nat -S
# iptables -t filter -S
# iptables -t raw -S
# iptables -t mangle -S

Все правила легко читаются даже без знания iptables.

Ок, с этим багажом уже можно разбираться с виртуальными сетями. Для большинства случаев нам не придется делать сети самостоятельно — libvirt берет эту работу на себя, предоставляя нам готовый результат. Начнем с устройства простейшей сети, которую со старта создает libvirt — defaults.

Без подсветки синтаксиса

# virsh n
et-list
Name State Autostart
-----------------------------------------
default active yes

# virsh net-list
Name State Autostart
-----------------------------------------
default active yes

Описание этой сети можно получить с помощью следующих команд:

Без подсветки синтаксиса

# virsh net-info default

Name default
UUID c598e36f-31fd-672e-09e3-2cbe061cd606
Active: yes
Persistent: yes
Autostart: yes
Bridge: virbr0

# virsh net-dumpxml default

# virsh net-info default

Name default
UUID c598e36f-31fd-672e-09e3-2cbe061cd606
Active: yes
Persistent: yes
Autostart: yes
Bridge: virbr0

# virsh net-dumpxml default

Без подсветки синтаксиса


default
c598e36f-31fd-672e-09e3-2cbe061cd606
mode='nat'/>
name='virbr0' stp='on' delay='0' />
address='192.168.122.1' netmask='255.255.255.0'>

start='192.168.122.40' end='192.168.122.254' />




default
c598e36f-31fd-672e-09e3-2cbe061cd606







Тот же самый результат можно получить и из python:

Без подсветки синтаксиса

import libvirt
from xml.etree.ElementTree import fromstring

conn = libvirt.open("qemu:///system")
net = conn.networkLookupByName('default')
xml = fromstring(net.XMLDesc(0))
print "default net addr =", xml.find('ip').attrib['address']
print "default net mask =", xml.find('ip').attrib['netmask']
print "default net bridge =", xml.find('bridge').attrib['name']

import libvirt
from xml.etree.ElementTree import fromstring

conn = libvirt.open("qemu:///system")
net = conn.networkLookupByName('default')
xml = fromstring(net.XMLDesc(0))
print "default net addr =", xml.find('ip').attrib['address']
print "default net mask =", xml.find('ip').attrib['netmask']
print "default net bridge =", xml.find('bridge').attrib['name 9;]

Еще один важный компонент сети — dnsmasq:

Без подсветки синтаксиса

$ ps aux | grep dnsmasq | grep -v grep
nobody 4503 0.0 0.0 25836 976 ? S 02:08 0:00
dnsmasq --strict-order --bind-interfaces
--pid-file=/var/run/libvirt/network/default.pid
--conf-file= --except-interface lo
--listen-address 192.168.122.1
--dhcp-range 192.168.122.40,192.168.122.254
--dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases
--dhcp-lease-max=215 --dhcp-no-override

$ ps aux | grep dnsmasq | grep -v grep
nobody 4503 0.0 0.0 25836 976 ? S 02:08 0:00
dnsmasq --strict-order --bind-interfaces
--pid-file=/var/run/libvirt/network/default.pid
--conf-file= --except-interface lo
--listen-address 192.168.122.1
--dhcp-range 192.168.122.40,192.168.122.254
--dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases
--dhcp-lease-max=215 --dhcp-no-override

Конфигурационные файлы сетей хранятся в /var/lib/libvirt/network:

Без подсветки синтаксиса

$ ls -l /var/lib/libvirt/network/
total 4
-rw-r--r-- 1 root root 543 2011-12-24 02:08 default.xml

$ ls -l /var/lib/libvirt/network/
total 4
-rw-r--r-- 1 root root 543 2011-12-24 02:08 default.xml

Итак — что получилось в итоге. Вот эта строка конфигурационного файла:

Без подсветки синтаксиса

 type="network">
network="default" />
mode="nat" />
dev="vnet7" />
address="{mac}" />






Подключила eth0 нашей виртуальной машины к бриджу virbr0 сети default. Эта сеть имеет маску 192.168.122.0/24, подключена через NAT к внешнему миру и обслуживается dhcp сервером. Причем сам virbr0 имеет ip 192.168.122.1 и служит гейтом для этой сети. Адреса из диапазона 192.168.122.2-192.168.122.40 я ранее зарезервировал для ручного распределения, отредактировав и перезапустив сеть.

Теперь вернемся к начальному вопросу — как программно узнать ip адрес, выданный нашей виртуалке? Есть три основных способа:

  • Если с виртуальной машиной уже был обмен данными, то можно посмотреть в кеше маршрутизации 'ip route show cache | grep virbr0' или в кеше аппаратных адресов — 'arp -na'. Способ наименее надежный, так как если обмена не было кеши будут пустые.
  • Достать информацию из базы dhcp сервера — leases. Для dnsmasq это по умолчанию файл /var/lib/libvirt/dnsmasq/default.leases:

Без подсветки синтаксиса

$ cat /var/lib/libvirt/dnsmasq/default.leases
1324718340 00:44:01:61:78:01 192.168.122.99 * *

$ cat /var/lib/libvirt/dnsmasq/default.leases
1324718340 00:44:01:61:78:01 192.168.122.99 * *

В при

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

 

# # 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 параметрами по умолчанию:

Показать код

    #  скопированно из oneiric.cfg
    lxc.network.type = veth
    lxc.network.flags = up
    lxc.network.link = virbr0
    lxc.network.hwaddr =  11:b2:c3:d4:e5:f7
    lxc.network.ipv4 = 192.168.122.190/24
    lxc.network.name = test_vm
    lxc.utsname = test

# значения по умолчанию
lxc.tty = 4
lxc.pts = 1024
lxc.rootfs = /var/lib/lxc/test/rootfs
lxc.mount = /var/lib/lxc/test/fstab
lxc.arch = amd64

# устройства, которые будут доступны из контейнера
lxc.cgroup.devices.deny = a
# Allow any mknod (but not using the node)
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
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
# /dev/{,u}random
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
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm
#fuse
lxc.cgroup.devices.allow = c 10:229 rwm

 

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

Без подсветки синтаксиса

# ls -l /dev/kvm
crw-rw----+ 1 root k
vm 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

 

# 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).

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

Показать код

# lxc-start -d -n test

# lxc-ls
kvm_in_lxc oneiric oneiric_br test # вообще все доступные контейнеры
test

# ip addr show
# #…..
# # это сетевой адаптер, ведущий в контейнер
22: vethcSEf3D: <broadcast,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000
link/ether d2:ea:5e:ba:3f:1b brd ff:ff:ff:ff:ff:ff
inet6 fe80::d0ea:5eff:feba:3f1b/64 scope link
valid_lft forever preferred_lft forever

# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.000000000000 no
virbr0 8000.d2ea5eba3f1b yes vethcSEf3D

# ps -eo pid,cgroup,user,args | grep cpuset:/test
# # показываем все процессы с информацией о cgroup
# # и фильтруем только те, что относятся в группе test типа cpuset

22526 6:cpuset:/test?5:freezer:/ root /sbin/init
22562 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd —mode=boot —attach-to-session
22593 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D
22594 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5
22617 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1
22630 6:cpuset:/test?5:freezer:/ root -bash
22715 6:cpuset:/sysdefault?5:fre root grep —color=auto cpuset:/test

# # это процессы из контейнера, обратите внимание на PID
# # логинимся в контейнер

# ssh root@192.168.122.190
root@192.168.122.190’s password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)

* Documentation: https://help.ubuntu.com/
Last login: Thu Jan 12 22:08:53 2012 from 192.168.122.1
root@test:~# # мы в контейнере
root@test:~# ps -eo pid,cgroup,user,args
PID CGROUP USER COMMAND
1 6:cpuset:/test?5:freezer:/ root /sbin/init
11 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd —mode=boot —attach-to-session
42 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D
43 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5
49 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1
61 6:cpuset:/test?5:freezer:/ root -bash
81 6:cpuset:/test?5:freezer:/ root ps -eo pid,cgroup,user,args

# # PID виртуализированы, а вот cgroup — нет
root@test:~# ip addr
24: test_vm: <broadcast,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:44:01:61:78:22 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.190/24 brd 192.168.122.255 scope global test_vm
inet6 fe80::244:1ff:fe61:7822/64 scope link
valid_lft forever preferred_lft forever
26: lo: <loopback,UP,LOW ER_UP> mtu 16436 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
# # сетевые интерфейсы тоже только те, что нужно

 

# lxc-start -d -n test

# lxc-ls
kvm_in_lxc oneiric oneiric_br test # вообще все доступные контейнеры
test

# ip addr show
# #…..
# # это сетевой адаптер, ведущий в контейнер
22: vethcSEf3D: <broadcast,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000
link/ether d2:ea:5e:ba:3f:1b brd ff:ff:ff:ff:ff:ff
inet6 fe80::d0ea:5eff:feba:3f1b/64 scope link
valid_lft forever preferred_lft forever

# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.000000000000 no
virbr0 8000.d2ea5eba3f1b yes vethcSEf3D

# ps -eo pid,cgroup,user,args | grep cpuset:/test
# # показываем все процессы с информацией о cgroup
# # и фильтруем только те, что относятся в группе test типа cpuset

22526 6:cpuset:/test?5:freezer:/ root /sbin/init
22562 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd —mode=boot —attach-to-session
22593 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D
22594 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5
22617 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1
22630 6:cpuset:/test?5:freezer:/ root -bash
22715 6:cpuset:/sysdefault?5:fre root grep —color=auto cpuset:/test

# # это процессы из контейнера, обратите внимание на PID
# # логинимся в контейнер

# ssh root@192.168.122.190
root@192.168.122.190’s password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)

* Documentation: https://help.ubuntu.com/
Last login: Thu Jan 12 22:08:53 2012 from 192.168.122.1
root@test:~# # мы в контейнере
root@test:~# ps -eo pid,cgroup,user,args
PID CGROUP USER COMMAND
1 6:cpuset:/test?5:freezer:/ root /sbin/init
11 6:cpuset:/test?5:freezer:/ root /sbin/plymouthd —mode=boot —attach-to-session
42 6:cpuset:/test?5:freezer:/ root /usr/sbin/sshd -D
43 6:cpuset:/test?5:freezer:/ syslog rsyslogd -c5
49 6:cpuset:/test?5:freezer:/ root sshd: root@pts/1
61 6:cpuset:/test?5:freezer:/ root -bash
81 6:cpuset:/test?5:freezer:/ root ps -eo pid,cgroup,user,args

# # PID виртуализированы, а вот cgroup — нет
root@test:~# ip addr
24: test_vm: <broadcast,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:44:01:61:78:22 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.190/24 brd 192.168.122.255 scope global test_vm
inet6 fe80::244:1ff:fe61:7822/64 scope link
valid_lft forever preferred_lft forever
26: lo: <loopback,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
# # сетевые интерфейсы тоже только те, что нужно

 

В итоге мы получили изолированную виртуальную машину. Ради интереса — по информации от 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

 

# 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

 

# 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:

Показать код

<domain type="lxc">
  test
  1048576
  
    exe
    /sbin/init
  
  1
  <clock offset="utc"/>
  destroy
  restart
  destroy
  
    /usr/lib/libvirt/libvirt_lxc
      <filesystem type="mount">
          <source dir="/var/lib/lxc/test/rootfs" />
          <target dir="/" />
      
        <interface type="network">
            <source network="default" />
            <forward mode="nat" />
            <target dev="vnet7" />
            <mac address="00:44:01:61:78:22" />
        
    <console type="pty" />

 



  test
  1048576
  
    exe
    /sbin/init
  
  1
  
  destroy
  restart
  destroy
  
    /usr/lib/libvirt/libvirt_lxc
      
          
          
      
        
            
            
            
            
        
    
  

 

Записываем это в %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:

Показать код

# virsh -c lxc:/// create lxc.xml
Domain test created from lxc.xml

# virsh -c lxc:/// list

Id Name State
———————————-
21834 test running

# ip addr show
…….
10: virbr0: <broadcast,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0

16: veth0: <broadcast,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000
link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff
inet6 fe80::8c05:b2ff:febb:bef2/64 scope link
valid_lft forever preferred_lft forever

# virsh -c lxc:// list
Id Name State
———————————-
13261 test11 running

# ssh root@192.168.122.190
root@192.168.122.190’s password:

root@192.168.122.190’s password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)

* Documentation: https://help.ubuntu.com/
Last login: Tue Jan 17 03:50:29 2012 from 192.168.122.1
root@test11:~# # мы в контейнере

 

# virsh -c lxc:/// create lxc.xml
Domain test created from lxc.xml

# virsh -c lxc:/// list

Id Name State
———————————-
21834 test running

# ip addr show
…….
10: virbr0: <broadcast,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0

16: veth0: <broadcast,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr0 state UP qlen 1000
link/ether 8e:05:b2:bb:be:f2 brd ff:ff:ff:ff:ff:ff
inet6 fe80::8c05:b2ff:febb:bef2/64 scope link
valid_lft forever preferred_lft forever

# virsh -c lxc:// list
Id Name State
———————————-
13261 test11 running

# ssh root@192.168.122.190
root@192.168.122.190’s password:

root@192.168.122.190’s password:
Welcome to Ubuntu 11.10 (GNU/Linux 3.1.3-030103-generic x86_64)

* Documentation: https://help.ubuntu.com/
Last login: Tue Jan 17 03:50:29 2012 from 192.168.122.1
root@test11:~# # мы в контейнере

 

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()

 

#!/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.opensu
se.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.

Автор: konstantin danilov

libvirt & Co. Облако "на коленке". Часть 3 — Дисковые образы

Следующий шаг — разобраться с дисковыми образами виртуальных машин. Основные вопросы — как хранить, где хранить и как модифицировать.

Как хранить

  • raw — самый простой формат, прямая копия данных с диска.
  • qcow2 — основной формат qemu. Обладает большим спектром возможностей
  • vdi — формат, используемый VirtualBox'ом
  • vmdkVMware формат
  • cow, qcow — каменный век

Обсуждать raw смысла не имеет — просто диск байт по байту. qcow2 самый распространенный и функциональный формат виртуальных дисков для qemu/xen.

  • содержит только те кластеры, в которые были записаны данные. Т.е. можно создать образ диска размером в 10G, но реальный размер файла будет расти только при фактической записи на диск.

  • поддерживает «наслоение» файлов. Qcow2 файл позволяет хранить только изменения относительно другого файла, представляющего базовый образ (backing store). При таком режиме базовый файл никогда не модифицируется — запись идет в «верхний» qcow2 файл, а чтение происходит из qcow2, если соответствующий кластер был модифицирован и записан в qcow2 или из базового в противном случае. Это позволяет, например, запустить несколько виртуальных машин на основе одного образа. После подготовки базового образа к нему подключается параллельно несколько qcow2 файлов и на их основе запускаются vm, каждая из которых «идет своей дорогой». Кроме экономии места на диске это также улучшает кеширование. Поддерживается перенесение(commit) изменений назад в базовый файл и некоторые другие возможности. Базовый файл может быть в любом формате — raw, qcow2, etc.
  • шифрование
  • компрессия (только для чтения, запись будет производиться в распакованный сектор)
  • можно делать qcow2 файлы с пред выделенным местом для метаданных, это повышает быстродействие в случае если ожидаются интенсивные дисковые операции.

Базовая утилита для работы с этими форматами — qemu-img.

Без подсветки синтаксиса

$ qemu-img create -f raw img.raw 10G 
Formatting 'img.raw', fmt=raw size=10737418240

$ qemu-img create -f qcow2 img.qcow2 10G
Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off
cluster_size=0

$ qemu-img create -f qcow2 -o preallocation=metadata img_m.qcow2 10G
Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off
cluster_size=0 preallocation='metadata'

$ ls -lhsS img*
1.8M -rw-r--r-- 1 koder koder 11G 2011-12-24 21:37 img_m.qcow2
0 -rw-r--r-- 1 koder koder 10G 2011-12-24 21:34 img.raw
136K -rw-r--r-- 1 koder koder 193K 2011-12-24 21:37 img.qcow2

$ qemu-img create -f raw img.raw 10G 
Formatting 'img.raw', fmt=raw size=10737418240

$ qemu-img create -f qcow2 img.qcow2 10G
Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off
cluster_size=0

$ qemu-img create -f qcow2 -o preallocation=metadata img_m.qcow2 10G
Formatting 'img.qcow2', fmt=qcow2 size=10737418240 encryption=off
cluster_size=0 preallocation='metadata'

$ ls -lhsS img*
1.8M -rw-r--r-- 1 koder koder 11G 2011-12-24 21:37 img_m.qcow2
0 -rw-r--r-- 1 koder koder 10G 2011-12-24 21:34 img.raw
136K -rw-r--r-- 1 koder koder 193K 2011-12-24 21:37 img.qcow2

Первая запись в ls это реальный размер файла на диске, а шестая — заявленный размер. В данном случае мы видим sparse файлы в действии. Все основные файловые системы в linux поддерживают выделение реального места на диске под файл при записи реальных данных и такое поведение установлено по умолчанию. В отличии от raw файлов qcow2 файлы со старта содержат управляющие структуры, так что их размен не нулевой. Впрочем при копировании/архивации и др. raw файлов все-же придется обрабатывать полный размер, в отличии от qcow2.

Без подсветки синтаксиса

$ qemu-img info img.raw  # информация о файле 
image: img.raw
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 0

$ qemu-img info img.qcow2
image: img.qcow2
file format: qcow2
virtual size: 10G (10737418240 bytes)
disk size: 136K
cluster_size: 65536

$ qemu-img info img.raw  # информация о файле 
image: img.raw
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 0

$ qemu-img info img.qcow2
image: img.qcow2
file format: qcow2
virtual size: 10G (10737418240 bytes)
disk size: 136K
cluster_size: 65536

Сделаем «наслаивающиеся» qcow2 файлы. Размер для нового файла указывать не нужно — так как он должен быть такого-же размера, как и базовый образ:

Без подсветки синтаксиса

$ qemu-img create -b img.qcow2 -f qcow2 -F qcow2 img_bs.qcow2
Formatting 'img_bs.qcow2', fmt=qcow2 size=10737418240
backing_file='img.qcow2' backing_fmt='qcow2' encryption=off cluster_size=0

$ qemu-img info img_bs.qcow2
image: img_bs.qcow2
file format: qcow2
virtual size: 10G (10737418240 bytes)
disk size: 136K
cluster_size: 65536
backing file: img.qcow2 (actual path: img.qcow2)

$ qemu-img create -b img.qcow2 -f qcow2 -F qcow2 img_bs.qcow2
Formatting 'img_bs.qcow2', fmt=qcow2 size=10737418240
backing_file='img.qcow2' backing_fmt='qcow2' encryption=off cluster_size=0

$ qemu-img info img_bs.qcow2
image: img_bs.qcow2
file format: qcow2
virtual size: 10G (10737418240 bytes)
disk size: 136K
cluster_size: 65536
backing file: img.qcow2 (actual path: img.qcow2)

Базовый файл может быть любого формата, но создаваемый с backing store файл может быть только qcow2. qemu-img также Поддерживает все основные операции — изменение размера образа, смена базового файла, проверка целостности и создание/работа со снимками.

Остальные форматы интересны только при использовании соответствующих систем виртуализации и рассматривать их в контексте qemu/kvm/xen смысле не имеет. qemu-img позволяет конвертировать файлы между всеми форматами, описанными сверху. Единственное исключение — разбитые на несколько файлов диски vmWare. Для работы сначала их нужно объединить в один файл с помошью vdiskmanager, который входит в поставку vmWare Workstation for linux. Таким образом можно с kvm/xen использовать широко доступные в интернете виртуальные машины для vmWare.

Подключение этих файлов к виртуальным машинам libvirt-disk, подключение CD делается примерно так-же, как и raw файлов.

Где хранить

Для локального хранения есть два основных варианта — файл или диск/раздел/LVM устройством ( всем линуксоидам, кто еще не освоил LVM — очень советую это сделать, это мощная система управления дисковым пространством; так-же полезное чтиво — devmapper, на нем и работают в linux LVM2, всякие software RAID & Co). Создание образа на разделе/LVM принципиально не отличается от файла на FS, нужно только указывать qemu-img format не raw а host_device:

Без подсветки синтаксиса

# qemu-img create -f host_device /dev/vm_images/img1 10G

# qemu-img create -f host_device /dev/vm_images/img1 10G

/dev/vm_images/img1 — это логический раздел img1 в группе разделов vm_images (LVM). После этого можно передать его в libvirt:

Без подсветки синтаксиса

 type='block' device='disk'>
name='qemu' type='raw' />
dev='/dev/vm_images/img1' />
dev='vda' bus='virtio' />





Обратите внимание на вместо него можно использовать , но virtio дает значительный прирост производительности. Virtio это система частичной паравиртуализации для kvm. Она позволяет установить в гостевой системе набор драйверов, которые будут обслуживать виртуальные устройства, передавая в гипервизор высокоуровневые запросы и избавляя гостевую систему от превращения их (запросов) в общение с портами и DMA, а гипервизор от имитации низкоуровневых протоколов. Особенно хорошо virtio ускоряет сеть и дисковые операции. В своем роде это аналог vmware tools. Все современные ядра linux поддерживают virtio без дополнительных телодвижений, но, например, ядро из debian lenny — нет. Так что образ debian lenny просто не загрузится с virtio устройства (есть virtio драйвера и для windows).

Это были локальные способы, кроме них есть еще вагон и маленькая тележка сетевых хранилищ (причем они могут быть использованы и как набор блочных устройств в режиме «по одному на VM», так и в режиме удаленного диска с файловой системой). Очевидными преимуществами сетевых хранилищ являются централизованное управление дисками для всего облака и облегчение миграции. Для небольших облаков они не так интересны, так что пробежимся по некоторым из ниx вскользь.

AOE — (ATA Over Ethernet) реализация ATA протокола поверх ethernet. Фактически ATA команды напрямую передаются удаленному винчестеру. Из преимуществ — скорость. Дисковый массив и все рабочие станции должны быть в одном сегменте локальной сети (нет маршрутизации). Основной производитель устройств — Coraid. Впрочем набор утилит открыт и можно достаточно просто самостоятельно сделать AOE сервер примеров в сети предостаточно.

iSCSI — SCSI поверх IP. Многие системы удаленного хранения работают именно на этом проколе. Маршрутизируемый (дисковый массив и рабочие станции могут располагаться в сети где угодно). Возможностей больше, чем у AOE, скорость — ниже.

nbd — Network Block Device. Протокол для раздачи блочных устройств по сети, подробнее обсуждается ниже на примере qemu-nbd.

rbd — Rados block device. Часть проекта ceph — распределенного сетевого хранилища и файловой системы (из интересного — есть прямая привязка к qemu/kvm).

drbd — Distributed Replicated Block Device. Название говорит само за себя.

Из действительно интересного — sheepdog. Это фактически первое(AFAIK) «правильное» распределенное сетевое хранилище (только для kvm/qemu). Построено в соответствии с идеями amazon dynamo (статья обязательно к прочтению всем, кто так или иначе сталкивается с распределенными системами хранения данных. Именно на этих идеях построены все основные NoSQL базы данных, ориентированные на сверхбольшие объемы данных, — Cassandra, REAK, Voldemort). К сожалению проект похож на заброшенный.

Больше информации по этим хранилищам можно получить еще и в блоге Daniel P. Berrange.

Я пока сознательно обхожу стороной производительность дисковых операций на разных форматах, поскольку это довольно большая тема для отдельного поста. Для локальных хранилищ с высоты птичьего полета результат примерно такой
— qcow2 почти такой же по скорости, как raw. Двухслойные qcow2 (и qcow2 поверх raw) примерно в два раза медленнее, если не используется SSD диск. А размещение образа на lvm разделе вместо файла увеличивает скорость работы только на больших блоках (файловые системы хорошо оптимизированы под такие нагрузки, по крайней мере XFS).

Как модифицировать

Есть масса причин модифицировать диски виртуальных машин до запуска — изменение настроек для соответствия виртуальному окружению, передача параметров в vm, установка драйверов, etc. Нас в первую очередь будет интересовать изменение сетевых настроек и передача параметров в vm.

Первый и самый основанной способ модифицировать файл с образом — каким-то образом примонтировать его в локальную файловую систему и дальше работать с локальными файлами. Для этого сначала нужно подключить его к виртуальному блочному устройству — loop.

Без подсветки синтаксиса

# losetup -f --show img.raw
/dev/loop0

# losetup -a
/dev/loop0: [0807]:2754650 (/home/koder/vm_images/img.raw)


# mkdir -p /mnt/vm/img_0
# mount /dev/loop0 /mnt/vm/img_0

# losetup -f --show img.raw
/dev/loop0

# losetup -a
/dev/loop0: [0807]:2754650 (/home/koder/vm_images/img.raw)


# mkdir -p /mnt/vm/img_0
# mount /dev/loop0 /mnt/vm/img_0

После этих команд файловая система из img.raw подключена в /mnt/vm/img_0 т.е. ее корень совпадает с /mnt/vm/img_0, /etc — /mnt/vm/img_0/etc и т.д. По окончанию обработки отключаем файл

Без подсветки синтаксиса

# umount /dev/loop0
# losetup -d /dev/loop0

# umount /dev/loop0
# losetup -d /dev/loop0

Все внесенные изменения будут сохранены в файл образа. Если на образе несколько разделов, то все чуть сложнее — либо сначала с помощью fdisk смотрим таблицу разделов, определяем смещение необходимого раздела и передаем его в опции -o в losetup либо используем kpartx:

Без подсветки синтаксиса

# kpartx -a img2.raw
# mount /dev/mapper/loop0p1 /mnt/vm/img_0

# kpartx -a img2.raw
# mount /dev/mapper/loop0p1 /mnt/vm/img_0

kpartx делает дополнительные виртуальные устройства /dev/mapper/loop0pX для всех разделов на образе (на самом деле он использует devmapper для /dev/loop0).

Однако так можно подключить только raw образы, для всех остальных нужно использовать qemu-nbd. Это сервер, которые умеет раздавать блочное устройство по nbd протоколу. (Вообще nbd довольно интересный прокол — позволяет раздать по сети любое локальное блочное устройство, или, что еще интереснее, иммитировать его из пользовательской программы ,а не из драйвера. Вот, например ndb сервера 1, 2 на python.)

Без подсветки синтаксиса

# modprobe nbd max_part=16 # <- очень важно передать max_part, если nbd
# qemu-nbd -b 127.0.0.1 -p 5555 img.qcow2
# nbd-client 127.0.0.1 5555 /dev/nbd0 # <- подключить к /dev/nb0

# modprobe nbd max_part=16 # <- очень важно передать max_part,
 если nbd
# qemu-nbd -b 127.0.0.1 -p 5555 img.qcow2
# nbd-client 127.0.0.1 5555 /dev/nbd0 # <- подключить к /dev/nb0

Если привязывать сервер не к localhost, то можно подключить это устройство на другом компьютере удаленно. Можно объединить оба шага в один:

Без подсветки синтаксиса

# qemu-nbd --connect=/dev/nbd0 img.qcow2

# qemu-nbd --connect=/dev/nbd0 img.qcow2

По итогу /dev/nbd0 будет представлять образ виртуальной машины, а /dev/nbd0pX — отдельные разделы. Дальнейшая работа не отличается от raw файла — монтирует полученные устройства в локальную файловую систему и елозим байты, вся поддержка формата qcow2 будет выполняться qemu-nbd сервером.

Совершенно другой способ модификации дисковых образов предлагает библиотека libguestfs. Это одна интересных библиотек, которые я видел. Она позволяет модифицировать образы виртуальных машин и многое другое. При это не требуются права root, поддерживаются все файловые системы, поддерживаемые в linux, LVM и все-все-все. Внутри она запускает простенькую виртуальную машину, монтирует в нее указанный файл образа и позволяет модифицировать его используя обширный API, который в итоге общается с VM по RPC. В принципе libguestfs позволяет и смонтировать файловую систему локально, использую fuse. Вообще возможности libguestfs очень обширны — p2v и v2v конвертация, модификация реестра windows, определение установленной операционной системы и ПО, автоматическое монтирование всего в соответствии с локальными fstab и др. Еще из интересного в нее входит guestfish — утилита командной строки, позволяющая выполнять все операции из командной строки, в комплекте с virsh они позволяют писать маленькие системы управления VM прямо на bash. Есть API для C, Python, Perl, Ruby, Ocaml и прочего. Меня, ессно, интересует в первую очередь python.

Для приведения образа vm к удобному для запуска виду нам нужно примерно такая функция:

Без подсветки синтаксиса

def prepare_guest_debian(disk_path, hostname, passwords, eth_devs, format=None, apt_proxy_ip=None):

# создаем и запускаем vm
gfs = guestfs.GuestFS()
gfs.add_drive_opts(disk_path, format=format)
gfs.launch()

# находим раздел с /etc. Не очень чистое решение, но для образов, когда все
# на одном разделе работает
for dev, fs_type in gfs.list_filesystems():
if fs_type in 'ext2 ext3 reiserfs3 reiserfs4 xfs jfs btrfs':
# монтирует раздел в корень файловой системы vm
gfs.mount(dev, '/')
# если есть etc - все ок
if gfs.exists('/etc'):
break
gfs.umount(dev)

# обновляем hostname, для правильной работы hostname нужно так-же модифицировать
# /etc/hosts
gfs.write('/etc/hostname', hostname)

# set eth device names for udev
templ = 'SUBSYSTEM=="net", DRIVERS=="?*", ATTR{{address}}=="{hw}", NAME="{name}"'

# записываем настройки сетевых интерфейсов в /etc/network/interfaces

rules_fc = []
interfaces = ["auto loniface lo inet loopback"]
for dev, (hw, ip, sz, gw) in eth_devs.items():
rules_fc.append(templ.format(hw=hw, name=dev))
interfaces.append("auto " + dev)

if ip == 'dhcp':
interfaces.append("iface {0} inet dhcp".format(dev))
else:
interfaces.append("iface {0} inet static".format(dev))
interfaces.append(" address " + ip)
network = int2ip(ip2int(ip) & ip2int(netsz2netmask(sz)))
interfaces.append(" network " + network)
interfaces.append(" netmask " + netsz2netmask(sz))

gfs.write('/etc/udev/rules.d/70-persistent-net.rules', "n".join(rules_fc))
gfs.write('/etc/network/interfaces', "n".join(interfaces))

# обновляем пароли для пользователей

chars = "".join(chr(i) for i in range(ord('a'), ord('z') + 1))
chars += "".join(chr(i) for i in range(ord('A'), ord('Z') + 1))
chars += "".join(chr(i) for i in range(ord('0'), ord('9') + 1))

hashes = {}
for login, passwd in passwords.items():
salt = "".join(random.choice(chars) for _ in range(8))
hashes[login] = crypt.crypt(passwd, "$6$" + salt)

new_shadow = []
need_logins = set(hashes)

for ln in gfs.read_file('/etc/shadow').split('n'):
ln = ln.strip()
if ln != '' and ln[0] != '#':
login = ln.split(':', 1)[0]
if login in hashes:
new_shadow.append("{login}:{hash}:{rest}".format(login=login,
hash=hashes[login],
rest=ln.split(':', 2)[2]))
need_logins.remove(login)
else:
new_shadow.append(ln)

for login in need_logins:
new_shadow.append("{login}:{hash}:{rest}".format(login=login,
hash=hashes[login],
rest="0:0:999 99:7:::"))

gfs.write('/etc/shadow', "n".join(new_shadow))

# тут еще модификация /etc/passwd, /etc/hosts, создание домащних папок, etc
# полная версия на github


def prepare_guest_debian(disk_path, hostname, passwords, eth_devs, format=None, apt_proxy_ip=None):

# создаем и запускаем vm
gfs = guestfs.GuestFS()
gfs.add_drive_opts(disk_path, format=format)
gfs.launch()

# находим раздел с /etc. Не очень чистое решение, но для образов, когда все
# на одном разделе работает
for dev, fs_type in gfs.list_filesystems():
if fs_type in 'ext2 ext3 reiserfs3 reiserfs4 xfs jfs btrfs':
# монтирует раздел в корень файловой системы vm
gfs.mount(dev, '/')
# если есть etc - все ок
if gfs.exists('/etc'):
break
gfs.umount(dev)

# обновляем hostname, для правильной работы hostname нужно так-же модифицировать
# /etc/hosts
gfs.write('/etc/hostname', hostname)

# set eth device names for udev
templ = 'SUBSYSTEM=="net", DRIVERS=="?*", ATTR{{address}}=="{hw}", NAME="{name}"'

# записываем настройки сетевых интерфейсов в /etc/network/interfaces

rules_fc = []
interfaces = ["auto loniface lo inet loopback"]
for dev, (hw, ip, sz, gw) in eth_devs.items():
rules_fc.append(templ.format(hw=hw, name=dev))
interfaces.append("auto " + dev)

if ip == 'dhcp':
interfaces.append("iface {0} inet dhcp".format(dev))
else:
interfaces.append("iface {0} inet static".format(dev))
interfaces.append(" address " + ip)
network = int2ip(ip2int(ip) & ip2int(netsz2netmask(sz)))
interfaces.append(" network " + network)
interfaces.append(" netmask " + netsz2netmask(sz))

gfs.write('/etc/udev/rules.d/70-persistent-net.rules', "n".join(rules_fc))
gfs.write('/etc/network/interfaces', "n".join(interfaces))

# обновляем пароли для пользователей

chars = "".join(chr(i) for i in range(ord('a'), ord('z') + 1))
chars += "".join(chr(i) for i in range(ord('A'), ord('Z') + 1))
chars += "".join(chr(i) for i in range(ord('0'), ord('9') + 1))

hashes = {}
for login, passwd in passwords.items():
salt = "".join(random.choice(chars) for _ in range(8))
hashes[login] = crypt.crypt(passwd, "$6$" + salt)

new_shadow = []
need_logins = set(hashes)

for ln in gfs.read_file('/etc/shadow').split('n'):
ln = ln.strip()
if ln != '' and ln[0] != '#':
login = ln.split(':', 1)[0]
if login in hashes:
new_shadow.append("{login}:{hash}:{rest}".format(login=login,
hash=hashes[login],
rest=ln.split(':', 2)[2]))
need_logins.remove(login)
else:
new_shadow.append(ln)

for login in need_logins:
new_shadow.append("{login}:{hash}:{rest}".format(login=login,
hash=hashes[login],
rest="0:0:99999:7:::"))

gfs.write('/etc/shadow', "n".join(new_shadow))

# тут еще модификация /etc/passwd, /etc/hosts, создание домащних папок, etc
# полная версия на github

Тут использован прямой интерфейс libguestfs. Наверное, для указанных задач проще смонтировать образ через guestmount и модифицировать локальные файлы, иногда делая chroot (это, например, позволит использовать локальный passwd для обновления пароля). OpenStack делает это именно так.

Итоги

После добавки кода, управляющего образами дисков, рефакторинга и перенесения конфигов в yaml файл tiny_cloud практически готов к использованию. Можно добавить интеграцию с fabric, мониторинг, кеш пакетов и многое другое, но и так уже вполне годно к использованию. Разве что python API сделать удобное.

Ссылки:
          ru.wikipedia.org/wiki/RAW_%28%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%29
          administratosphere.wordpress.com/2008/05/23/sparse-files-what-why-and-how
          en.wikipedia.org/wiki/Logical_Volume_Manager_%28Linux%29
          en.wikipedia.org/wiki/Qcow#qcow2
          en.wikipedia.org/wiki/VDI_%28file_format%29#Virtual_Disk_Image
          en.wikipedia.org/wiki/VMDK
          www.vmware.com
          www.virtualbox.org
          www.vmware.com/support/ws45/doc/disks_vdiskmanager_eg_ws.html
          people.gnome.org/~markmc/qcow-image-format.html
          libvirt.org/formatdomain.html#elementsDisks
          linux.die.net/man/1/qemu-img
          nbd.sourceforge.net