[recovery mode] Практика применения CFEngine в реальном мире

Продолжим начатый пользователем alex_www в двух предыдущих статьях рассказ о CFEngine. В этой речь пойдёт о практике применения CFEngine и некоторых нюансах его настройки в условиях реального мира. Для уменьшения объёма текста я предполагаю, что основными понятиями из мира CFEngine вы владеете, возможно даже пробовали его где-то использовать. В качестве букваря могу посоветовать книгу Диего Замбони (Diego Zamboni) «Learning CFEngine 3», она небольшая, очень понятная и читается на одном дыхании.В статье даны примеры для настройки с чистого листа на Debian GNU/Linux с использованием Git. Если вы хотите дополнить статью примерами для своих любимых дистрибутивов и VCS, то присылайте личные сообщения или высказывайтесь в комментариях. По возможности я буду их добавлять в основной текст с указанием авторства.Установка Самый простой способ установить CFEngine3 — сделать это через официальный репозиторий. Для этого сначала нужно добавить ключ, которым подписаны пакеты: cd /tmp wget http://cfengine.com/pub/gpg.key cat gpg.key # не ныряй в незнакомых местах apt-key add gpg.key rm gpg.key # чисто там, где не сорят …, а потом добавить репозиторий и поставить CFE:

echo «deb http://cfengine.com/pub/apt community main» > /etc/apt/sources.list.d/cfengine-community.list chmod 644 /etc/apt/sources.list.d/cfengine-community.list # некоторые любят umask 0700 apt-get update apt-get install cfengine-community Инициализация Чтобы инициализировать CFE, нужно выполнить (например, вручную) первый запуск cf-agent. Если вам приходится часто вводить в строй новые серверы, то установку CFE и bootstrap лучше всего производить из preseed или же из скрипта, запускающегося один раз при первом старте системы; хорошим примером такого скрипта может послужить скрипт, создающий серверные ключи для OpenSSH. /var/cfengine/bin/cf-agent -IC --bootstrap Вместо нужно подставить соответствующий IP-адрес или доменное имя вашего policy hub, причём если вы инициализируете сам policy hub, то нужно указать тот IP, на котором он потом будет обслуживать клиентов. В случае доменного имени, оно будет разрешено в IP-адрес, который и будет использоваться в дальнейшем, так что проблемы с возможной недоступностью DNS-серверов не станут помехой.

Параметр -I включает создание краткого отчёта о выполнении, а -C — раскраску вывода в консоли. Оба не являются обязательными, но я их использую в интерактивных сессиях для собственного удобства. Ещё один полезный параметр запуска это -v, подробный режим. Он имеет приоритет перед -I и выдаёт очень подробную информацию о ходе выполнения промисов. Здорово помогает при отладке.

Первичная настройка После завершения инициализации policy hub, ещё до соединения с первым клиентом, нужно настроить кое-какие мелочи. Дело в том, что параметры по умолчанию в файле def.cf (здесь и далее для всех относительных путей корнем считается /var/cfengine/masterfiles, если не указано иное) неплохо подходят для экспериментов «на коленке» или для демонстрации возможностей, однако для прода эти параметры подходят мало. Чуть ниже, я напишу об организации процесса разработки промисов и выкатывания их в прод, а пока что лучше всего сделать локальную копию /var/cfengine/masterfiles и работать с ней.Первым делом укажем наше полное доменное имя. Несмотря на усилия команды разработчиков сделать хорошую систему автоопределения текущих параметров системы, она неидеальна, поэтому везде, где можно и целессобразно лучше всего не полагаться на автоматику и указывать данные вручную. Начнём с bundle common def, и укажем domain, mailto, mailfrom и smtpserver:

'domain' string => 'example.org'; 'mailto' string => 'sysadmin-queue@${def.domain}'; # на этот адрес CFE будет пытаться слать сообщения в случае необходимости 'mailfrom' string => 'root@cfe-policy-server.${def.domain}'; 'smtpserver' string => 'internal-mail-collector.${def.domain}'; Безопасность CFE может обеспечивать безопасность соединений и аутентификацию клиентов, однако какими-то исключительно развитыми средствами для этого не обладает, так как имеющихся за глаза хватает для выполнения поставленной задачи. Так или иначе, в коммуникациях с клиентами используется система с доверенными ключами, аналогичная OpenSSH, и списки доверенных IP-сетей и доменов. По умолчанию соединения разрешёны для всех хостов в домене policy hub и для /16 его основного IP. Все ключи, полученные по успешно установленным соединениям считаются доверенными. Такой подход может в условиях работы в доверенной сети может сильно облегчить разворачивание с нуля и очень удобен на стадии R&D, но крайне небезопасен в реально жизни. Учитывая эфемерность IP-адресов и географическую распределённость контролируемых машин я предпочитаю использовать следующий подход: CFEngine (cf-serverd, если быть точнее) принимает соединения с любого IP-адреса и доверяет только тем ключам, которые ему известны заранее (то есть, находятся в /var/cfengine/ppkeys): 'acl' slist => { '0.0.0.0/0', }; comment => 'Connections are allowed from any IP', handle => 'common_def_vars_acl';

'trustkeysfrom' slist => { # NEVER ADD ANYTHING HERE. DON’T TRUST STRANGERS! }, comment => 'Only keys in /var/cfengine/ppkeys are trusted', handle => 'common_def_vars_truskeysfrom'; В случае, когда нужно ограничивать доступ по IP-адресам я предпочитаю использовать фаерволл, как более подходящий для решения задачи инструмент.

Чтобы добавить ключ клиента в доверенные нужно скопировать содержимое файла /var/cfengine/ppkeys/localhost.pub (обычный RSA-ключ в Base64) на policy hub и запустить cf-key -t /path/to/client_key.pub. Программа cf-key сама добавит его в /var/cfengine/ppkeys с правильным именем и правами.

Автоматизация контроля за конфигурациями, позволяющая легко вносить масштабные изменения, с такой же лёгкостью приводит и к масштабным ошибкам. Поэтому необходимо разумное количество «ремней безопасности» и «больших красных кнопок». Один из таких «ремней» — это внесение стандартной библиотеки в VCS вместе с вашим кодом. При установке пакета с новой версией, содержимое каталогов /var/cfengine/masterfiles и /var/cfengine/inputs не обновляется, так как в результате будет невозможно гарантировать непротиворечивость конфигурации. Поэтому один из важных этапов обновления — это слияние изменений стандартной библиотеки с вашей копией и именно здесь вам пригодится вся помощь, какую ваша VCS может вам предложить, а так же умение пользоваться утилитами diff и patch.package_latests Одним из механизмов, который я часто использую для гарантий обновлений некоторых пакетов является бандл package_latest из стандартной бибилотеки. К сожалению, там есть баг, из-за которого бандл в Дебиане не работает. Фикс весьма тривиален. В файле lib/3.6/packages.cf нужно найти код бандла packages_latest и привести его к такому виду (можно использовать патч из багрепорта): debian:: »$(package)» package_policy => «addupdate», package_version => »999999999:9999999999», package_method => apt_get_permissive; Bug 6870 Ещё одним досадным багом, который я обнаружил в проде, является баг 6870. Суть проблемы заключается в том, что CFEngine устанавливает некоторые классы на основании PTR-записей IP-адресов на интерфейсах. Как можно догадаться, такое поведение системы весьма и весьма небезопасно, да и противоречит постулату CFEngine о неприемлимости внешних знаний. Однако, в своей книге Диего Замбони учит определять хост исполнения по классам вида host1_example_org, а исправление этого бага может разломать слишком много работающих сейчас систем. Поэтому, пока разработчики не предоставили более надёжного способа, мы его создадим сами. Вот код, который можно добавить в свою версию стандартной библиотеки в файл lib/3.6/bug_6870.cf: bundle common bug_6870_workaround { classes: 'bug6870_workaround_${sys.host}' expression => 'any'; } Затем нужно добавить имя файла в ${stdlib_common.inputs} по аналогии с уже перечисленными там файлами и после этого уже использовать класс bug6870_workaround_host1_example_org не опасаясь неожиданных пересечений с PTR-записями других IP.

Теперь, когда всё готово для творчества, я расскажу немного об организации процесса разработки промисов и немного о более приземлённых, бытовых вещах.Инструменты Прежде всего, промисы надо в чём-то писать. Для этого, скорее всего, подойдёт ваш любимый текстовый редактор. Я предпочитаю Vim и использую плагины, написанные Нилом Ватсоном (Niel Watson), а мой коллега Валера Островерхов (Val Astraverkhau) создал очень хороший плагин для поддержки CFEngine в Sublime Text 2 и 3. Пользователям Emacs будет небезынтересна лекция Тэда Златанова (Ted Zlatanov) об использовании Emacs в качестве CFEngine IDE.Так же вам понадобится хорошая система контроля версий. Меня всем устраивает Git, но я уверен, что подойдёт любая современная VCS. Требования тут те же, что и к разработке обычного софта, так что берите то, с чем вам удобно работать.

Организация файлов и точка входа Скажу сразу, что предложенный мной способ не единственно верный. Знакомый программистам на Perl принцип TIMTOWTDI применим и здесь; и как нигде, здесь уместна полемика. Вот примерная структура директорий, относительно корня проекта: /bin /masterfiles /masterfiles/cfe_internal /masterfiles/cfe_internal/ha /masterfiles/controls /masterfiles/controls/3.4 /masterfiles/inventory /masterfiles/example_org /masterfiles/lib /masterfiles/lib/3.5 /masterfiles/lib/3.6 /masterfiles/services /masterfiles/services/autorun /masterfiles/sketches /masterfiles/sketches/meta /masterfiles/templates /masterfiles/update /static /static/bird-lg /static/firewall-configs /static/ssh-keys /templates В директории /masterfiles/example_org лежит тот код, который мы пишем. Остальные поддиректории в /masterfiles — это части стандартной поставки, которые я стараюсь не менять без крайней необходимости. Все нестандартные темплейты вынесены в /templates, а в /static, как видно из названия, хранится «статичная» информация — публичные SSH-ключи, настройки фаерволлов, пользовательские настройки, конфигурационные файлы и прочее, что не меняется от хоста к хосту. В директории /bin лежит пара сервисных скриптов, включая «большую красную кнопку» — скрипт, перекладывающий все нужные файлы туда, откуда CFEngine сможет их раздать клиентам.

Точка входа расположена в /masterfiles/example_org/main.cf, где содержатся два промиса: bundle common example_org, в котором перечислены используемые файлы и происходит классификация серверов, и bundle agent example_org_main, где в зависимости от класса управление передаётся нужному бандлу, описывающему как именно серверы этого класса должны быть сконфигурированы.

Для указания точки входа в файле promises.cf нужно внести следующие изменения:

body common control { bundlesequence => { # […] @{example_org.bundles}, };

inputs => { # […] 'example_org/main.cf', @{example_org.inputs}, }; } Сам же example_org/main.cf выглядит примерно так:

bundle common example_org { vars: 'inputs' slist => { 'example_org/add_default_users.cf', 'example_org/basic_packages.cf', 'example_org/configure_dns.cf', 'example_org/configure_firewall.cf', 'example_org/configure_ftp.cf', 'example_org/configure_ssh.cf', 'example_org/cve_2015_0235.cf', 'example_org/lib.cf', };

'bundles' slist => { 'example_org', 'example_org_main', };

classes: 'ftp_server' or => {classmatch ('BUG6870_ftp.*')}; 'dns_server' expression => classmatch ('BUG6870_dns.*');

reports: verbose_mode:: '${this.bundle}: defining inputs=»${inputs}»'; '${this.bundle}: defining bundles=»${bundles}»';

ftp_server:: 'This host assumes FTP server role';

dns_server:: 'This host assumes DNS server role'; }

bundle agent example_org_main { methods: any:: 'example_org_update_motd' usebundle => 'update_motd'; 'example_org_basic_packages' usebundle => 'basic_packages'; 'example_org_add_default_users' usebundle => 'add_default_users'; 'example_org_configure_firewall' usebundle => 'configure_firewall'; 'example_org_configure_ssh' usebundle => 'configure_ssh'; 'example_org_cve_2015_0235' usebundle => 'cve_2015_0235';

any.Min30_35:: 'heartbeat' usebundle => 'heartbeat';

# FTP servers configuration ftp_server:: 'example_org_configure_ftp' usebundle => 'configure_ftp';

# DNS servers configuration dns_server:: 'example_org_configure_dns' usebundle => 'configure_dns'; } Этот пример был составлен из нескольких настоящих проектов для демонстрации, в реальности, конечно же, всё немного сложнее и больше. Цель такого подхода — минимизировать количество изменений в стандартной библиотеке, чтобы потом было проще поддерживать изменения в ней.

Отладка и тестирование Поскольку цена ошибки велика, прежде, чем нажать на «большую красную кнопку», стоит протестировать свои промисы. Для тестов у меня есть небольшой полигон: несколько виртуальных машин, которые я запускаю на своём рабочем компьютере. На них я проверяю правильность выполнения промисов и занимаюсь экспериментами.В разработке промисов я придерживаюсь стиля школы отладки через printf, то есть пользуюсь промисами типа report очень широко. Ещё один незаменимый инструмент — это комплектная утилита cf-promises. Кроме формальной валидации синтаксиса, она умеет показывать все доступные во время исполнения классы (параметр --show-classes) и переменные с их содержимым (параметр --show-vars). Ну и конечно же, запуск cf-agent в подробном режиме (параметр --verbose).

Обновления Обновить версию CFEngine на всех подконтрольных машинах можно двумя способами: либо через пакетный менеджер, либо используя механизмы самого CFEngine. Я предпочитаю пакетный менеджер, для чего у меня есть специальный промис, который я подключаю только на время обновлений, суть его сводится к вызову package_latest. На мой взгляд такой подход лучше всего соответствует концепциям CFE.Большая Красная Кнопка Выкатывание в прод — это всегда немного волнительный момент, даже если оно происходит по многу раз за день, и я не осуждаю людей, у которых для этого существует какой-то ритуал. В случае массовых изменений конфигурации, это может определить смысл жизни на следующие несколько суток, если что-то пойдёт не так. Поэтому никакой автоматики, никаких хуков для Git. Только ручной режим, как залог уверенности, что всё сделано правильно, оттестированно и готово для прода. У меня в качестве кнопки выступает скрипт deploy.sh с правами 0600, чтобы его никак нельзя было запустить случайно. Набирать руками в консоли bash bin/deploy.sh — это мой ритуал и последняя возможность отменить запуск. Сам скрипт весьма тривиален: при помощи rsync он синхронизирует masterfiles, static и templates с содержимым /var/cfengine/{masterfiles, static, templates} и запускает две команды: cf-agent -KIC -f update.cf и cf-agent -KIC -f promises.cf. Так я могу быть уверенным, что как минимум policy hub может выполнить промисы и раздать их всем клиентам. Это далеко не все тонкости и премудрости, но этого вполне достаточно, чтобы начать внедрение CFEngine у себя. За рамками статьи остались такие интересные темы как «Design Center», отчёты, внутреннее устройство, разные сценарии использования и многое другое. Если CFEngine представляет какой-то интерес для хабрасообщества, я с удовольствием расскажу о нём больше, а пока, если у вас есть какие-то острые вопросы прямо сейчас, не стесняйтесь задавать их в комментариях. Я и мой коллега по lastops cagliostro постараемся на них ответить.

© Habrahabr.ru