Each project usually needs a collection of tasks to build, deploy and perform regular operations during its lifetime.
2 folders are present in this repository:
ansible-example
; offers a view with (fake) data, hosts and vars to illustrate the conventionsansible
; should be blend enough to be copied in place in any project and be the start of the devops related work for the ansible playbooks and tasks.
The conventions are the following:
- An
ansible
folder at the root of the primary repository - Adapted
.gitignore
files to handle propely ansible (roles, logs, retry files) - 1 inventory file per environment (e.g.
inventory.dev
,inventory.staging
) - One
main.yml
playbook to run the whole setup / provisioning from end-to-end - A collection of
setup-*.yml
anddeploy-*.yml
files - Groups named
all-dev
,all-staging
,all-prod
to share variables per environment, vars.dev
andvars.staging
files at the rootansible.cfg
at the root- Vaults when needing to protect data - no password should be kept in-clear
- Project specific roles prefixed with the project name in
roles
requirements.yml
file with the list of roles to install- A comprehensive README file in the
ansible
foldere to explain how things are to be ran
Each project need its collection of scripts / tasks to do the devops related operations.
You must choose in accordance with the project owner the repository most suitable to store this folder.
This folder will contain ALL the tasks / playbooks / config needed to build everything for the project; from the dev environment until the regular release of the code in production.
Make sure you add an ansible.cfg
file just for the sake of having your best practices config available at all time; caching, ssh agent, etc.
We do not want to store useless data, logs, community roles, be sure you update the .gitignore
file. See the one from this repo, it covers some edge cases.
Let's call an inventory an inventory and agree on the naming convention inventory.ENV
.
ENV
is then to be one of either dev
, staging
, prod
and so on.
The inventory files must share the same groups indenpendently of the environment. Playbooks and tasks, should only "care" about those groups and should never address an individual host.
See the example in the ansible-example
folder.
Ansible is able to handle idempotence, but it may sometime be quite slow - due to network or number of tasks. So we prefer to split playbooks and use include:
whenever possible.
Several playbooks are usually found depending of the projects:
main.yml
: is the full A to Z playbook, doing everything from setup to deployment. It usually calls other plugins to do the worksetup-*.yml
playbooks: handle the setup (installation of the services and so-on),deploy-*.yml
playbooks: handle the deployment (depoyment of the code per-se).
Each of those playbooks must be able to run "independently".
Note: Some playbooks are only meant to be executed on subset of the architecture, yet need to be able to refer to other components (e.g. an APP server needs to know the details of the DB servers - even when the task is about deploying the code). You will then need to use the ping trick to ensure your inventories are "pre-loaded" when you execute commands. See the example in the setup-app.yml
playbook in the ansible-example
folder.
Variables comes at various times and from various sources (non-weigthed list):
host_vars
andgroup_vars
folder- playbooks and tasks
- roles / vars and default folders
- extra files and command lines
For host_vars
and group_vars
- prefer the use of folders instead of files to store host and groups vars. It will simplify you life when you need to add the support for other files, or vault.
The all
folder in group_vars
is a catch-all group and will get its values offered to all the hosts defined.
We often use additional similar groups - but applied at the environement level (prod vs. staging vs. dev).
The naming convention for those files are all-ENV
; e.g.
all-dev
for the dev environmentall-staging
for the staging environment
This is however NOT a built-in feature of ansible, and we need to define those groups explicitely in the inventory files.
# Define the all-dev group as a group of .. groups
[all-dev:children]
group1
group2
Variables defined in the group_vars
and host_vars
are applied to hosts - they are NOT available globally. To have variables available globally - and not be tied to a specific host, you need to pass them as an extra var.
Either pass them as -e "my_global_var=toto"
or via a file -e @vars.dev
. We prefer the file approach for reason of clarity.
Such approach allows then to use conditional when:
even at the playbook level, before even having "selected" a pool of servers.
- Run the playbook as such:
ansible-playbook -i inventory.dev -e @vars.dev main.yml
vars.dev
; define the vars specific to the dev environment
---
env: dev
main.yml
; without having yet selected any host
---
- include: dev-only.yml
when: env is defined and env == "dev"
dev-only.yml
; a random playbook aimed at being executed only on dev
- hosts: all
tasks:
- name: something to run on all the hosts, for dev only
ping: {}
Variables set in vaults are encrypted; the resulting vault file is changing 100% of the time upon even the smallest change. It can be confusing.
To limit the amount of change, you will want to create several vault files; that are having different period of a refresh. Some values will never change, other may change on a weekly / monthly basis.
Typically, you will move long lasting variables together.
e.g. Split the SSL certs outside
group_vars/all-staging/vault
group_vars/all-staging/vault-ssl-cert