Best Practices

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
[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
$ 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
  • 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
  • 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.
  • Apache server restarted when configuration file sent over
  • restart_apache triggers when notified by copy 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
  • 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.
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"

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"
        
  • 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
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
  • Default: Role tasks execute before tasks of playbooks in which they appear
  • To override default, use pre_tasks and post_tasks
    • pre_tasks: Tasks performed before any roles applied
    • post_tasks: Tasks performed after all roles completed
---
- hosts: remote.example.com
  pre_tasks:
    - shell: echo 'hello'
  roles:
    - role1
    - role2
  tasks:
    - shell: echo 'still busy'
  post_tasks:
    - shell: echo 'goodbye'
  • 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 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
  • 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
  • Use descriptive, unique human-meaningful variable names
  • Prefix role variables with role name
haproxy_max_keepalive: 25
haproxy_port: 80
tomcat_port: 8080
  • 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
  • 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