Практика застосування CFEngine в реальному світі

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 <policy_hub_address>


Замість
<policy_hub_address>
підставте відповідний 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, але я впевнений, що підійде будь-яка сучасна СКВ. Вимоги тут ті ж, що й до розробки звичайного софта, так що беріть те, з чим вам зручно працювати.

Організація файлів і точка входу
Скажу відразу, що запропонований мною спосіб не єдино вірний. Знайомий програмістам на 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 постараємося на них відповісти.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.