ANSIBLE BEST PRACTICES
FOR STARTUPS TO
ENTERPRISES
Timothy Appnel, Principal Product Manager
James Martin, Automation Practice Lead
Ansible by Red Hat
June 28, 2016
THE ANSIBLE WAY
COMPLEXITY KILLS
Strive for simplification
Optimize for readability
Think declaratively
WORKFLOW
WORKFLOW
Create a style guide for your playbook developers
Consistency in:
Tagging
Whitespace
Naming of Tasks, Plays, Variables, and Roles
Directory Layouts
Enforce the style
WORKFLOW
Version control your Ansible content
Iterate
Start with a basic playbook and static inventory
Refactor and modularize later
PROJECT LAYOUTS: BASIC
basic-project/
config.yml
inventory
group_vars
host_vars
hosts
provision.yml
site.yml
PROJECT LAYOUT: ORGANIZATIONAL ROLES
myapp/
config.yml
provision.yml
roles
myapp
tasks
main.yml
etc.etc
nginx
etc.etc
proxy
etc.etc
site.yml
PROJECT LAYOUTS: SHARED ROLES
myapp/
config.yml
provision.yml
roles
requirements.yml
setup.yml
USE EDITOR WITH SYNTAX
HIGHLIGHTING
Vim
pearofducks/ansible-vim
Glench/Vim-Jinja2-Syntax
Sublime
Atom
Emacs
INVENTORY
INVENTORY
Give inventory nodes human-meaningful names rather than
IPs or DNS hostnames.
Groups -- the more the better.
Use a single source of truth if you have it -- even if you multiple
sources Ansible can unify them.
MEANINGFUL INVENTORY NAMES
10.1.2.75
10.1.5.45
10.1.4.5
10.1.0.40
db1
db2
db3
db4
w14301.acme.com
w17802.acme.com
W19203.acme.com
w19304.acme.com
web1
web2
web3
web4
ansible_host=10.1.2.75
ansible_host=10.1.5.45
ansible_host=10.1.4.5
ansible_host=10.1.0.40
ansible_host=w14301.acme.com
ansible_host=w17802.acme.com
ansible_host=w19203.acme.com
ansible_host=w19203.acme.com
INVENTORY GROUPS
[db]
db[1:4]
[web]
web[1:4]
[east]
db1
web1
db3
web3
[west]
db2
web2
db4
web4
[dev]
db1
web1
[testing]
db3
web3
[prod]
db2
Web2
db4
web4
DYNAMIC INVENTORY
Reduces human error
Lots of existing scripts
Roll your own
VARIABLES
VARIABLES
Use descriptive, unique human-meaningful variable names
Prefix role variables with role name
apache_max_keepalive: 25
apache_port: 80
tomcat_port: 8080
VARIABLES
No bare variables -- now deprecated in v2.0+
with_items: my_list
# NO
with_items: "{{ my_list }}" # YES
VARIABLES
Separate logic (tasks) from variables and reduce repetitive
patterns
EXHIBIT A
- name: Clone student lesson app for a user
host: nodes
tasks:
- name: Create ssh dir
file:
state: directory
path: /home/{{ username }}/.ssh
- name: Set Deployment Key
copy:
src: files/deploy_key
dest: /home/{{ username }}/.ssh/id_rsa
- name: Clone repo
git:
accept_hostkey: yes
clone: yes
dest: /home/{{ username }}/lightbulb
key_file: /home/{{ username }}/.ssh/id_rsa
repo: [email protected]:example/apprepo.git
EXHIBIT B
- name: Clone student lesson app for a user
host: nodes
vars:
user_home: /home/{{ username }}
user_ssh: "{{ user_home }}/.ssh"
deploy_key: "{{ user_ssh }}/id_rsa"
example_app_dest: "{{ user_home }}/exampleapp"
tasks:
- name: Create ssh dir
file:
state: directory
path: "{{ user_ssh }}"
- name: Set Deployment Key
copy:
src: files/deploy_key
dest: "{{ deploy_key }}"
- name: Clone repo
git:
dest: "{{ example_app_dest }}"
key_file: "{{ deploy_key }}"
repo: [email protected]:example/exampleapp.git
accept_hostkey: yes
clone: yes
PLAYS & TASKS
PLAYS & TASKS
Use native YAML syntax for your plays
Vertical reading is easier
Supports complex parameter values
NO!
- name: install telegraf
yum: name=telegraf-{{ telegraf_version }} state=present update_cache=yes disable_gpg_check=yes enablerepo=tel
notify: restart telegraf
- name: configure telegraf
template: src=telegraf.conf.j2 dest=/etc/telegraf/telegraf.conf
- name: start telegraf
service: name=telegraf state=started enabled=yes
Better, but no
- name: install telegraf
yum: >
name=telegraf-{{ telegraf_version }}
state=present
update_cache=yes
disable_gpg_check=yes
enablerepo=telegraf
notify: restart telegraf
- name: configure telegraf
template: src=telegraf.conf.j2 dest=/etc/telegraf/telegraf.conf
- name: start telegraf
service: name=telegraf state=started enabled=yes
- name: install telegraf
yum:
name: telegraf-{{ telegraf_version }}
state: present
update_cache: yes
disable_gpg_check: yes
enablerepo: telegraf
notify: restart telegraf
- name: configure telegraf
template:
src: telegraf.conf.j2
dest: /etc/telegraf/telegraf.conf
notify: restart telegraf
- name: start telegraf
service:
name: telegraf
state: started
enabled: yes
YES
PLAYS & TASKS
Give all your playbooks and tasks brief, reasonably unique and
human-meaningful names
USE HUMAN MEANINGFUL NAMES
EXHIBIT A
- hosts: web
tasks:
- yum:
name: httpd
state: latest
- service:
name: httpd
state: started
enabled: yes
EXHIBIT B
- hosts: web
name: installs and starts apache
tasks:
- name: install apache packages
yum:
name: httpd
state: latest
- name: starts apache service
service:
name: httpd
state: started
enabled: yes
USE HUMAN MEANINGFUL NAMES
EXHIBIT A
EXHIBIT B
PLAY [web]
*****************************************
PLAY [installs and starts apache]
******************************************
TASK [setup]
*****************************************
ok: [web1]
TASK [setup]
******************************************
ok: [web1]
TASK [yum]
*****************************************
ok: [web1]
TASK [install apache packages]
******************************************
ok: [web1]
TASK [service]
*****************************************
ok: [web1]
TASK [starts apache service]
******************************************
ok: [web1]
PLAYS & TASKS
Keep plays and playbooks focused. Multiple simple ones are
better than having a huge one full of conditionals
PLAYS & TASKS
Separate provisioning from deployment and configuration tasks
acme_corp/
configure.yml
provision.yml
site.yml
$ cat site.yml
--- include: provision.yml
- include: configure.yml
PLAYS & TASKS
Use the run command modules (shell/command/script/raw) as
a last resort
command module is safer than shell
ALWAYS SEEK OUT A MODULE FIRST
EXHIBIT A
- name: add user
command: useradd appuser
- name: install apache
command: yum install httpd
- name: start apache
shell: |
service httpd start && chkconfig httpd on
$ ansible-doc <module_name>
http://docs.ansible.com/ansible/modules_by_category.html
EXHIBIT B
- name: add user
user:
name: appuser
state: present
- name: install apache
yum:
name: httpd
state: latest
- name: start apache
service:
name: httpd
state: started
enabled: yes
PLAYS & TASKS
Remove 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
PLAYS & TASKS
Dont 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
USING RUN COMMANDS A LOT?
- hosts: all
vars:
cert_store: /etc/mycerts
cert_name: my cert
tasks:
- name: check cert
shell: certify --list --name={{ cert_name }} --cert_store={{ cert_store }} | grep "{{ cert_name }}"
register: output
- name: create cert
command: certify --create --user=chris --name={{ cert_name }} --cert_store={{ cert_store }}
when: output.stdout.find(cert_name)" != -1
register: output
- name: sign cert
command: certify --sign --name={{ cert_name }} --cert_store={{ cert_store }}
when: output.stdout.find("created")" != -1
CONSIDER WRITING A MODULE
- hosts: all
vars:
cert_store: /etc/mycerts
cert_name: my cert
tasks:
- name: create and sign cert
certify:
state: present
sign: yes
user: chris
name: "{{ cert_name }}"
cert_store: "{{ cert_store }}"
http://docs.ansible.com/ansible/developing_modules.html
TEMPLATES
TEMPLATES
Jinja2 is powerful but you needn't use all of it
Templates should be simple:
Variable substitution
Conditionals
Simple control structures/iterations
Design for your use case, not the world's
EXAMPLES OF THINGS TO AVOID
Managing variables in a template
Extensive and intricate conditionals
Conditional logic based on hostnames
Complex nested iterations
TEMPLATES
Label template output files as being generated by Ansible
Consider using the ansible_managed** variable with the
comment filter
{{ ansible_managed | comment }}
ROLES
ROLES
Like playbooks -- keep roles purpose and function focused
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
ROLES
Use ansible-galaxy init to start your roles
...then remove unneeded directories and stub files
Use ansible-galaxy to install your roles -- even private ones
ROLES CAN LIVE WITH YOUR APP CODE
Manage your roles in your applications repo
As part of build process, push role to artifact repository
Use ansible-galaxy to install role from artifact repository
SCALING YOUR ANSIBLE WORKFLOW
SCALING YOUR ANSIBLE WORKFLOW
Coordination across a distributed organization
Controlling access to credentials...
Track, audit and report Ansible usage...
Provide self-service or delegation
Integrate Ansible with enterprise systems...
Join us for an Interactive Discovery Session
For an in-depth and interactive whiteboard discussion on how you can discover,
assess, and prioritize opportunities to streamline IT processes with Ansible
automation
Reduce complexity and increase optimization with Ansible automation
Jonathan Davila, Architect, Automation, Red Hat
Wednesday, June 29, 3:30 PM - 4:30 PM (West Lobby, Level 2)
To learn more, visit red.ht/discoverysession