Это одна из самых рабочих и самых экзаменуемых тем. Если коротко: ansible template - это про то, как из единственного .j2-файла получить N разных, но корректных конфигов. Поехали разбираться, как это устроено.
Чем template отличается от copy и зачем тут jinja2 шаблоны
Модуль ansible.builtin.copy кладёт файл на узел КАК ЕСТЬ, байт в байт. Никакой обработки содержимого - что в исходнике, то и на сервере. Это идеально для бинарников, ключей, статики.
Модуль ansible.builtin.template работает иначе: он берёт исходный файл (по соглашению с расширением .j2), прогоняет его через движок шаблонов Jinja2, подставляет переменные, факты, выполняет циклы и условия - и только результат кладёт на целевой хост. То есть template = copy + рендеринг.
Внутри .j2-файла действуют три конструкции Jinja2, которые надо запомнить намертво:
- {{ ... }} - выражение, подставить значение. Например {{ http_port }} превратится в 80.
- {% ... %} - управляющая логика: циклы for, условия if, set.
- {# ... #} - комментарий, в итоговый файл НЕ попадёт.

Переменные, факты, циклы и условия внутри шаблона
В jinja2 шаблоны попадает ВЕСЬ контекст переменных Ansible, который виден задаче: vars, group_vars, host_vars, defaults роли, а также собранные факты (ansible_facts). Это и есть суперсила: конфиг подстраивается под конкретную машину сам.
Простой пример - кусок шаблона nginx.conf.j2:
Код: Выделить всё
# {{ ansible_managed }}
user nginx;
worker_processes {{ ansible_facts['processor_vcpus'] }};
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
}
http {
server {
listen {{ http_port }};
server_name {{ ansible_facts['fqdn'] }};
{% if enable_tls %}
listen 443 ssl;
ssl_certificate /etc/pki/tls/certs/{{ cert_name }}.crt;
ssl_certificate_key /etc/pki/tls/private/{{ cert_name }}.key;
{% endif %}
{% for backend in upstreams %}
location {{ backend.path }} {
proxy_pass http://{{ backend.host }}:{{ backend.port }};
}
{% endfor %}
}
}
А вот переменные, которые этот шаблон ждёт, например в group_vars/prod.yml:
Код: Выделить всё
http_port: 80
enable_tls: true
cert_name: cyberlake
nginx_worker_connections: 4096
upstreams:
- path: /
host: 10.0.0.10
port: 8080
- path: /api
host: 10.0.0.11
port: 9000
Отдельно про {{ ansible_managed }} в первой строке. Это специальная переменная-пометка, что файл сгенерирован Ansible и править руками его бесполезно (затрётся при следующем прогоне). По умолчанию она разворачивается в "Ansible managed", но в ansible.cfg её можно расширить, чтобы видеть имя шаблона, хост и время:
Код: Выделить всё
[defaults]
ansible_managed = ВНИМАНИЕ: файл сгенерирован Ansible из {file} на {host} - не редактировать руками!
Сам вызов в плейбуке выглядит так:
Код: Выделить всё
- name: Развернуть конфиг nginx из шаблона
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: true
validate: nginx -t -c %s
notify: Restart nginx
- src / dest - откуда шаблон и куда положить результат. Обязательные.
- owner / group / mode - владелец и права итогового файла. mode пиши строкой в кавычках ('0644'), иначе YAML съест ведущий ноль как восьмеричное число и ты получишь не те права. Это классический срез.
- backup: true - перед заменой сохранит старую версию рядом с timestamp в имени. Резервная копия создаётся ТОЛЬКО когда файл реально изменился, идемпотентные прогоны мусор не плодят.
- validate - команда проверки ДО установки. %s - плейсхолдер, куда Ansible подставит путь к временному отрендеренному файлу. Если проверка вернёт не ноль - файл на место НЕ встанет, и ты не положишь битый конфиг, который уронит сервис. Для nginx это nginx -t -c %s, для sshd - /usr/sbin/sshd -t -f %s, для sudoers - visudo -cf %s.
После template обычно идёт notify на хендлер перезапуска сервиса (ansible.builtin.systemd_service или service), плюс на RHEL не забывай про firewalld и SELinux: порт 8080 нестандартный, и SELinux его не пропустит, пока не добавишь его в http_port_t через community.general.seport. На Ubuntu/Debian путей и имён пакетов меньше (nginx тот же, но конфиг в /etc/nginx/sites-available/, апач называется apache2), на RED OS и Astra - всё как в RHEL-семействе и Debian-семействе соответственно.
Типичные грабли
- mode без кавычек. mode: 0644 в YAML - это число, права получатся кривые. Всегда '0644' строкой.
- undefined-переменная. Если в шаблоне есть {{ foo }}, а foo нигде не задана, рендеринг падает с "AttributeError" или "is undefined". Лечится фильтром default или проверкой {% if foo is defined %}.
- Лишние пустые строки от блоков. Строки с {% if %} и {% for %} оставляют после себя перевод строки. Если конфиг чувствителен к этому, включи trim_blocks/lstrip_blocks - в Ansible они по умолчанию включены для template, но в кастомных окружениях проверь. Минус-синтаксис {%- ... -%} тоже подрезает пробелы.
- Забыл .j2 положить в templates/. В роли шаблоны живут строго в templates/, иначе src его не найдёт.
- Перепутал template и copy. Положил конфиг с {{ }} через copy - и на сервере остались голые двойные скобки, потому что copy ничего не рендерит. Конфиги с переменными - только через template.
- Создай роль: ansible-galaxy role init webconf. Внутри будет каталог templates/.
- Положи в templates/nginx.conf.j2 шаблон из урока с переменной http_port, фактом processor_vcpus, условием enable_tls и циклом по upstreams.
- Заведи group_vars/dev.yml и group_vars/prod.yml с разными значениями http_port и enable_tls.
- Напиши задачу template с mode: '0644', backup: true и validate: nginx -t -c %s.
- Прогони на dev и prod, сравни результат: cat /etc/nginx/nginx.conf на обоих узлах должен отличаться, хотя шаблон один.
- Сломай шаблон специально (убери закрывающую скобку в server-блоке) и убедись, что validate не дал положить битый файл.
- Поменяй значение переменной, прогони ещё раз - проверь, что появился .backup-файл, а при повторном прогоне без изменений - changed=0.
- Чем template принципиально отличается от copy и в каком случае что использовать?
- Что делает параметр validate и почему %s в команде обязателен? Приведи команду проверки для sshd_config.
- Зачем mode писать строкой '0644', а не числом 0644?
- Как внутри .j2 подставить факт о числе ядер и как защититься от необъявленной переменной?
template = copy + движок Jinja2: один .j2-шаблон с переменными, фактами, циклами и условиями превращается в правильный конфиг на каждом узле. Запомни связку owner/group/mode (строкой), backup для отката и validate для защиты от битых конфигов, плюс {{ ansible_managed }} как пометку автогенерации. На RHCE/EX294 template встречается почти всегда - чаще всего просят раскатить конфиг сервиса из шаблона по переменным окружения, и именно validate с правильными правами отделяет зачёт от провала.