Приветствую. Нередко в крупных проектах используются довольно большие наборы одинаковых серверов, имеющих одинаковую программную конфигурацию(читай - корень). И нередко у администраторов этих машин возникает необходимость поддерживать их в симметричном состоянии - одинаковые наборы пакетов, конфигов, и т.д. и т.п. В качестве одного из решений этой проблемы предлагается загрузка таких машин по сети, дабы они имели общий корень и держали его в 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;
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) и говорим:
Данная команда запакует образ, как 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-скрипт, который работает следующим образом:
В заголовке мы объявляем VEID'ы "моделей" (виртуалок, с которых будем снимать образы) и dhcp-сервера. Если ваш dhcp-сервер живёт не на VZ-контейнере - просто поправьте $out_path)
Для каждой виртуалки, указанной в скрипте, проверяется время изменения /var/lib/apt/extended_states(этот файл изменяется каждый раз при измнении списка пакетов - установке/обновления чего либо через apt-get), полученный тайм-штамп записывается в /var/cache/netboot/номер(директория должна существовать).
Если тайм-штамп из кэша скрипта ниже, чем время изменения проверяемого файла - виртуалка, останавливается.
Далее, зачищается /var/cache/apt(для экономии места) и собирается cpio-образ, после чего виртуалка запускается обратно. Предварительно, выполняется предыдущий скрипт, разбирающий хард-линки. Считается, что он лежит в директории, описанной в PATH, а сам он называется nb_links_breaker.
В финальной стадии cpio-образ жмётся gzip`ом, перемещается в нужное место(по дефолту - /var/lib/tftpboot/images/имя_виртуалки/дата и к нему прикладывается линк от current.
Как видите, скрипт простейший и при желании его можно переписать под реакцию на любое другое изменение. Можно повесить его cron и он будет сам следить за процессом, собирая по мере необходимости образы.
Итого
Явных минусов у предложенного варианта два - расход ОЗУ на использование RAM-диска и большее время загрузки по сравнению с жесткими дисками(засчёт получения образа и его распаковки). Обратная сторона - получаемый функционал: один раз собрав образ, вы получите возможность ввести в строй новый сервер за считанные минуты, а каждый собранный образ может служить точкой отката в случае неудачного обновления.
Разумеется здесь описаны только общие принципы и манипуляции, необходимые для получения базовой системы, запакованной в initrd с возможностью загрузки по сети. Различные варианты допиливания, типа конфига mdadm`a и маленьких скриптиков, облегчающих жизнь(например, для разметки дисков на новом сервере) остаются на усмотрение читателя.
Надеюсь, что это кому-нибудь будет пригодится. Спасибо за внимание. :)
Иcтoчник