Project
Version Control
Keep your playbooks and inventory file in git (or another version control system), and commit when you make changes to them. This way you have an audit trail describing when and why you changed the rules that are automating your infrastructure.
Tip
Always use version control!
Take a look at the Development section for additional information.
Ansible configuration
Always use a project-specific ansible.cfg
in the parent directory of your project. The following configuration can be used as a starting point:
[defaults]
# Define inventory, no need to provide '-i' anymore.
inventory = inventory/production.ini
# Playbook-Output in YAML instead of JSON
callback_result_format = yaml
Show check mode
The following parameter enables displaying markers when running in check mode.
The markers are DRY RUN
at the beginning and ending of playbook execution (when calling ansible-playbook --check
) and CHECK MODE
as a suffix at every play and task that is run in check mode.
Example
$ ansible-playbook -i inventory.ini playbook.yml -C
DRY RUN ******************************************************************
PLAY [Install and configure Worker Nodes] [CHECK MODE] *******************
TASK [Gathering Facts] [CHECK MODE] **************************************
ok: [k8s-worker1]
ok: [k8s-worker2]
ok: [k8s-worker2]
...
Show task path when failed
For easier development when handling with very big playbooks, it may be useful to know which file holds the failed task. To display the path to the file containing the failed task and the line number, add this parameter:
Example
When set to true
:
...
TASK [Set motd message for k8s worker node] **************************************************
task path: /home/timgrt/kubernetes-installation/roles/kube-worker/tasks/configure.yml:39
fatal: [k8s-worker1]: FAILED! =>
...
When set to false
:
Even if you don't set this, the path is displayed automatically for every task when running with -vv
or greater verbosity, but you'll need to run the playbook again.
Dependencies
Your project will have certain dependencies, make sure to provide a requirements.yml
for necessary Ansible collections and a requirements.txt
for necessary Python packages.
Consider using Execution Environments where all dependencies are combined in a Container Image.
Collections
Always provide a requirements.yml
with all collections used within your project.
This makes sure that required collections can be installed, if only the ansible-core binary is installed.
Install all collections from the requirements-file:
Python packages
Always provide a requirements.txt
with all Python packages need by modules used within your project.
Install all dependencies from the requirements-file:
Directory structure
.
├── ansible.cfg
├── hosts
├── k8s-install.yml
├── README.md
├── requirements.txt
├── requirements.yml
└── roles
├── k8s-bootstrap
│ ├── files
│ │ ├── daemon.json
│ │ └── k8s.conf
│ ├── tasks
│ │ ├── install-kubeadm.yml
│ │ ├── main.yml
│ │ └── prerequisites.yml
│ └── templates
│ └── kubernetes.repo.j2
├── k8s-control-plane
│ ├── files
│ │ └── kubeconfig.sh
│ └── tasks
│ └── main.yml
└── k8s-worker-nodes
└── tasks
└── main.yml
Filenames
Folder- and file-names consisting of multiple words are separated with hyphens (e.g. roles/grafana-deployment/tasks/grafana-installation.yml
).
YAML files are saved with the extension .yml
.
.
├── ansible.cfg
├── hosts
├── k8s-install.yml
├── README.md
├── requirements.yml
└── roles
├── k8s-bootstrap
│ ├── files
│ │ ├── daemon.json
│ │ └── k8s.conf
│ ├── tasks
│ │ ├── install-kubeadm.yml
│ │ ├── main.yml
│ │ └── prerequisites.yml
│ └── templates
│ └── kubernetes.repo.j2
├── k8s-control-plane
│ ├── files
│ │ └── kubeconfig.sh
│ └── tasks
│ └── main.yml
└── k8s-worker-nodes
└── tasks
└── main.yml
Playbook-name without hyphens and wrong file extension, role folders or task files inconsistent, with underscores and wrong extension.
.
├── ansible.cfg
├── hosts
├── k8s-install.yaml
├── README.md
└── roles
├── k8s_bootstrap
│ ├── files
│ │ ├── daemon.json
│ │ └── k8s.conf
│ ├── tasks
│ │ ├── installKubeadm.yaml
│ │ ├── main.yml
│ │ └── prerequisites.yaml
│ └── templates
│ └── kubernetes.repo.j2
├── k8sControlPlane
│ ├── files
│ │ └── kubeconfig.sh
│ └── tasks
│ └── main.yaml
└── k8s_worker-nodes
└── tasks
└── main.yaml
Subject to change
Maybe this has to change in the future, as collection roles only allow underscores for separation.
See Ansible Docs - Roles directory for more information.
Also, ansible-lint checks role names to ensure they conform these requirements, which must be disabled otherwise.
YAML Syntax
Following a basic YAML coding style across the whole team improves readability and reusability.
Indentation
Two spaces are used to indent everything, e.g. list items or dictionary keys.
The so-called YAML "one-line" syntax is not used, neither for passing parameters in tasks, nor for lists or dictionaries.
Booleans
Use true
and false
for boolean values in playbooks.
Do not use the Ansible-specific yes
and no
as boolean values in YAML as these are completely custom extensions used by Ansible and are not part of the YAML spec. Also, avoid the use of the Python-style True
and False
for boolean values.
YAML 1.1 allows all variants whereas YAML 1.2 allows only true/false, you can avoid a massive migration effort for when it becomes the default.
Use the | bool
filter when using bare variables (expressions consisting of just one variable reference without any operator) in when
conditions.
Using a variable upgrade_allowed
with the default value false
, task is executed when overwritten with true
value.
Quoting
Do not use quotes unless you have to, especially for short module-keyword-like strings like present, absent, etc.
When using quotes, use the same type of quotes throughout your playbooks. Always use double quotes ("
), whenever possible.
Comments
Use loads of comments!
Well, the name parameter should describe your task in detail, but if your task uses multiple filters or regex's, comments should be used for further explanation.
Commented code is generally to be avoided. Playbooks or task files are not committed, if they contain commented out code.
Bad
Why is the second task commented? Is it not necessary anymore? Does it not work as expected?
- name: Change port to {{ grafana_port }}
community.general.ini_file:
path: /etc/grafana/grafana.ini
section: server
option: http_port
value: "{{ grafana_port }}"
become: true
notify: restart grafana
# - name: Change theme to {{ grafana_theme }}
# ansible.builtin.lineinfile:
# path: /etc/grafana/grafana.ini
# regexp: '.*default_theme ='
# line: "default_theme = {{ grafana_theme }}"
# become: yes
# notify: restart grafana
Comment commented tasks
If you really have to comment the whole task, add a description why, when and by whom it was commented.