Best Practices
Inventory
Inventory - Use Human Meaningful Names
Poor Better 10.1.2.75 db1 ansible_host=10.1.2.75 10.1.5.45 db2 ansible_host=10.1.5.45 w14301.acme.com web1 ansible_host=w14301.acme.com w17802.acme.com web2 ansible_host=w17802.acme.com
Inventory - Group Hosts Generously
[db]
db[1:4]
[web]
web[1:4]
[east]
db1
web1
db3
web3
[dev]
db1
web1
[west]
db2
web2
db4
web4
[testing]
db3
web3
[prod]
db2
web2
db4
web4
--limit (see also --tags)
$ ansible-playbook site.yml --limit 'web' # Only group web $ ansible-playbook site.yml --limit 'web,db3' # Group web and db3 $ ansible-playbook site.yml --limit 'all:!prod' # All non group prod
Plays and Tasks
- Keep plays and playbooks focused. Multiple simple ones are better than having a huge single playbook full of conditionals.
- Focus avoids complexity
- Separate provisioning from deployment and configuration tasks
$ ls acme_corp/ ├── configure.yml ├── provision.yml └── site.yml
$ cat site.yml --- - import_playbook: provision.yml - import_playbook: configure.yml
Blocks and Handlers
Handlers
- Handler: Task that responds to notification triggered by other tasks
- Has globally unique name
- Triggered at end of block of tasks in playbook
- “Inactive” task that must be triggered using
notify- Does not run if not notified by name
- If notified, runs once after all other tasks in play have completed.
- Use same modules in handlers as for any other task
- Normal use cases:
- Reboot hosts
- Restart services.
Handlers Example
- Apache server restarted when configuration file sent over
restart_apachetriggers when notified bycopyabout change:
tasks:
- name: Copy example conf to apache servers
copy
src: /var/lib/templates/demo.example.conf.template
dest: /etc/httpd/conf.d/demo.example.conf
notify:
- restart_apache
handlers:
- name: restart_apache
service:
name: httpd
state: restarted
Plays and Tasks - Blocks
- Complex playbooks may contain long list of tasks
- Some tasks related in function
- Blocks: Alternative method of task organization
- Use to group related tasks
- Improves readability
- Allows performance of task parameters at block level
- Blocks also introduce the ability to handle errors in a way similar to exceptions in most programming languages.
Block error handling example
tasks:
- name: Attempt and gracefully roll back demo
block:
- debug:
msg: "I execute normally"
- command: /bin/false
- debug:
msg: "I never execute, due to the above task failing"
rescue:
- debug:
msg: "I caught an error"
- command: /bin/false
- debug:
msg: "I also never execute :-("
always:
- debug:
msg: "this always executes"
Block run handlers in error handling
Another example is how to run handlers after an error occurred :
tasks:
- name: Attempt and gracefull roll back demo
block:
- debug:
msg: "I execute normally"
notify: run me even after an error
- command: /bin/false
rescue:
- name: make sure all handlers run
meta: flush_handlers
handlers:
- name: run me even after an error
debug:
msg: "this handler runs even on error"
Roles
- Like playbooks — keep roles purpose and function focused
- Limit role dependencies
- Use a roles/ subdirectory for roles developed for organizational clarity in a single project
- Follow the Ansible Galaxy pattern for roles that are to be shared beyond a single project
- Use
ansible-galaxyinit to start your roles.
- Use ansible-galaxy to install your roles — even private ones
- Use a roles files (i.e. requirements.yml) to manifest any external roles your project is using
- Always peg a role to a specific version such as a tag or commit
Shared Roles Structure
myapp/ ├── config.yml ├── provision.yml ├── roles │ └── requirements.yml └── setup.yml
$ ansible-galaxy install -r requirements.yml
$ cat requirements.yml # from galaxy - src: yatesr.timezone # from GitHub - src: https://github.com/bennojoy/nginx version: v1.4 # from a webserver, where the role is packaged in a tar.gz - src: https://some.webserver.example.com/files/master.tar.gz name: http-role
Tasks - Order of Execution
- Default: Role tasks execute before tasks of playbooks in which they appear
- To override default, use
pre_tasksandpost_taskspre_tasks: Tasks performed before any roles appliedpost_tasks: Tasks performed after all roles completed
Order of Execution Example
---
- hosts: remote.example.com
pre_tasks:
- shell: echo 'hello'
roles:
- role1
- role2
tasks:
- shell: echo 'still busy'
post_tasks:
- shell: echo 'goodbye'
Templates
- Templates should be simple:
- Variable substitution
- Conditionals
- Simple control structures/iterations
- Design for your use case, not the world’s
- Things to avoid:
- Managing variables in a template
- Extensive and intricate conditionals
- Conditional logic based on hostnames
- Complex nested iterations
- Label template output files as being generated by Ansible
- Tells users file created by Ansible and changes are likely to be overwritten.
- Consider using the ansible_managed** variable with the comment filter
{{ ansible_managed | comment }}
Ansible Vault
- Ansible users can use
ansible-vaultto encrypt any sensitive Ansible structured data file- Inventory variables and Variable files passed as arguments
- Variable files included in playbook or defined in roles
ansible-vaultoptions include:- Create:
ansible-vault create secret.yml - Encrypt:
ansible-vault encrypt secret1.yml secret2.yml - View:
ansible-vault view FILENAME - Decrypt:
ansible-vault decrypt FILENAME
- To run playbook using ansible-vault, specify:
–ask-vault-pass–vault-password-file:
[student@demo ~]$ ansible-playbook --ask-vault-pass site.yml Vault password: redhat
Misc
Variables
- Find the appropriate place for your variables based on what, where and when they are set or modified
- Separate logic (tasks) from variables and reduce repetitive patterns
Proper variable names can make plays more readable and avoid variable name conflicts
- Use descriptive, unique human-meaningful variable names
- Prefix role variables with role name
haproxy_max_keepalive: 25 haproxy_port: 80 tomcat_port: 8080
Smoke Tests
- Don’t just start services — use smoke tests
- name: check for proper response
uri:
url: http://localhost/myapp
return_content: yes
register: result
until: '"Hello World" in result.content'
retries: 10
delay: 1
Plays and Tasks - Debugging
- Clean up your debug tasks in production or make them optional with the verbosity param in v2.1
- debug: msg: "This always displays" - debug: msg: "This only displays with ansible-playbook -vv+" verbosity: 2