16 марта 2011 в 03:29

Сервер из образа: 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 и маленьких скриптиков, облегчающих жизнь(например, для разметки дисков на новом сервере) остаются на усмотрение читателя.

Надеюсь, что это кому-нибудь будет пригодится. Спасибо за внимание. :)
Рассел @mixermsk
карма
40,0
рейтинг 0,0
Самое читаемое Администрирование

Комментарии (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 перестал ругаться на конфиг =(

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