Работа с виртуальными машинами KVM. Клонирование виртуальных машин

    Clone

    Продолжаем серию статей о виртуализации на базе KVM. В предыдущих статьях было рассказано об инструментарии, о настройке хост-машины и создании виртуальной машины. Сегодня мы поговорим о создании образа виртуальной машины и его клонировании.



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

    Для получения образа виртуальной машины в минимальной системе в ней достаточно поменять всего пару файлов чтобы получить нормально работающую систему, но в случае Debian появляются небольшие сложности.

    Для создания новой виртуальной машины на основе имеющейся системы нужно внести следующие изменения:

    • изменить hostname
    • поправить файл hosts
    • изменить настройки DNS
    • заменить хост-ключи SSH
    • изменить пароль для root


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

    Эту библиотеку написал Richard Jones из небезызвестной компании Red Hat. Она позволяет работать с файловыми системами (начиная от ext2 и заканчивая NTFS в Windows, UFS в FreeBSD — в общем, со всеми файловыми системами, с которыми умеет работать ядро), образами систем, LVM-разделами, в случае установки гостевых ОС из семейства MS Windows — править системный реестр (через библиотеку hivex). В общем, утилита очень богатая возможностями и очень гибкая. И что самое главное — не требует административных (root) прав для ее использования.

    Исследуем образ



    Итак, приступим к работе.

    Основным инструментом, с помощью которого мы будем работать с образом гостевой системы, является guestfish.

    Попробуем произвести некоторые операции в интерактивном режиме:

    $ guestfish
    ><fs> add-drive debian_5_i386.img
    ><fs> run
    ><fs> list-filesystems
    /dev/vda1: ext3
    ><fs> mount-vfs rw ext3 /dev/vda1 /
    ><fs> cat /etc/fstab
    # /etc/fstab: static file system information.
    #
    # <file system> <mount point> <type> <options> <dump> <pass>
    proc /proc proc defaults 0 0
    /dev/vda1 / ext3 errors=remount-ro 0 1
    /dev/hdc /media/cdrom0 udf,iso9660 user,noauto 0 0


    Что очень здорово — все необходимые операции можно производить и в неинтерактивном режиме (по заранее составленному сценарию). Приведу пример скрипта, который редактирует файлы hosts, hostname и interfaces в системе:

    $ guestfish <<EOF
        add-drive debian_guest.img
        run
        mount-vfs rw ext3 /dev/vda1 /
        upload -<<END /etc/hosts
    127.0.0.1 localhost.localdomain localhost debian_guest.local debian_guest
    10.10.10.100 debian_guest.local
    END
        upload -<<END /etc/resolv.conf
    nameserver 8.8.8.8
    END
        upload -<<END /etc/hostname
    debian_guest.local
    END
        upload -<<END /etc/network/interfaces
    auto lo
    iface lo inet loopback
    allow-hotplug eth0
    iface eth0 inet static
        address 10.10.10.100
        gateway 10.10.10.10
        netmask 255.255.255.0
        network 10.10.10.0
        broadcast 10.10.10.255
    END
    EOF


    Использование heredoc оказалось очень удобным в данном контексте.

    (К слову: если возникают какие-либо вопросы по библиотеке, на них сам автор очень быстро отвечает на IRC канале #libguestfs на irc.freenode.net. Да и вообще парень очень интересный.)

    Secure Hell



    Как видно из названия, я с этим вопросом достаточно долго промучился: в Debian/Ubuntu автоматической регенерации ключей при их удалении попросту нет. В других системах, которые я пробовал использовать, с этим всё в порядке, а для deb-based операционных систем с этим проблемы.

    Я сделал вот так:

    $ guestfish
    ><fs> add-drive debian_guest.img
    ><fs> run
    ><fs> mount-vfs rw ext3 /dev/vda1 /
    ><fs> download /etc/init.d/ssh /home/username/debian_5_etc_init_ssh


    Далее были сделаны следующие изменения:

    --- /home/username/debian_5_etc_init_ssh 2012-12-21 00:00:00.000000000 +0000
    +++ /home/username/debian_5_etc_init_ssh_fixed 2012-12-21 00:00:00.000000000 +0000
    @@ -32,6 +32,10 @@
    ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ]
    }

    +check_ssh_host_key() {
    +    if [ ! -e /etc/ssh/ssh_host_key ] ; then
    +        echo "Generating Hostkey..."
    +         /usr/bin/ssh-keygen -t rsa1 -f /etc/ssh/ssh_host_key -N '' || return 1
    +    fi
    +    if [ ! -e /etc/ssh/ssh_host_dsa_key ] ; then
    +        echo "Generating DSA-Hostkey..."
    +         /usr/bin/ssh-keygen -d -f /etc/ssh/ssh_host_dsa_key -N '' || return 1
    +     fi
    +    if [ ! -e /etc/ssh/ssh_host_rsa_key ] ; then
    +        echo "Generating RSA-Hostkey..."
    +        /usr/bin/ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N '' || return 1
    +    fi
    +}
    +
    check_for_no_start() {
        # forget it if we're trying to start, and /etc/ssh/sshd_not_to_be_run exists
        if [ -e /etc/ssh/sshd_not_to_be_run ]; then
    @@ -75,6 +79,7 @@

    case "$1" in
        start)
    +       check_ssh_host_key
            check_privsep_dir
            check_for_no_start
            check_dev_null
    @@ -106,6 +111,7 @@
        ;;

        restart)
    +       check_ssh_host_key
            check_privsep_dir
            check_config
            log_daemon_msg "Restarting OpenBSD Secure Shell server" "sshd"


    Внимание, патч нерабочий, он приведён как пример необходимых изменений.

    И для двух версий Debian/Ubuntu я сделал аналогичный файл с уже изменённым файлом ssh. Далее его можно просто загрузить в виртуальную машину.

    ><fs> upload /home/username/debian_5_etc_init_ssh_fixed /etc/init.d/ssh

    А теперь удалим ключи, чтобы они сгенерировались автоматически:

    ><fs> glob rm /etc/ssh_host_*_key*

    Удаление по маске не работает. Поскольку в API данный метод не реализован, префикс glob позволяет развернуть маску в список файлов.

    Для FreeBSD и CentOS достаточно просто удалить ключи, при старте они сами сгенерируются.

    Идентификация пользователей



    Для начала стоит рассказать о том, как представлено хранение информации о пользователях в Linux/FreeBSD. Это будет немного занудно, но необходимо для понимания того, что мы всё-таки делаем. Хотя по минимуму достаточно информации только о shadow-файле.

    Вся необходимая для аутентификации пользователей хранится в файлах /etc/passwd и /etc/shadow(/etc/master.passwd в FreeBSD).

    Рассмотрим структуру файла /etc/passwd

    root:x:0:0:root:/root:/bin/bash

    Процитирую из вики порядок использования полей:

    • регистрационное имя или логин
    • хеш пароля (сейчас не используется, используется скрытый в shadow пароль)
    • идентификатор пользователя
    • идентификатор группы по умолчанию
    • информационное поле GECOS
    • начальный (он же домашний) каталог
    • регистрационная оболочка, или shell


    Рассмотрим структуру /etc/shadow

    root:$1$APv1HQOB$HJQhYFq9JSnhusQ.1Ql10.:14977:0:99999:7:::

    Опять же из wiki:

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


    Нам нужно изменить конкретно второе поле (хэш пароля). Его можно разбить на три части:

    • 1 — тип шифрования md5, 2 — SHA512 (поправьте меня если я не прав)
    • APv1HQOB — соль, через которую шифруется пароль
    • HJQhYFq9JSnhusQ.1Ql10. — непосредственно хэш пароля с солью.


    Хэш генерируется командой:

    $ mkpasswd --method=md5 --salt="APv1HQOB" "$password"
    $1$APv1HQOB$HJQhYFq9JSnhusQ.1Ql10.


    Его нам и нужно подставить в файл /etc/shadow.

    Я написал небольшой скрипт, который будет генерировать случайный пароль и соль длиной 8 символов, выводить его, генерировать хэш и подставлять его в нужный файл:

    #!/bin/bash
    tempfile=`mktemp`
    shadow="/etc/shadow"
    salt=`pwdgen`
    passwd=`pwdgen`
    hash=`pwhash $salt $password`
    hash_esc=`escape_hash $hash `
    pwdgen() {
        charspool=('a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' '0' 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z');
        len=${#charspool[*]}
        for c in $(seq 8); do
            echo -n ${charspool[$((RANDOM % len))]}
        done
    }

    pwhash(){
        salt=$1
        password=$2
        hash=`mkpasswd --method=md5 --salt=$salt $password`
        echo $hash
    }

    # Функция нужна, чтобы sed корректно отработал закрывающие слэши и знаки $
    escape_hash() {
        echo $1 | sed -e 's/\//\\\//g' -e 's/\$/\\\$/g'
    }

    guestfish <<EOF
    add-drive debian_guest.img
    run
    mount-vfs rw ext3 /dev/vda1 /
    download /etc/shadow $tempfile
    ! sed 's/^root:[^:]\+:/root:$hash_esc:/' $tempfile > $tempfile.new
    upload $tempfile.new $shadow
    EOF


    Как вы наверняка заметили, мы использовали внешнюю команду внутри скрипта, в которой мы заменили содержимое первой секции на полученный в скрипте хэш. Для этого используется внешний оператор "! ": он очень удобен, когда нам нужно сделать какие-то небольшие операции, не прерывая процесс работы с guestfish (поскольку на запуск guestfish всё таки требуется некоторое время).

    Подготовка мастер-образа



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

    Что нам нужно убрать в нашем образе:

    1. Очистить логи
    2. Удалить следы пребывания в системе
    3. Удалить скачанные пакеты (актуально для Debian и Ubuntu, только они мусорят)
    4. Удалить файл с настройками сетевой карты
    5. Удалить ключи.


    После этого нам нужно будет уменьшить размер файловой системы, уменьшить раздел и отрезать от образа лишнее.

    Приложу небольшой участок кода, который выполняет первую часть необходимого действия:

    guestfish <<EOF
    add-drive debian_guest.img
    run
    mount-vfs rw ext3 /dev/vda1 /
    upload /home/username/debian_5_etc_init_ssh_fixed /etc/init.d/ssh
    -glob rm /etc/ssh/ssh_host_*
    -glob rm /etc/udev/rules.d/70-persistent-net.rules
    -glob rm /root/*
    -glob rm /root/.*
    -glob rm /var/log/*
    -glob rm /var/cache/apt/archives/*deb

    EOF

    Флаг "-" перед командой означает, что мы не должны выходить, если какая-то из команд вернёт -1. Это сделано специально, чтобы отсутствие каких-либо файлов не прерывало выполнение остальных команд; таким образом, кастомизация данного скрипта для различных дистрибутивов становится не нужной, хотя она и возможна.

    А теперь приступим к уменьшению образа:

    $ guestfish <<EOF
    add-drive add-drive ${images}/${os}_${version}_${arch}.img
    run
    e2fsck-f /dev/vda1
    resize2fs-M /dev/vda1
    tune2fs /dev/vda1 | grep "Block count:" | sed -e 's/Block\ count:\ //g' -e 's/$/*4+2144/g' | bc > /tmp/block_count
    EOF
    $ foo=`cat /tmp/block_count`
    $ guestfish <<EOF
    allocate debian_guest_minimal.img ${foo}k
    EOF


    Цифра 2144 — это размер загрузчика и таблицы разделов.

    Вкратце суть проделанного в следующем: мы ужимаем файловую систему до минимального размера, вычисляем, сколько она стала занимать (минимальное количество блоков), и умножаем их на 4, поскольку размер блока 4 кбайта, после чего создаём образ полученной величины.

    После этого нам необходимо будет воспользоваться утилитой virt-resize из комплекта утилит libguestfs, чтобы перенести получившуюся файловую систему в новый, меньший образ.

    $ virt-resize --shrink /dev/vda1 debian_guestl.img debian_guest_minimal.img

    Следует сразу обговорить ограничения данного метода: это применимо только для файловых систем ext2-4, поскольку resize2fs работает только с ними. Для чего-то нестандартного можно легко допилить нужный функционал (правда, как я уже упоминал ранее, libguestfs очень сложно собрать). Для образца можно посмотреть мой патч для реализации resize2fs-M.

    К сожалению с FreeBSD всё сильно сложнее, и пока нет никаких вариантов решения проблемы с ней кроме добавления в конфиг виртуальной машины ещё одного диска и его монтирования.

    Теперь же мы должны, по желанию, конечно, упаковать получившийся образ при помощи xz (это долго, но результат стоит того):

    $ xz -9 debian_guest.img
    $ ls -lsha debian_guest.img.xz
    107M -rw-r--r-- 1 username username 107M Dec 21 00:00 debian_guest.img.xz


    Разворачивание образа



    Итак, образ виртуальной машины мы получили, но образы — это не готовые рабочие системы. Чтобы получить рабочую систему, нам нужно произвести несколько операций:

    1. Аллоцировать образ на диск
    2. Скопировать загрузчик и таблицу разделов
    3. Перенести информацию из шаблона в образ виртуальной машины
    4. Расширить файловую систему
    5. Сменить пароль root
    6. прописать сетевые настройки


    Для Linux всё элементарно: в составе libguestfs есть замечательная утилита, написанная на OCaml — virt-resize, пункты с 2 по 4 выполняются ею без проблем.

    По ряду причин на '''guestfish''' реализовать изменение размера диска невозможно (копирование mbr в guestfish невозможно), посему нужно использовать более функциональные средства.

    $ guestfish <<EOF
    allocate debian_guest_clone.img 10G
    EOF
    $ virt-resize --expand /dev/vda1 debian_guest.img debian_guest_clone.img


    Собственно, это все, что минимально требуется знать для осуществления клонирования образов виртуальных машин.

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

    Подробнее
    Реклама

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

      +2
      Спасибо за статьи. Ждем продолжения :)
        +3
        Всегда пожалуйста.
        0
        а зачем эта страшная либа, если есть kpartx?
          +1
          Для kpartx нужны рутовые права + у kpartx нет развесистого API для работы с кучей языков программирования. И по большому счёту libguestfs предоставляет возможность использовать её в том числе и для массовых манипуляций с конфигами виртуалок.

          + В составе libguestfs есть демон guestfsd, который можно запускать внутри системы и через канал в libvirt общаться с живой операционкой.
            +1
            это всё понятно, но извращения со сборкой сводят на нет все преимущества библиотеки.
              +1
              Какие извращения? Берёте с сайта библиотеку, ставите и получаете результат.
              Извращения со сборкой начинаются, когда нужно добавить какой-то функционал, да и то проблемы в основном при сборке из git. Из уже готовых архивов всё намного проще.
              0
              кстати по поводу рутовых прав. а как вы склонируете систему, у которой диск это LVM раздел без рутовых прав? они все равно нужны.
              да и API не сильно то и нужно, если всё равно основной код будет на шеле.
                +1
                Я с lvm и libguestfs особо не работал, но вообще действительно, для изменения раздела lvm нужны рутовые права.
                Я для частных нужд советую использовать именно LVM, поскольку тогда не будет лишней прослойки в виде файловой системы и администратор может более гибко распределять ресурсы диска, а для нужд хостинга разумно использовать raw или qcow2 образы.

                На bash реализовать клонирование невозможно, потому что при клонировании нужно копировать загрузочную запись. Нужно обязательно использовать более мощные языки.
                + API необходимо когда вы делаете некоторую более серьёзную работу с виртуалками, нежели обычное клонирование.
                  +1
                  хехе
                  я бы на вашем месте не говорил слово невозможно.
                  извените а зачем копировать загрузочную запись? dd не устраивает?
                  и не обязательно использовать более мощные языки, это если вы плохо знаете простые програмки UNIX — тогда да.
                  что вы подразумеваете под понятием более серьёзную работу с виртуалками?
                    +1
                    Ок.
                    Представим ситуацию, у нас есть N серверов, к которым у нас есть доступ через некоторое API, допустим SOAP, через который мы управляем конфигурацией хост машин, занимаемся деплойментом виртуальных машин, изменением их конфигураций, управлением этимим виртуалками. + Ко всему этому сбор статистики, нотификации и прочее.
                    И в самом низком уровне у вас лежит набор bash скриптов, которые вы дёргаете через exec с какими-либо параметрами?
                    Как то это не очень хорошо.

                    С известными извращениями на bash я даже делал генератор статического сайта(и один раз делал не очень статического), но зачем если всё тоже самое можно сделать намного проще, на тех языках, которые предназначены для этого.
                      +1
                      вот видите, Вы уже говорите что это не «не возможно», а «не очень хорошо».
                      Кстати по поводу такого API советую посмотреть в сторону OpenStack.
                        +1
                        Про «невозможно» я говорил касаясь конкретно libguestfs, там можно скопировать какие то участки с начала диска, но конкретно нужную часть вырезать у диска без примеренения нормальных языков, как раз невозможно.

                        «Не очень хорошо» касаясь того, что для собственных нужд bash очень хорошо подходит, а когда нужно сделать что то целостное, то лучше перенести всё это в какую-то одну сущность.

                        Про OpenStack знаю, спасибо, но в ближайшее время мы API открывать не будем. Позже — вполне возможно, но не прямо сейчас.
                          +1
                          как это невозможно? там что вычисления не используют "+", "-", "/" и "*"?
                          в принципе на баше можно написать всё, было бы желание и опыт. и это как раз одна сущность. быстро, дешево и сердито.

                          а зря, в том-то и проблема что все хостеры делают свои велосипеды, почему бы не взять то что уже работает? кстати OpenStack использует API совместимое с RackSpace и Amazon EC2, что очень удобно как для тех кто не работал с ним и в будущем все таки прийдется и для тех кто уже работал и не надо изучать новое.
                            +1
                            На bash можно написать почти всё.

                            Во многих случаях велосипеды вполне обоснованы. Хотя бы тем что нужно встраивать что то новое в уже существующую инфраструктуру. Если понадобится, можно вполне мигрировать на другую инфраструктуру, но на это нужно время, деньги и люди.
                              0
                              я Вас достаю не потому что Вы написали что-то не нужное или неинтересное, но потому что Вы слишком категоричны в высказываниях.
                                0
                                Бывает, вот что слишком крепкий кофе делает с людьми. Я обычно мягкий и шелковистый, пока не побрился налысо :)
                                  0
                                  попробуйте зеленый чай, там кофеина больше, а вреда для организма меньше ;)
                                    +1
                                    Я кофе обычно как некоторый деликатес использую. По праздникам, так сказать.
                                    А зелёный чай у меня обычно настолько крепкий, что лучше уж кофе :)
              0
              Кстати про kpartx, статья появилась про изменение размера образов виртуальных машин.
              +1
              Хотелось бы аналогичную статью для win виртуальных машин
                +1
                Когда будем предоставлять услуги Windows виртуалок, тогда напишу. Но в принципе в msdn должно быть написано что нужно менять, а вообще windows как мне кажется лучше ставить с установочного диска, а изменение размера вполне возможно для ntfs из linux.
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Окей, ваш способ тоже работает. Но в условии когда у нас есть Nцать образов, с разными операционными системами, и тут у нас приехало на все(или на часть) обновление безопасности. Запустили все базовые виртуалки, к которым подключены образы, обновили всё, запустили подготовленный скрипт и идём пить чай.

                  И когда у нас много образов, экономия места внезапно становится очень существенной.

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

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