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_apache
triggers when notified bycopy
about 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-galaxy
init 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_tasks
andpost_tasks
pre_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-vault
to 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-vault
options 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