Jinja2 — From Data to Config File
Summary in one line
A template breaks "a configuration file that differs per environment" into one frame + per-environment data.
Why this is needed
If you keep a separate nginx configuration for each of dev, stage, and prod, the three files drift apart. Every time a backend server is added you have to edit three places, and if you miss one, only that environment gets flooded with traffic. A template keeps the server list as data and lets you write the frame only once. When backends are added, only one line of data grows.
How it works
Jinja2 uses only three kinds of syntax.
{{ 값 }}— outputs a value{% 제어 %}— control statements such as if / for{# 주석 #}— a comment that is not rendered
On top of this come filters. You chain them with a pipe, as in {{ name | upper }}, {{ timeout | default(30) }}, {{ data | to_nice_json }}. The default filter is especially important — it gives a safe fallback value to a value that may be undefined, so that <no value> or an error does not end up in the output when the variable is missing.
The most frequent problem is whitespace. A control statement such as {% for %} occupies a line of its own, so blank lines are left in the rendered result. In a YAML configuration, those blank lines can even break the file. There are two solutions. Use {%- / -%} in the statement to strip the whitespace before and after it, or turn on trim_blocks/lstrip_blocks for the template module.
Two options of the template module are also worth knowing, because they prevent accidents. validate checks the rendered result with a specified command and puts the file in place only if it passes. backup: true keeps a copy before overwriting. The moment you deploy configuration files automatically, a path opens by which a bad configuration can kill the service. validate is the cheapest safeguard that blocks that path.
What it looks like in the field
First, a template whose rendered result changes every time. If you put a timestamp in a comment, the file changes every run, the handler runs every time, and the service restarts every time. There are plenty of cases where a zero-downtime deployment collapses because of this single line.
Second, a template that uses facts. If you use values from ansible_facts as they are, a different configuration comes out automatically for each server. However, in special environments facts can produce unexpected values, so set up a line of defense with default.
Third, putting too much logic into a template. When ifs are stacked five levels deep, that is not a template but a program. It is better for maintenance to lift such branching up into the variable computation stage and keep the template simple.
Filters you use often in templates
The value of Jinja2 comes from its filters. Knowing just these covers most needs.
{{ port | default(8080) }} 값이 없으면 기본값
{{ name | mandatory }} 없으면 에러 — 조용한 빈 값을 막는다
{{ items | join(',') }} 목록을 문자열로
{{ config | to_nice_yaml(indent=2) }} 딕셔너리를 YAML 블록으로
{{ secret | b64encode }} 쿠버네티스 시크릿용
{{ path | basename }} 경로 조각
{{ hosts | map(attribute='ip') | list }} 목록에서 필드만 뽑기
The habit of using mandatory is important. When a variable is missing, Jinja2 by default inserts an empty
string. That produces a broken configuration such as listen ;, and the problem only shows up when the service
restarts.
If you set this in ansible.cfg, an undefined variable immediately becomes an error.
[defaults]
error_on_undefined_vars = True
How to handle whitespace
Most of the blank lines that pile up in a generated file are caused by control structures.
{% for h in hosts %}
server {{ h }};
{% endfor %}
Written this way, a newline is left for every {% %} line. Add hyphens to remove them.
{% for h in hosts -%}
server {{ h }};
{% endfor -%}
Ansible's template module does not turn on trim_blocks by default, so if you need it,
set it on the first line of the template.
#jinja2: trim_blocks: True, lstrip_blocks: True
How to test a template
Look at the result before you deploy.
# 렌더링 결과만 보기 (파일을 쓰지 않는다)
ansible -i inv web -m template -a "src=nginx.conf.j2 dest=/tmp/out.conf" --check --diff
# 문법 검사
ansible-playbook site.yml --syntax-check
# 실제로 무엇이 바뀌는지
ansible-playbook site.yml --check --diff
The --check --diff combination is the most useful. It shows you what would change without changing anything.
If you do not run this before a production deployment, a single line of configuration can stop the service.
Then add a step that verifies the generated configuration with that program's own checker.
- name: nginx 설정
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: 'nginx -t -c %s' # ← 실패하면 파일을 바꾸지 않는다
notify: reload nginx
validate checks a temporary file and moves it into place only when it passes. A broken configuration
never reaches the disk.
What you will do in the next lab
Starting from basic rendering, you use filters, loops, and conditions in turn, and use whitespace control to make the output clean. You use facts and inventory_hostname in a template, and use validate to keep a bad configuration from being put in place. Finally, you generate a whole nginx configuration from the backend list in a group variable.