UMGUM.COM (лучше) 

ISC-DHCPd + InCron ( Настройка автоматизированного распространения изменений конфигурации в спарке DHCP-серверов. )

20 сентября 2017  (обновлено 13 декабря 2017)

OS: Linux Debian 6/7/8/9.
Application: ISC-DHCPd, InCron и RSync.

Начальные условия: располагаем корпоративной и провайдерской сетью с десятком-другим тысяч пользователей, от которых исходит густой поток DHCP-запросов на выдачу сетевых настроек.

Задача: учитывая то, что прерывать обслуживание приносящих деньги клиентов нельзя, а традиционно используемое в Linux приложение ISC-DHCPd не поддерживает применение произвольной новой конфигурации без полного перезапуска, необходимо наладить предоставление сервиса с двух идентично настроенных серверов, одновременно и по отдельности могущих обработать весь поток запросов, по возможности автоматизировав распространение настроек с условного "первичного" сервера на "вторичный".


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

# apt-get install isc-dhcp-server incron rsync tree

Настройка кластеризации ISC-DHCPd.

В "этом вашем Linux" ещё с прошлого века прижился DHCP-сервер разработки американской "Internet Systems Consortium", решения которой не блещут по нынешним временам изящностью, но работают везде. С ростом популярности и повышением требований к непрерывности сервиса для распределения нагрузки в него вкрутили куцый протокол обмена записями о клиентских состояниях и отчасти балансировки.

Работает это примерно так:

Каждый из DHCP-серверов сразу после запуска приступает к автономному обслуживанию клиентских запросов, сохраняя состояния таковых в файле "dhcpd.leases" (по умолчанию располагающемся в директории "/var/lib/dhcp/").

При запуске и периодически в процессе работы DHCP-сервер пытается подключится к указанному в настройках "failover" серверу-соседу и обменяться с ним данными о сохраняемых в файле "dhcpd.leases" клиентских состояниях, по возможности синхронизируя таковые.

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

Развал "кластера" или рассинхронизация сведений о состояниях практически не влечёт за собой глобальных сбоев - как правило конфликты "lease"-ов решаются автоматическим отзывом проблемных адресов с последующей их выдачей по вынужденному запросу клиента.

Настройка взаимодействия между серверами-соседями (их может быть только два: "primary" и "secondary" - дальнейшее масштабирование распределённой схемы обслуживания невозможно) весьма проста и укладывается в один блок параметров (в данном примере без распределения ролей балансировки нагрузки):

# vi /etc/dhcp/dhcpd.conf

....
# Блок описания параметров взаимодействия DHCP-серверов
failover peer "failover-partner" {

  # Роль в спарке (primary|secondary)
  primary;

  # FQDN/IP-адрес и TCP-порт этого сервера для обслуживания входящих запросов серверных взаимодействий
  address dhcp.example.net;
  port 519;

  # FQDN/IP-адрес и TCP-порт сервера-соседа
  peer address dhcp2.example.net;
  peer port 519;

  # Задержки ожидания ответа сервера-соседа
  # (в отсутствии связи сервер перейдёт в автономный режим обслуживания всех поступающих запросов)
  max-response-delay 60;
  max-unacked-updates 10;
}

Естественно, параметры взаимодействия нужно применить к обеим DHCP-серверам, определив каждому из них свои роли и настройки сетевого подключения, взаимно противоположные.

Сервер ISC-DHCPd принимает параметры конфигурации только путём полного перезапуска (даже в будущем разработчиками не планируется поддержка перезагрузки по чему-то вроде сигнала SIGHUP), так что важно предварительно убедится в корректности внесённых изменений:

# dhcpd -t -cf /etc/dhcp/dhcpd.conf
# /etc/init.d/isc-dhcp-server restart

Обязательно просматриваем свежие записи журнала событий (настраивается отдельно):

# tail -1000 /var/log/dhcpd.log

....
dhcpd: For info, please visit https://www.isc.org/software/dhcp/
dhcpd: Internet Systems Consortium DHCP Server 4.1.1-P1
....
dhcpd: Wrote 0 deleted host decls to leases file.
dhcpd: Wrote 0 new dynamic host decls to leases file.
dhcpd: Wrote 32736 leases to leases file.
dhcpd: failover peer failover-partner: I move from normal to startup
dhcpd: failover peer failover-partner: peer moves from normal to communications-interrupted
dhcpd: failover peer failover-partner: I move from startup to normal
....
dhcpd: balancing pool 1316a70 10.20.30.0/24 total 245 free 97 backup 148 lts -25 max-own (+/-)25
dhcpd: balanced pool 1316a70 10.20.30.0/24 total 245 free 97 backup 148 lts -25 max-misbal 37
dhcpd: Sending updates to failover-partner.

Справедливости ради надо отметить, что в свежих версиях ISC-DHCPd реализована поддержка API изменения конфигурации "на лету", которым можно пользоваться посредством утилиты "omshell", но пока функционал очень урезан и вообще кривовато работает, так что проще держать два сервера, каждый из которых по отдельности можно перезагружать безболезненно для клиентов.

Упорядочивание файлов конфигурации ISC-DHCPd.

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

Практическая реализация простая:

Один (корневой "/etc/dhcp/dhcpd.conf") конфигурационный файл описывает параметры сопряжения "failover peer";

Все остальные параметры, одинаковые для "главного" и "вторичного" DHCP-серверов описания клиентских подсетей, выносятся в директорию "/etc/dhcp/dhcpd.subnets/", которая будет синхронизироваться между таковыми.

Выглядит это примерно так:

# tree /etc/dhcp/

/etc/dhcp/
├── dhcpd.conf
├── dhcpd.subnets
│   ├── main.conf
│   ├── subnet_one.conf
│   ├── subnet_two.conf
│   ├── ....
│   └── subnets.conf
└── README

Чтобы хоть как-то сориентировать желающего внести дополнения в конфигурацию клиентских подсетей я оставляю в корне директории настроек сервера файл с описанием его статуса "/etc/dhcp/README", что в сочетании с говорящими именами вроде "dhcp.example.net" и "dhcp2.example.net" позволяет надеятся на то, что новенький специалист не станет изменять настройки "вторичного" DHCP-сервера вместо "первичного":

Этот сервер "dhcp.example.net" считается главным (первичным) и настройки клиентских подсетей нужно изменять на нём. Все изменения автоматически распространяются на вторичный сервер "dhcp2.example.net" посредством "InCron+RSync".

Отслеживание факта изменения конфигурационных файлов.

Несмотря на видимое в первом приближении удобство взаимной безусловной синхронизации работающих в сущности равноценно DHCP-серверов, практически одинаково обслуживающих клиентов (условное разделение в функционале "failover" на "первичный" и "вторичный" серверы обозначает лишь главенство в разрешении конфликтов среди выданных клиентам адресов, но конфигурация подсетей при этом у них полностью идентична) на практике надёжнее автоматически распространять конфигурацию только в одну сторону, от условно "первичного" DHCP-сервера к "вторичному".

Для этого настраиваем две цепочки процессов:

На "первичном" мониторим изменения файлов и по мере накопления событий высылаем обновления "вторичному" посредством RSync.

На "вторичном" мониторим поступившие изменения и, по мере накопления событий, перезапускаем DHCP-сервер для принятия изменений.

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

Отслеживать события модификации конфигурационных файлов будем с помощью приложения "InCron", опирающегося на функционал ком­понента ядра Linux "inotify". "InCron" напоминает привычный "Cron", но выполняет действия не по наступлению даты и времени, а по событиям в файловой системе, поступающим от ядра Linux посредством уведомлений "inotify". Это просто, немедленно и минимально по затратам системных ресурсов.

Обработка событий изменения конфигурации "первичного" DHCP-сервера.

Заранее заготовим Bash-скрипт, который будет реализовывать логику обработки событий.

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

# mkdir -p /etc/incron.scripts
# vi /etc/incron.scripts/rsync-dhcpd.sh
# chmod ug+x /etc/incron.scripts/rsync-dhcpd.sh

#!/bin/bash

SOURCE="/etc/dhcp/dhcpd.subnets/"
TARGET="rsync://dhcp2.example.net:873/etc-dhcpd-subnets/"
COUNTER="/var/run/incron/vars/RSYNC_DHCPD_COUNT"

# Накручиваем счётчик количества вызовов
COUNT=$(($(cat "${COUNTER}")+1))

# Запускаем процедуру только после некоторого количества вызовов скрипта
if [ "${COUNT}" -gt "5" ]
then

  # Запускаем синхронизацию только в том случае, если файл конфигурации DHCP-сервера корректен
  /usr/sbin/dhcpd -f -t -q 2>&1 >/dev/null && /usr/bin/rsync --checksum --recursive --delete "${SOURCE}" "${TARGET}"

  # Сбрасываем счётчик количества вызовов скрипта
  COUNT=0
fi

# Сохраняем переменную счётчика
[ -f "${COUNTER}" ] || mkdir -p "$(dirname "${COUNTER}")"
echo "${COUNT}" > "${COUNTER}"

exit 0

Составляем правило реакции на интересующие нас события изменения конфигурационных файлов:

# vi /etc/incron.d/rsync-dhcpd

#InCron_table_does_not_support_comments_with_several_(more_than_one)_words!
#<directory>_<file_change_mask>_<command_or_action>_<options>

/etc/dhcp/dhcpd.subnets/ IN_CLOSE_WRITE,IN_CREATE,IN_DELETE /etc/incron.scripts/rsync-dhcpd.sh

Сервис "incrond" мониторит состояние своих конфигурационных файлов и сразу применяет изменения:

# tail -100 /var/log/syslog

....
incrond[11568]: system table rsync-dhcpd changed, reloading

Для пущей уверенности можно дать указание перечитать конфигурацию принудительно:

# /etc/init.d/incron reload

Для того, чтобы скомпенсировать возможные сбои в работе реагирующей на события подсистемы "incron" дополнительно запланируем вызов процедуры синхронизации каждые пять часов:

# vi /etc/crontab

....
# Run the synchronization preparation script
1 */5  * * *  root  /etc/incron.scripts/rsync-dhcpd.sh &

Очевидно, что я предпочитаю в автоматическом режиме лишь отправлять изменения конфигурации "первичного" DHCP-сервера на "вторичный", в то время как применение таковой в работу на "первичном" сервере остаётся ручным. Полагаю, что администратору сервиса виднее, когда следует активировать набор изменений. Правда, это конфликтует с идеей полностью автоматического применения изменений, отправляемых на "вторичный" DHCP-сервер, но в данном случае это вынужденный компромис, обусловленный примитивностью используемого "ISC DHCPd".

Обработка событий изменения конфигурации "вторичного" DHCP-сервера.

Следующим шагом, считая, что по накоплению некого порога количества модификаций конфигурации таковая выгружается с "первичного" DHCP-сервера на "вторичный", обеспечим приём изменённых файлов и перезапуск сервиса DHCPd по готовности.

Разрешим стартовому скрипту "/etc/init.d/rsync" запускать сервис "rsyncd" и зададим ряд его параметров в файле "/etc/default/rsync":

....
RSYNC_ENABLE=true
....
RSYNC_CONFIG_FILE=/etc/rsyncd.conf
....
RSYNC_OPTS=' --ipv4 --port=873'

В конфигурационном файле выше мы привязали сервис "rsyncd" к определённому протоколу (только IPv4) и прослушиваемому порту.

Приведём конфигурационный файл сервиса "RSync" к следующей базовой конфигурации:

# vi /etc/rsyncd.conf

log file = /var/log/rsyncd.log
pid file=/var/run/rsyncd.pid

# hosts allow = dhcp.example.net
hosts allow = 1.2.3.4

max connections = 1
dont compress = *

uid = root
gid = root
use chroot = yes

[etc-dhcpd-subnets]
  path = /etc/dhcp/dhcpd.subnets
  write only = yes
  read only = no
  list = no

# /etc/init.d/rsync restart

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

# mkdir -p /etc/incron.scripts
# vi /etc/incron.scripts/dhcpd-reload.sh
# chmod ug+x /etc/incron.scripts/dhcpd-reload.sh

#!/bin/bash

COUNTER="/var/run/incron/vars/DHCPD_RELOAD_COUNT"

# Накручиваем счётчик количества вызовов
COUNT=$(($(cat "${COUNTER}")+1))

# Запускаем процедуру только после некоторого количества вызовов скрипта
if [ "${COUNT}" -gt "2" ]
then

  # Перезапускам сервис только в том случае, если файл конфигурации DHCP-сервера корректен
  /usr/sbin/dhcpd -f -t -q 2>&1 >/dev/null && /etc/init.d/isc-dhcp-server restart

  # Сбрасываем счётчик количества вызовов скрипта
  COUNT=0
fi

# Сохраняем переменную счётчика
[ -f "${COUNTER}" ] || mkdir -p "$(dirname "${COUNTER}")"
echo "${COUNT}" > "${COUNTER}"

exit 0

Составляем правило реакции на интересующие нас события изменения (по факту загрузки посредством RSync) конфигурационных файлов:

# vi /etc/incron.d/dhcpd-reload

#InCron_table_does_not_support_comments_with_several_(more_than_one)_words!
#<directory>_<file_change_mask>_<command_or_action>_<options>

/etc/dhcp/dhcpd.subnets/ IN_MODIFY,IN_CREATE,IN_DELETE /etc/incron.scripts/dhcpd-reload.sh

Для того, чтобы скомпенсировать возможные сбои в работе реагирующей на события подсистемы "incron" дополнительно запланируем вызов процедуры синхронизации каждые пять часов:

# vi /etc/crontab

....
# Run the reload preparation script
1 */5  * * *  root  /etc/incron.scripts/dhcpd-reload.sh &

Очевидно, что по мере поступления изменений в конфигурации сервис DHCP будет автоматически перезагружаться (после успешной проверки синтаксиса, разумеется).

Режим использования.

Итак, мы запустили "вторичный" DHCP-сервер, настроили отслеживание модификации конфигурации "первичного" сервера, выгрузку таковой на "вторичный" и последующий перезапуск сервиса "вторичного" сервера для применения изменений. В общем схема проста и надёжна (у меня она с год как работает без намёка на сбой), а эксплуатация её сводится к правке файлов описания клиентских подсетей в директории "/etc/dhcp/dhcpd.subnets/" на "первичном" сервере, синтаксической проверке и перезапуске сервиса только на "первичном" сервере (на "вторичном" изменения с некоторой задержкой распространятся полностью автоматически):

# vi /etc/dhcp/dhcpd.subnets/subnets.conf
# dhcpd -t
# /etc/init.d/isc-dhcp-server restart


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )