«Грамотный DevOps»: пишем конфигурацию с помощью Emacs, Org и Jinja

Содержание

  1. Что такое вообще «Грамотный DevOps»?

  2. От текста с разметкой к интерактивному блокноту

  3. Как это работает

  4. Исполняемые блоки кода

  5. Пишем скрипты

    1. Генерация exim.j2

    2. Установка exim.conf

  6. Отправляем тестовые письма

  7. Собираем все вместе

  8. Заключение

  9. Примечания

Что такое вообще «Грамотный 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

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 каталогах происходит так же, как и в локальных. Кроме прочего, можно «подключиться» и к собственному компьютеру через sudo1. А еще можно подключаться по цепочке: например, через несколько 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 может запутаться.

© Habrahabr.ru