Сервер из образа: DHCP + TFTP + Initrd + OpenVZ

    Приветствую. Нередко в крупных проектах используются довольно большие наборы одинаковых серверов, имеющих одинаковую программную конфигурацию(читай — корень). И нередко у администраторов этих машин возникает необходимость поддерживать их в симметричном состоянии — одинаковые наборы пакетов, конфигов, и т.д. и т.п. В качестве одного из решений этой проблемы предлагается загрузка таких машин по сети, дабы они имели общий корень и держали его в RAM, а хранимые данные(например /var/www для веб-серверов) держали на жестких дисках, монтируемых после загрузки. Об этом и поговорим.

    Пара слов о том, что будем делать

    Одна из целей, которую я ставил — максимальная простота. Поэтому в этой статье используется минимальный набор инструментов, а сами они максимально просты в использовании. Образ мы будем паковать прямо в initrd, а выдавать его тем же TFTP-сервером, который будет отдавать ядро и загрузчик.
    В качестве опционального продолжения, в последнем разделе я расскажу о применении OpenVZ в этой задаче — на мой взгляд, работать с виртуальным хостом удобней, чем с chroot-окружением — особенно, если вы производите массовые обновления серверов(да и для банального тестирования удобно).
    Если вы решите применять OpenVZ — подумайте над тем, чтобы поселить DHCP и TFTP сервер внутри VZ-контейнера(особенно, если у вас уже есть несколько хост-систем) — это позволит развернуть DHCP-сервер в случае отказа основной хост-системы.
    Итак, поехали.

    DHCP

    Здесь всё просто. Я использовал dhcpd. В дефолтный конфиг вписываем:
    #Включаем возможность передавать параметры из dhcpd в pxelinux
    option space pxelinux;
    option pxelinux.magic code 208 = string;
    option pxelinux.configfile code 209 = text;
    option pxelinux.pathprefix code 210 = text;
    option pxelinux.reboottime code 211 = unsigned integer 32;

    site-option-space "pxelinux";
    option pxelinux.magic f1:00:74:7e;
    if exists dhcp-parameter-request-list {
    option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list,d0,d1,d2,d3);
    }

    group
    {
    option pxelinux.configfile "configs/bla-bla.ru/config"; #Общий конфиг pxelinux`a для группы хостов
    filename "/var/lib/tftpboot/pxelinux.0"; #Сам загрузчик
    #Первый хост
    host first.bla-bla.ru
    {
    hardware ethernet 48:5b:39:90:b9:06; #MAC первого сервера
    fixed-address 192.168.0.100;
    option host-name "first.bla-bla.ru";
    }
    #Второй хост
    host second.bla-bla.ru
    {
    hardware ethernet 48:5b:39:90:b9:07; #MAC второго сервера
    fixed-address 192.168.0.101;
    option host-name "second.bla-bla.ru";
    }
    }

    subnet 192.168.0.0 netmask 255.255.255.0 #Сеть, в которой живёт наш dhcp-сервер
    {
    option routers 192.168.0.1; #Шлюз
    option domain-name-servers 192.168.0.2; #DNS
    range 192.168.0.100 192.168.0.150; #Какие адреса будем выдавать
    }

    deny unknown-clients; #Незнакомым не отвечать


    Рестартим dhcpd. Теперь оно будет предлагать машинкам загрузиться по сети.

    TFTP

    Для TFTP использовался tftp-hpa. Для его корректной работы в /etc/xinetd.d/tftp(в ubuntu файл нужно было создать самостоятельно) вписываются строки:

    service tftp
    {
    port = 69
    socket_type = dgram
    wait = yes
    user = root
    server = /usr/sbin/in.tftpd
    server_args = /var/lib/tftpboot
    disable = no
    }


    Как видно из конфига, всё добро, предназначенное для загрузки машинок по сети мы будем хранить в /var/lib/tftpboot. Рестартим xinetd. TFTP теперь тоже готов

    Загрузчик(PXELinux)

    PXELinux — это брат-близнец SYSLinux`a(который используется, наверно во всех установочных Linux-CD), но ориентированный на загрузку по сети. Сам загрузчик я взял из netboot-образа Ubuntu, хотя, подозреваю, есть и другие способы его получения. Кладём бинарник(pxelinux.0) в /var/lib/tftpboot/. Его дефолтные конфиги могут лежать в поддиректории pxelinux.cfg, тогда в качестве основного будет использоваться default(полный путь для него будет таким: /var/lib/tftpboot/pxelinux.cfg/default). Но мы указали в конфиге dhcpd, что сервера группы «bla-bla.ru» будут забирать свои конфиги из configs/bla-bla.ru/config(полный путь /var/lib/tftpboot/configs/bla-bla.ru/config). Поэтому создаём необходимые директории и пишем в конфиг:

    default linux
    timeout 100

    label linux
    kernel kernels/vmlinuz-2.6.32
    append panic=15 initrd=images/bla-bla.ru/current


    Как видно, ядро будет храниться в kernels/, а cам образ в images/. Версию ядра, вы, конечно же, укажете позже свою. Но всё это потом, а пока мы займёмся изготовлением самого образа.

    Готовим образ

    Как я уже сказал, образ наш будет храниться прямо в initrd. Этот путь гораздо проще, нежели применение SquashFS + UnionFS(т.к. последний не входит в ядро, а значит требует хронических перекомпиляций последнего). И проще/надёжней nfs_root т.к. не требует дополнительных сервисов, которые могут стать дополнительными точками отказа.
    Я собирал свой образ в Ubuntu, для Debian процедура будет аналогичной. Итак, нам нужен debootstrap. Создаём где-нибудь директорию(пусть это будет /root/image) и разворачиваем в неё образ. Ну, например, Ubuntu Lucid:

    debootstrap lucid /root/image

    Теперь chroot`имся туда и ставим ядро(в чруте). Выходим из chroot`a, а полученное ядро тащим в /var/lib/tftpboot/kernels/ и вписываем его в конфиг из предыдущего раздела. Кстати, из самого окружения его можно удалить, чтобы оно не занимало драгоценное место.
    Как вы уже, наверно, поняли — это окружение и есть будущий образ. Сюда же вы можете ставить нужные пакеты и править конфиги. Так же вам обязательно нужно создать /etc/fstab(даже если вы не хотите его заполнять, хотя /proc я бы туда всё же вписал ;)) и файл init в корне(без него почему-то получался Kernel Panic, хотя на файле намеренно не стояло прав на выполнение, а содержимое было пустым). Так же советую вписать получение ip-адресов по dhcp в /etc/network/interfaces — наш DHCP-сервер может выдавать статические адреса.
    Теперь мы готовы собрать образ. Но перед этим вам нужно знать об одной особенности процесса: cpio — не умеет паковать хардлинки. Это значит, что если их не разобрать — вы лишитесь некоторых утилит — например ifup,ifdown,mkfs и некоторых других файлов.
    Для решения этой проблемы я написал на Perl`е небольшой костыль, который их разбирает: код тут. Говорим этому скрипту в качестве параметра директорию /root/image, дабы он разобрал там линки. Далее заходим в директорию с окружением(/root/image) и говорим:

    find | cpio -H newc -o -p > ../bla-bla.ru; gzip -9 ../bla-bla.ru;cp ../bla-bla.ru.gz /var/lib/tftpboot/images/bla-bla.ru/2011-03-16;ln -s /var/lib/tftpboot/images/bla-bla.ru/2011-03-16 /var/lib/tftpboot/images/bla-bla.ru/current

    Данная команда запакует образ, как initrd, положит в нужное место и создаст на него ссылку с current(который указан в конфиге).
    Вот и всё! При попытке загрузиться по сети машинка должна раскрутиться из созданного нами образа.

    OpenVZ

    С помощью OpenVZ мы будем делать живую модель образа. Он(OpenVZ) удобен для наших целей прежде всего всё тем, что образы VZ-виртуалок хранятся на диске «как есть» в виде дерева, а поэтому их удобно паковать. В моём случае я пошел чуть дальше и собрал DHCP+TFTP тоже внутри OpenVZ-контейнера(отдельного, разумеется).
    Если вы так же хотите использовать OpenVZ в решении этой задачи — будем считать, что сервер со всем необходимым для работы этой системы виртуализации(поддержка в ядре, vzctl) у вас уже настроен — благо, документации на эту тему хватает.

    Создадим виртуалку:

    vzctl create 100 --hostname model.bla-bla.ru

    Запускать её пока не надо. Удаляем всё содержимое private-директории( по дефолту /var/lib/vz/private/100, где 100 — VEID указанный при создании) и кладём в неё дерево директорий, которое мы сделали в предыдущем пункте.

    Если вы уже успели вписать авто-получение сетевых настроек в разворачиваемую из образа систему — не поленитесь вписать в конфигурацию DHCP-сервера и выдачу адреса этой виртуалке(MAC-адрес контейнера можно узнать, зайдя в него из консоли хост-системы через vzctl enter и выполнив там ifconfig), иначе она рискует остаться без сети.

    Запускаем:

    vzctl start 100

    При положительном исходе виртуалка сходит к DHCP-серверу за адресом и станет доступна по сети. Теперь вы можете работать с ней точно так же, как с полноценной машиной, из которой будут создаваться образы.
    Для автоматической сборки образов я написал небольшой Perl-скрипт, который работает следующим образом:
    1. В заголовке мы объявляем VEID`ы «моделей»(виртуалок, с которых будем снимать образы) и dhcp-сервера. Если ваш dhcp-сервер живёт не на VZ-контейнере — просто поправьте $out_path)
    2. Для каждой виртуалки, указанной в скрипте, проверяется время изменения /var/lib/apt/extended_states(этот файл изменяется каждый раз при измнении списка пакетов — установке/обновления чего либо через apt-get), полученный тайм-штамп записывается в /var/cache/netboot/номер(директория должна существовать).
    3. Если тайм-штамп из кэша скрипта ниже, чем время изменения проверяемого файла — виртуалка, останавливается.
    4. Далее, зачищается /var/cache/apt(для экономии места) и собирается cpio-образ, после чего виртуалка запускается обратно. Предварительно, выполняется предыдущий скрипт, разбирающий хард-линки. Считается, что он лежит в директории, описанной в PATH, а сам он называется nb_links_breaker.
    5. В финальной стадии cpio-образ жмётся gzip`ом, перемещается в нужное место(по дефолту — /var/lib/tftpboot/images/имя_виртуалки/дата и к нему прикладывается линк от current.


    Как видите, скрипт простейший и при желании его можно переписать под реакцию на любое другое изменение. Можно повесить его cron и он будет сам следить за процессом, собирая по мере необходимости образы.

    Итого

    Явных минусов у предложенного варианта два — расход ОЗУ на использование RAM-диска и большее время загрузки по сравнению с жесткими дисками(засчёт получения образа и его распаковки). Обратная сторона — получаемый функционал: один раз собрав образ, вы получите возможность ввести в строй новый сервер за считанные минуты, а каждый собранный образ может служить точкой отката в случае неудачного обновления.
    Разумеется здесь описаны только общие принципы и манипуляции, необходимые для получения базовой системы, запакованной в initrd с возможностью загрузки по сети. Различные варианты допиливания, типа конфига mdadm`a и маленьких скриптиков, облегчающих жизнь(например, для разметки дисков на новом сервере) остаются на усмотрение читателя.

    Надеюсь, что это кому-нибудь будет пригодится. Спасибо за внимание. :)
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 6
    • 0
      > Но перед этим вам нужно знать об одной особенности процесса: cpio — не умеет паковать хардлинки

      Ошибаетесь, нормально пакует.
      • 0
        Поделитесь опытом? Я неизменно получал файлы нулевого размера
        • 0
          Чеснослово, ничего специально не делал. Обычный find. | cpio -o -H newc На файлы нулевого размера ни разу не попадал.

          М.б. у вас файлы на какой-нибудь специфичной файловой системе лежат, у которой cpio мета-данные не очень удачно понимает.
      • 0
        Примерно то же самое, только SuSE, KIWI, Xen и выкачивание образа по HTTP с проверкой чексумм.
        Сложнее в настройке? Не особо.
        Сложнее процесс загрузки? Да, но при этом получающаяся система может раскатываться на диск (и поддерживаться в обновленном состоянии), имея при этом защиту от ошибок (кажется, там MD5 проверяется).
        Все-таки, жизнь внутри initrd — удовольствие, как мне кажется, не для слабонервных. Сказываются ограничения TFTP, файлуха раскатывается в память полностью (в отличие от squashfs, например), да и вообще есть в этом некоторое нарушение предназначения initrd.
        • 0
          По сравнению с вашей схемой, если я правильно понял, nfs_root лишь добавляет точку отказа. Его использование только немного увеличит скорость загрузки, за счет того что не будут передаваться лишние файлы… Хотя это вообще спорный вопрос… Какого размера у вас образ получается в среднем?

          Кстати squshfs и aufs ведь уже достаточно давно в основной ветке. Не должно так то быть проблем с ними…
          • 0
            А dhcpd какой версии использовали, 3? У меня в 4.1 ругается на site-options 208-211, мол нельзя их как local объявлять из-за RFC в котором их назначение теперь жестко указано. Я так понял, что теперь можно просто прописывать в конфиге:
            configfile = «examleconfig»;
            pathprefix = "/pxelinux.cfg/";
            но у меня так почему-то не заработало, хотя dhcpd перестал ругаться на конфиг =(

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