Two audiences read a template’s output, and they are not the same people.
The reviewer reads the template in a pull request and has to decide
whether the change is safe. The operator reads the rendered file on
a host at three in the morning and has to work out where it came from
and whether editing it will help.
Everything in this lesson serves one of those two.
The header, for the operator
A generated file that does not say it is generated will be edited by
hand. Not out of carelessness — because there is nothing on the file to
suggest otherwise, and editing the config in front of you is the obvious
move during an incident.
That edit survives until the next Ansible run and then vanishes, which
is worse than either alternative. The change appeared to work, the
problem appeared to be fixed, and it silently came back hours later.
Read-only / Safethe header worth writing— Three facts an operator needs: that the file is generated, what generates it, and where to change it.
# {{ ansible_managed }}
# Template: roles/webserver/templates/site.conf.j2
# Source of truth: git.example.com/infra/ansible
#
# Local edits to this file are overwritten on the next Ansible run.
# To change it, change the template or the variables and deploy.
server {
listen {{ listen_port }};
...
}
ansible_managed is a variable made available to template, whose
default value is the string Ansible managed. Verified on 2.21.3 —
ansible-config dump reports DEFAULT_MANAGED_STR(default) = Ansible managed.
Read-only / Safesetting the header as a variable— Forward-compatible, visible in group_vars, and can reference anything else available at render time.
# group_vars/all.yml
ansible_managed: >-
This file is managed by Ansible. Local edits are overwritten.
Source: git.example.com/infra/ansible
Comment discipline
The header is for provenance. Comments in the body are for the operator
who needs to understand a value.
Two rules that hold up:
Comment the non-obvious value, not the syntax. A comment saying
# set the worker count above worker_processes is noise. A comment
saying # 4 = one per core on the c5.xlarge fleet; see INFRA-2291 is
the reason the number is what it is, and it is the thing nobody can
reconstruct.
Render the reason, not the variable name. A rendered file saying
worker_processes 8; with no context makes the reader open the role.
worker_processes {{ workers }}; # from group_vars/webservers.yml is a
Jinja comment away from telling them where to change it — though note
that {# #} comments are removed from the output, so a comment you want
the operator to see must be a comment in the target file’s own syntax.
The size limit, for the reviewer
Here is the failure this section is about. A template starts at 40
lines. A new environment needs one thing different, so an {% if %}
appears. Then a second environment, then a special case for one host,
then a nested condition inside a loop. Eighteen months later it is 300
lines with six levels of nesting, and nobody can say what it renders for
a given host without running it.
At that point the template has stopped being reviewable. A reviewer
faced with a three-line diff inside a nested conditional cannot answer
“which hosts does this affect”, which means the review is a formality.
Moving logic out
The general move is: the template renders, the variables decide.
Read-only / Safebefore - the decision is in the template— A reviewer has to hold four cases in their head, and the values are buried in the layout.
Read-only / Safeafter - the decision is in group_vars— Two lines. The values live where environment-specific values already live, and the diff for a tuning change is one line in one file.
The values move to group_vars/prod.yml, group_vars/staging.yml and
the role’s defaults/main.yml. What you gain is not brevity — it is
that variable precedence now handles the layering, one mechanism instead
of two, and a reviewer looking at a tuning change sees a one-line diff
in a file named after the environment it affects.
Knowledge check
Knowledge check · 4 questions
Q1. A team sets ansible_managed to a string containing the render timestamp. What is the consequence?
Q2. What is the current guidance for setting the managed header on ansible-core 2.21?
Q3. Which are signals that a template has grown past what a reviewer can verify? Select all that apply.
Q4. Moving a four-branch environment conditional out of a template and into group_vars replaces one mechanism with an existing one, rather than adding complexity.
Passing score: 75%. Answers are checked in this browser.