Шаблоны Jinja2: конфиги из переменных
Урок 13 - генерируем конфиги под каждый сервер с помощью Jinja2-шаблонов.
«Один шаблон nginx.conf.j2 - и на каждом сервере свой правильный конфиг с его портом, его доменом, его числом воркеров».
Копировать готовый файл модулем copy хорошо, когда он одинаков везде. Но конфиги почти всегда отличаются от хоста к хосту. Jinja2-шаблоны решают это: ты пишешь шаблон с подстановками, а модуль template рендерит его под каждый хост, подставляя переменные и факты.
Синтаксис Jinja2
Три ключевые конструкции: {{ переменная }} - подстановка значения; {% if %}...{% endif %} - условие; {% for %}...{% endfor %} - цикл. Пример шаблона nginx.conf.j2:
worker_processes {{ nginx_worker_processes }};
server {
listen {{ nginx_port }};
server_name {{ server_domain }};
{% if ssl_enabled %}
ssl_certificate /etc/ssl/{{ server_domain }}.crt;
{% endif %}
{% for path in static_paths %}
location {{ path }} { root /var/www; }
{% endfor %}
}
А вот задача, которая его применяет. Обрати внимание на validate и notify:
- name: Развернуть конфиг nginx из шаблона
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
notify: Перезапустить nginx
Как работает под капотом
Модуль template читает .j2-файл, прогоняет его через движок Jinja2 со всеми переменными и фактами хоста, получает итоговый текст. Затем сравнивает его с тем, что уже лежит на хосте: если совпадает - ok и handler не сработает; если отличается - записывает новый файл, статус changed, и срабатывает notify. Параметр validate запускает проверку (nginx -t) до подмены, защищая от записи битого конфига.
Смоделируем рендеринг шаблона с подстановкой, условием и циклом средствами Python.
# Имитация рендеринга Jinja2-шаблона через format и циклы
def render(vars_):
lines = [f"worker_processes {vars_['workers']};", "server {",
f" listen {vars_['port']};",
f" server_name {vars_['domain']};"]
if vars_.get("ssl_enabled"): # {% if %}
lines.append(f" ssl_certificate /etc/ssl/{vars_['domain']}.crt;")
for path in vars_.get("static_paths", []): # {% for %}
lines.append(f" location {path} {{ root /var/www; }}")
lines.append("}")
return "\n".join(lines)
config = render({"workers": 4, "port": 443, "domain": "shop.example.com",
"ssl_enabled": True, "static_paths": ["/img", "/css"]})
print(config)
Попробуй сам ▶ Из набора переменных получился готовый текст конфига - ровно то, что делает template, только настоящий движок мощнее (фильтры, наследование шаблонов). Поменяй ssl_enabled на False - блок сертификата исчезнет.
Фильтры Jinja2
Фильтры преобразуют значения через |: {{ name | upper }} - в верхний регистр, {{ items | length }} - длина списка, {{ port | default(80) }} - значение по умолчанию, если переменная не задана. default - один из самых полезных: страхует от undefined.
Частые ошибки
- Undefined-переменная в шаблоне. Если переменная не задана - рендер упадёт. Страхуйся фильтром
default. - Путать {{ }} и {% %}. Двойные фигурные - вывод значения; фигурная с процентом - управляющая конструкция (if/for).
- Не использовать validate. Без проверки можно записать синтаксически битый конфиг и уронить сервис при рестарте.
Best practices
- Для любых конфигов с переменной частью -
template, а неcopy. - Связывай
templateс handler черезnotify: перезапуск только при реальном изменении. - Используй
validateдля критичных конфигов (nginx, sshd, sudoers) иdefaultдля необязательных переменных.
В реальной работе
Jinja2-шаблоны - это место, где переменные, факты и логика сходятся в готовый артефакт. На практике в шаблонах активно используют фильтры: to_nice_json и to_nice_yaml для генерации структурированных конфигов, join для сборки списков серверов в строку, regex_replace для преобразований. Частый приём - генерация конфига балансировщика, который перечисляет все бэкенды: цикл for по группе web через groups['web'] и hostvars вытаскивает адреса всех веб-серверов и вставляет их в upstream. Так конфиг автоматически обновляется при добавлении сервера в группу - тебе не нужно править его руками, достаточно изменить инвентарь и прогнать playbook.
Итоги
Jinja2-шаблоны через модуль template генерируют конфиги под каждый хост, подставляя переменные и факты, с условиями и циклами. validate защищает от битых конфигов, notify перезапускает сервис только при изменении. Это вершина связки «переменные + факты + шаблоны». Дальше упакуем всё в роли.