«Грамотный DevOps»: пишем конфигурацию с помощью Emacs, Org и Jinja
Содержание
Что такое вообще «Грамотный DevOps»?
От текста с разметкой к интерактивному блокноту
Как это работает
Исполняемые блоки кода
Пишем скрипты
Генерация exim.j2
Установка exim.conf
Отправляем тестовые письма
Собираем все вместе
Заключение
Примечания
Что такое вообще «Грамотный DevOps»?
Если совсем коротко: «грамотный DevOps» (Literate DevOps) — это использование принципов «грамотного программирования» (Literate programming) для работы с инфраструктурой в виде кода. Термин Literate DevOps придумал Говард Абрамс, и он же показал, как реализовать его с помощью Emacs и Org.
Теперь подробнее.
Как все знают, концепцию «грамотного программирования» (мне больше нравится вариант перевода «литературное программирование», он как-то больше соответствует духу этой концепции, но раз уж в Википедии употребляется вариант «грамотное», то пусть будет так) создал великий Дональд Кнут. В этой концепции комментарии и программный код как бы меняются местами: текст программы выглядит не как код, к которому где-то приписаны комментарии, а как связное и последовательное («литературное») описание логики работы, структуры данных и так далее, в которое вставлены блоки программного кода. Далее специальная программа может извлечь из этого текста все блоки кода и расставить их в нужном порядке, создав, таким образом, исходный код, который можно скомпилировать, получив работающую программу. Эта процедура, в терминах Кнута, называется tangle («запутывание»). Другая процедура, под названием weave («сплетение») позволяет из того же текста получить очень красиво отформатированный документ.
Конечно, этот принцип можно применить и к файлам конфигураций. Вот так, например, может выглядеть фрагмент файла конфигурации почтового сервера Exim, написанного по правилам «грамотного программирования» с использованием разметки, принятой в Org.
* Основные настройки
Укажите здесь каноническое имя вашего хоста. Обычно это полное
«официальное» имя вашего хоста. Если этот параметр не установлен, для
получения имени вызывается функция =uname()=. Во многих случаях это
правильно, и вам не нужно ничего явно задавать.
#+BEGIN_SRC conf-unix :tangle exim.conf
# primary_hostname =
#+END_SRC
Блоки выделяются разметкой #+BEGIN_SRC
и #+END_SRC
. Внутри Emacs их можно редактировать со всеми удобствами, которые предоставляет режим для соответствующего языка: синтаксической подсветкой, автоформатированием, автозавершением и так далее. А затем все эти блоки из файла можно собрать и экспортировать в другой файл, или несколько файлов, согласно параметрам в заголовке блока (:tangle exim.conf
). Для этого достаточно одной команды (C-c C-v t
). В exim.conf
получится готовая к применению конфигурация.
Из того же самого файла с разметкой Org можно (тоже одной командой) получить красивую страницу в html:
img
…и во множестве других форматов.
На моем гитхабе можно взять файл конфигурации Exim, извлеченный непосредственно из исходных кодов Exim 4.94.2 и переведенный в формат org-mode
, со всеми комментариями на русском языке (перевод тоже мой). В него внесены модификации, более подробно описанные в прошлой заметке: «Настройка Exim для отправки почты на несколько провайдеров».
От текста с разметкой к интерактивному блокноту
Изящная концепция Кнута не получила широкого распространения в том виде, в каком ее реализовал автор. Количество исходного кода в современных программах огромно, и большую часть его неинтересно описывать «литературно». Кроме того, современные средства генерации кода и сборки программ давно позволяют делать почти все, что делают tangle и weave (даже в позднейших реализациях).
Но развитие идеи «вставки» кусочков программного кода в документ привело к появлению следующей концепции: интерактивного блокнота, в который вставляется уже не только код, но и данные, а сами кусочки кода можно сразу же в этом же блокноте запускать и сюда же вставлять результаты работы кода в любом виде: графическом, табличном и так далее. Возможно, самым известным таким блокнотом является Jupyter.
Но блокноты обычно ориентированы на работу с данными, научные исследования и прочие подобные задачи. А мы сейчас сделаем блокнот для задач администрирования. Этот блокнот позволит нам не только сгенерировать exim.conf
, но и установить его в нужное место (например, /etc/exim
), перезапустить сервис exim
и отправить тестовое письмо — и все это не покидая единственного документа, открытого в Emacs, без бесконечных переключений в терминалы, открытия ssh-сессий и ввода паролей для sudo. (Ладно, если вы работаете с удаленным сервером, то ssh настроить все-таки придется).
Собственно говоря, вы сейчас этот блокнот и читаете, и если бы этот файл был открыт у вас в Emacs, вы могли бы все это тут же сделать.
Как это работает
План у нас такой. Основной шаблон будет описан в файле exim.org
по правилам «грамотного программирования». В блоках кода будет использоваться синтаксис Jinja. Затем Emacs все эти блоки кода будет собирать в файле exim.j2
— это будет уже готовый шаблон конфигурационного файла, который можно пропустить через шаблонизатор Jinja и получить сам конфигурационный файл. В шаблон можно включать другие файлы, например, так:
{% include 'secrets/40_smarthosts' ignore missing %}
Можно использовать переменные, вот так:
{# Пути к файлам сертификата и приватного ключа для поддержки TLS. #}
tls_certificate = {{ TLS_CERTIFICATE }}
tls_privatekey = {{ TLS_PRIVATEKEY }}
В общем, можно делать все, что позволяет Jinja. Для запуска шаблонизатора мы будем использовать небольшой скрипт jinja2-cli, который позволяет использовать Jinja из командной строки. Значения переменных можно передать в jinja2-cli
через файл на языке разметки Yaml, вот в таком виде:
CONFDIR: /etc/exim
TLS_CERTIFICATE: /etc/letsencrypt/live/example.org/fullchain.pem
TLS_PRIVATEKEY: /etc/letsencrypt/live/example.org/privkey.pem
А шаблонизатор запускается так:
jinja2 exim.j2 data.yaml -o exim.conf
Он берет шаблон exim.j2
, подставляет в него значения переменных, взятые из data.yaml
и создает итоговый файл exim.conf
.
И cделать все это нам помогут исполняемые блоки кода.
Исполняемые блоки кода
Org и Emacs поддерживают исполнение непосредственно из документа кода на десятках языков: от Python и C до SQL до языков рисования диаграмм и графиков PlantUML и gnuplot. Разумеется, поддерживается и язык оболочек (sh, bash, zsh, fish, csh…). Собственно, выше вы уже видели пример исполняемого блока — это команда вызова шаблонизатора. Полностью этот блок выглядит так:
#+NAME: генерация exim.conf
#+BEGIN_SRC sh :tangle no :results output :eval no-export
jinja2 exim.j2 data.yaml -o exim.conf
#+END_SRC
Достаточно поставить курсор внутрь этого блока и нажать C-c C-c
, и записанный в блоке скрипт будет выполнен. Обратите внимание на заголовок #+NAME:
— это название блока, и мы его используем позже.
Возможности взаимодействия с блоками кода намного шире, чем можно показать здесь, и для их изучения придется обратиться к документации.
Пишем скрипты
Напишем элементарные скрипты для выполнения остальных задач.
Генерация exim.j2
Однострочечный скрипт на Emacs Lisp для генерации из файла exim.org
шаблона exim.j2
, который будет обрабатываться шаблонизатором. Он просто вызывает функцию org-babel-tangle-file
. Того же результата можно добиться, открыв в Emacs файл exim.org
и нажав C-c C-v t
.
#+NAME: генерация exim.j2
#+BEGIN_SRC emacs-lisp :tangle no :exports none :eval no-export
(org-babel-tangle-file "exim.org")
#+END_SRC
Скрипт для генерации exim.conf
мы уже видели выше.
Установка exim.conf
А вот для установки полученного файла exim.conf
в /etc/exim
и перезапуска сервиса нам понадобятся права. К счастью, в Emacs есть такая замечательная вещь, как TRAMP. Он позволяет подключаться почти к чему угодно: к серверу с ssh, к контейнеру docker, к виртуальным файловым системам и даже к устройствам на Android, подключенным через USB. Выполнение блоков кода в подключенных через TRAMP каталогах происходит так же, как и в локальных. Кроме прочего, можно «подключиться» и к собственному компьютеру через sudo
1. А еще можно подключаться по цепочке: например, через несколько ssh-серверов, или через ssh на сервер, а затем в запущенный на этом сервере контейнер… Синтаксисы всего этого описаны в документации. Сейчас нам хватит и sudo
.
Следующий скрипт устанавливает в /etc/exim:
exim.conf — основной файл конфигурации
secrets/passwd — пароли для доступа к серверу Exim на этом компьютере
secrets/passwd.smarthosts — пароли для доступа к смартхостам
secrets/smarthosts — правила маршрутизации в зависимости от отправителя.
Форматы файлов passwd.smarthosts и smarthosts описаны в предыдущей заметке, а passwd — в документации Exim.
Файлы с паролями должны быть доступны на чтение пользователю, от которого будет работать Exim. В OpenSUSE этот пользователь входит в группу mail, поэтому устанавливаем с параметрами -m 640 -g mail
.
После копирования exim.conf
скрипт вызывает exim -C /etc/exim/exim.conf -bV
для проверки нового файла (только синтаксической, расширения здесь проверены быть не могут). Если проверка не прошла, выполнение прервется. Файлы можно восстановить из резервной копии, которую создает install
.
#+NAME: установка exim.conf
#+BEGIN_SRC sh :dir /sudo:: :var wd=(eval 'default-directory) :results output :exports none :eval no-export
cd $wd && \
install --backup=numbered -m 644 ./exim.conf /etc/exim/exim.conf && \
exim -C /etc/exim/exim.conf -bV && \
install --backup=numbered -m 640 -g mail ./secrets/passwd /etc/exim/passwd && \
install --backup=numbered -m 640 -g mail ./secrets/passwd.smarthosts /etc/exim/passwd.smarthosts && \
install --backup=numbered -m 640 -g mail ./secrets/smarthosts /etc/exim/smarthosts && \
systemctl restart exim && \
systemctl status exim
#+END_SRC
Обратите внимание на конструкцию :var wd=(eval 'default-directory)
. Так мы передаем в скрипт каталог, в котором лежит этот файл и сгенерированный exim.conf
, ведь после sudo
текущим каталогом будет /root
. Переменная wd
получит значение, и его можно будет использовать в скрипте.
Отправляем тестовые письма
Для тестирования отправки писем хорошо подходит Swaks.
Первое поле в файле passwd.smarthosts
у нас соответствует адресу электронной почты, с которого нужно отправить письмо. Приведенный ниже скрипт извлекает первое поле из всех строк этого файла, пропуская комментарии, начинающиеся с #, и записывает их в массив emails
. Команда mapfile
специфична для bash
.
В заголовке имеются переменные MAILTO
— это адрес, на который мы отправляем тестовые письма, и PASSWORD
— это пароль для аутентификации на нашем сервере Exim (хэши паролей хранятся в /etc/exim/passwd
). Он у нас один на всех. Переделка скрипта на использование разных паролей оставлена в качестве упражнения для читателя.
#+BEGIN_SRC bash :var MAILTO="email@example.org" PASSWORD="hunter2" :results value
mapfile -t <<<$(grep -v '#' ./secrets/passwd.smarthosts \
| cut -f 1 -d :) emails
for i in "${emails[@]}"
do
swaks -tls -a \
--to $MAILTO \
--from $i \
--server localhost \
--auth-user $i \
--auth-password $PASSWORD
done
#+END_SRC
Собираем все вместе
Во-первых, соберем блоки кода из этого файла.
#+BEGIN_SRC emacs-lisp :results list
(org-babel-tangle)
#+END_SRC
Org может вызывать на исполнение блоки кода, как функции в традиционных языках программирования. Для этого у блока должно быть имя (#+NAME:
). Синтаксис показан ниже:
#+CALL: генерация exim.j2()
#+CALL: генерация exim.conf()
#+CALL: установка exim.conf()
А еще Org умеет исполнять блоки кода из отдельно взятого раздела документа. Для этого служит функция org-babel-execute-subtree
, комбинация клавиш по умолчанию C-c C-v s
. По нажатию этой комбинации все и произойдет.
Заключение
В этой статье мы познакомились с методикой «грамотного DevOps» и ее реализацией с помощью редактора Emacs и org-mode. Мы увидели, как можно написать конфигурационный файл почтового сервера Exim в виде документа, содержащего блоки кода на разных языках, которые можно извлекать, компоновать и исполнять прямо внутри документа. Такой подход имеет несколько преимуществ для devops-инженера:
Документирование. Конфигурационный файл сопровождается текстовым описанием, которое объясняет его логику и назначение. Это упрощает понимание и поддержку конфигурации, а также облегчает передачу знаний между членами команды.
Воспроизводимость. Конфигурационный файл можно легко перенести на другую машину или среду, так как он содержит все необходимые команды для его установки и запуска. Это повышает надежность и безопасность конфигурации, а также уменьшает риск ошибок и сбоев.
Интерактивность. Конфигурационный файл можно редактировать и тестировать в режиме реального времени, используя возможности Emacs для работы с кодом. Это ускоряет разработку и отладку конфигурации, а также повышает ее качество и эффективность.
Emacs является мощным и гибким инструментом для работы с текстом и кодом, который может значительно улучшить процесс DevOps. Методика «грамотного DevOps» позволяет использовать Emacs не только как редактор, но и как среду для создания, документирования и исполнения конфигурационных файлов. Мы надеемся, что эта статья поможет вам освоить этот подход и применить его на практике.
И да, последние абзацы написал ChatGPT.
Примечания
1 Не используйте всяких хитроумных настроек командной строки вроде Oh My Posh, а то TRAMP может запутаться.