more tips n tricks
TRANSCRIPT
#>whoami?- Ansible core maintainer
- bcoca @ IRC and github and mailing lists
- worn hats as SA/QA/Programmer/DBA/etc
- tech janitor
2
Why not ‘best practice’?
- No such thing- Many ‘good practices’- Environments are different: Florist e-commerce != VR mobile app- Companies and compliance are different- Rules and standards are great … until they get in the way
- Context is important- Similarities exist but things are rarely identical (siblings not twins)- “One size fits all” solutions are limited and limiting- Adaptability is key, provides choice- Decide on a workflow, use tools to achieve it- Pain appears when systems and workflows are in opposition
TIP: non bad practices
3
PEP8A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is the most important.
However, know when to be inconsistent -- sometimes style guide recommendations just aren't applicable. When in doubt, use your best judgment.
Look at other examples and decide what looks best. And don't hesitate to ask!
4
What is wrong?
- hosts: webservers handlers: - service: name: nginx state: restarted name: restart_nginx tasks: - template: src: templates/nginx.conf dest: /etc/nginx.conf notify: restart_nginx name: Set nginx config pre_tasks: - stat: path=/etc/nginx.conf
Bad Practice
5
- hosts: wx01 tasks: - template: src: templates/xl183.conf dest: /etc/apache2.conf notify: service handlers: - service: name=httpd state=restarted
How can it be better?
TIPS: Better Practices
6
- hosts: webserver01 pre_tasks: - stat: path=/etc/nginx.conf tasks: - name: Configure app02 webserver template: src: templates/webapp02.conf dest: /etc/apache2.conf notify: restart_httpd handlers: - name: restart_httpd service: name=httpd state=restarted
- Nothing syntactically wrong
- Dictionary order does not matter to
Ansible, but it matters to humans
- List order matters to all (- stuff)
- name: is your documentation
- Good host, group and task names
make things evident.
- Relying on explicit behaviour over
implicit keeps things clear
- Nothing I say is mandatory
Basic Layout
TIPS: Inventory
7
ansible/inventorygroup_vars/
all.yml appservers.ymldatabases.ymlloadbalancers.ymlnetwork.ymlwebservers/
secrets.ymlservice_vars.yml
host_vars/webserver01.ymlwebserver02.yml
PROS
- Simple setup
- Quick to get started
- Works for your average app
- Naming by function (not actual names)
CONS
- All or nothing access
- Grouped only on one criteria
Complicating layout
TIPS: Inventory
8
ansible/inventory/production (groups: dc1,dc2)development(idf)staging (dc1)group_vars/
all.yml appservers.ymldatabases.ymlloadbalancers.ymlnetwork.ymlwebservers/
secrets.ymlservice_vars.yml
dc1.ymldc2.yml
idf.yml host_vars/
PROS
- I can target environments by file
- File permissions for access (also ACLs)
- inventory_file or groups for ‘location’ and
‘environment’ variables, avoids overlap.
- Avoids relying on --limit
CONS
- More things to keep track of
- Requires more knowledge to setup
- Geared towards central mgmt host
- Overlaps with --limit
Unix tools use Unix permissions
TIPS: Inventory
9
jumphost#>ls -l /etc/ansible/inventory
drwxr-xr-x 2 root ops 4096 Aug 19 00:55 group_varsdrwxr-xr-x 2 root ops 4096 Aug 12 11:10 host_vars-rw-r----- 1 root ops 1825 Aug 18 03:07 production
-rw-r----- 1 root qa 251 Aug 18 03:07 staging
-rw-r----- 1 root devel 789 Aug 18 03:07 devel
jumphost#> getfacl production
# file: production
# owner: root
# group: ops
user:bcoca:rw-
user::rw-
group::r--
other::---
visible permissionsmy ‘back door’
Designing your inventory
TIPS: Inventory
10
- Inventory is the expression of your environment
- Hostnames, groups, vars are for YOUR use, they have to make sense to YOU
- Ansible cares about hosts and tasks, everything else is in support of that
- Select a single source of truth .. or try to minimize duplication of data
- Normally, there is a simpler way to do it
- Ansible makes it easy to switch approaches, don’t be afraid to test and try
- Mistakes are not failures
Generate YAML Inventory
Tricks
11
- hosts: localhost gather_facts: false tasks: - template: src: dump_hosts_yaml.j2 dest: /tmp/hosts.yml
{% set builtins = ['hostvars', 'vars', … ]%}
{% set dumped_vars = [] %}
{% for group in groups if group != 'all'%}
{{group}}:
{% for host in groups[group] %}
{{ host }}:
{% for myhostvar in hostvars[host] if myhostvar not in builtins %}
{{ myhostvar}}: {{hostvars[host][myhostvar]|to_json}}
{% if loop.last %}{% do dumped_vars.append(host) %}{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
migrate_yaml_inventory.yml dump_hosts_yaml.j2
12
ungrouped: ubuntu15test: testvar: default from all test2: {"key3": "valueall3", "key1": "valueall1"} ansible_python_interpreter: /usr/bin/python2.7 ansible_ssh_host: 192.168.1.7ubuntu: ubuntu12test: ubuntu14test: testvar: default from all test2: {"key3": "valueall3", "key1": "valueall1"}tests: ubuntu12test: ubuntu15test: centos7test: testvar: inv groupvar test2: {"key3": "valueall3", "key1": "valueall1"} cron_service: crond ansible_ssh_host: 192.168.0.24 test_prio: testsaptdistros: ubuntu12test: ubuntu14test:
/tmp/hosts.yml
Generate INI Inventory
Tricks
13
{% set builtins = ['hostvars', 'vars', 'groups', 'group_names', … '] %}{% set
dumped_vars = [] %}
{% for group in groups if group != 'all'%}
[{{group}}]
{% for host in groups[group] %}
{{ host }} {% for myhostvar in hostvars[host] if myhostvar not in builtins and host
not in dumped_vars %} {{ myhostvar}}={{hostvars[host][myhostvar]|to_json}} {% if
loop.last %}{% do dum
ped_vars.append(host) %}{% endif %} {% endfor %}
{% endfor %}
{% endfor %}
dump_hosts_ini.j2
14
[ungrouped]
ubuntu15test testvar=”default from all” test= {"key3": "valueall3", "key1": "valueall1"}
[ubuntu]
ubuntu12test
ubuntu14test testvar=”default from all” test2= {"key3": "valueall3", "key1": "valueall1"}
[tests]
ubuntu12test
ubuntu15test
centos7test testvar=”inv groupvar” ansible_ssh_host="192.168.0.24" test2={"key3": ...
[aptdistros]
ubuntu12test
/tmp/hosts.ini
Variable Sources
TIPS: vars can go in many places
15
PRECEDENCE (low to high):
role defaultsinventory file varsinventory group_vars, host_varsplaybook group_vars, host_varshost factsplay vars, vars_prompt, vars_filesregistered varsset_factsrole parameters and include varsblock(only for tasks in block), task varsextra vars (CLI, global, precedence)
● Many choices, flexible!
● Many choices, confusing!
● Where do I define my var?
○ Locality (host, group, play, role, task)
○ Use only what you need, ignore rest
○ Inheritance and scope (play, host)
● group/host_vars are always loaded
● variables are flattened per host
Variable Sources
TIPS: vars can go in many places
16
inventory/
...
playbooks/
webconfig/
...
certificates/
load_balancer_main.yml
load_balancer_internal.yml
ldap.yml
vars/
vaulted_certificates.yml
- Avoid vault errors
- Allows ‘need to know’
- group/host_vars are still autoloaded
- explicit: vars_files, include_vars from vars/
- vault files require passwords
- ansible cannot know if vault is needed, must
run play and open vault to find out.
- no tardis plugin (yet)
vars_prompt
TIPS: vars can go in many places
17
- Interactive vars
- Per play
- Available only at start
- Smart prompting:
- only if tty is present
- skip if already provided (extra vars)
- Unconditional
vars_prompt:
- name: "myvar"
prompt: "Question?"
Pause
TIPS: vars can go in many places
18
- Interactive vars
- It is a task:
- Can appear at any point a task can
- Tasks are conditional (when, tags)
- host scope, persists across plays
- No validation
- Only if tty is present
- result var structure (myvar.user_input)
- pause: prompt=”Question?” register: myvar
Single deployment script
Tricks
19
#!/usr/bin/ansible-playbook- hosts: localhost vars_prompt: - name: app_name prompt: “Which app do you want to deploy?” default: mainapp - name: app_version prompt: “Choose version/tag (default HEAD)” default: ‘HEAD’ tasks: - git: repo=git@myreposerver/{{app_version}} version={{app_version}} ...
bin/deploy Normal Interactive commandWhen using cron:
- pass in extra vars- let it use defaults
3 plays that can run in parallel
Tricks: parallel playbook execution
20
- name: play1 hosts: all gather_facts: false tasks: - debug: msg=starting1 - wait_for: timeout=10 - debug: msg=ending1
- name: play2 hosts: all gather_facts: false tasks: - debug: msg=starting2 - wait_for: timeout=14 - debug: msg=ending2
- name: play3 hosts: all gather_facts: false tasks: - debug: msg=starting3 - wait_for: timeout=16 - debug: msg=ending3
play1.yml play3.ymlplay2.yml
Sometimes you don’t want to run stuff serially .. if only ansible-playbook ran in parallel!
From the command line
Tricks: parallel playbook execution
21
#> time ansible-playbook play?.ymlreal 0m40.691suser 0m1.371ssys 0m0.353s
#>time parallel ansible-playbook {} ::: play?.ymlreal 0m16.816suser 0m3.928ssys 0m0.848s
#> time $(ls play?.yml|xargs -n1 -P3 ansible-playbook)real 0m16.788suser 0m3.963ssys 0m0.842s
- Ansible is unix tool, many other tools do parallel
- Several options to run in parallel
- Easy to redirect output
- manage resources with nice and ionice
- use with inotify, tcpserver, cron, at
… even with procmail filters
ansible-meta?
Tricks: parallel playbook execution
22
- name: really trying hard to avoid shell scripts hosts: localhost gather_facts: False tasks: - shell: ansible-playbook play1.yml async: 10000 poll: 0 - shell: ansible-playbook play2.yml async: 10000 poll: 0 - shell: ansible-playbook play3.yml async: 10000 poll: 0
#>time ansible-playbook async.ymlreal 0m3.572suser 0m0.497ssys 0m0.105s
Less typing!
Tricks: parallel playbook execution
23
- name: really trying hard to avoid shell scripts v2 hosts: localhost gather_facts: False tasks: - shell: ansible-playbook play{{item}}.yml async: 10000 poll: 0 with_items: [1,2,3]
#>time ansible-playbook async2.ymlreal 0m3.531suser 0m0.463ssys 0m0.107s
ansible-meta … with results!
Tricks: parallel playbook execution
24
- name: really trying hard to avoid shell scripts v3 hosts: localhost gather_facts: False tasks: - shell: ansible-playbook play{{item}}.yml async: 10000 poll: 0 with_items: [1,2,3] register: runplays
- async_status: jid={{runplays.results[item.index].ansible_job_id}} resgister: jobs
until: jobs.finished with_indexed_items: [1,2,3] retries: 100
#>time ansible-playbook async3.ymlreal 0m24.140suser 0m1.239ssys 0m0.265s
Static vs Dynamic
TIPS: includes
25
- Beginning in 2.0 includes can be dynamic, loops, conditionals!
- Static includes are not real tasks, dynamic includes … almost are
- Only task includes can be dynamic, play includes are always static
- Dynamic includes do not show included tasks in --list-tasks/tags
- Handler includes are static by default (both types have config setting)
- Task includes have dynamic include detection, use static: yes|no (>=2.1) directive to control
- Task behaviour can change in each case
dynamic include loops
TIPS: includes
26
- hosts: localhost gather_facts: False tasks: - include: test.yml with_items: [1,2] static: yes
- set_fact: outer={{item}}- debug: msg="{{outer}} and {{item}}" with_items: ['a','b']
TASK [debug] ********************************************************ok: [localhost] => (item=a) => { "item": "a", "msg": "1 and a"}ok: [localhost] => (item=b) => { "item": "b", "msg": "1 and b"}
TASK [debug] ********************************************************ok: [localhost] => (item=a) => { "item": "a", "msg": "2 and a"}ok: [localhost] => (item=b) => { "item": "b", "msg": "2 and b"}
play.yml
test.yml
Since 2.1 you can use loop_control instead of set_fact
New in 2.2
Tricks: include_role
27
- hosts: localhost tasks: - package: name={{httpd}} state=latest
- include_role:name: webapptasks_from: install.yml
- service: name={{httpd}} state=started
- include_role:name: webapptasks_from: configure.yml
vars_from: “{{ansible_os}}.yml” with_items: “{{ applications }}”
Multiple notification
TIPS: fun with handlers
28
- hosts: all tasks: - name: configure nginx template: src=nginx.j2 dest=/etc/nginx.conf notify: - restart_uwcgi - restart_nginx
handlers: - name: restart_uwcgi service: name=uwcgi state=restarted
- name: restart_nginx service: name=nginx state=restarted
- Most flexible
- Can get repetitive
- List can be a variable
Chaining handlers
TIPS: fun with handlers
29
- hosts: all tasks: - name: configure nginx template: src=nginx.j2 dest=/etc/nginx.conf notify: restart_nginx_cluster
handlers:
- name: restart_nginx_cluster service: name=uwcgi state=restarted notify: restart_nginx
- name: restart_nginx service: name=nginx state=restarted
- Call observes definition order
- Cannot call previous
- Less flexible
- Can call middle of chain
Grouping handlers
TIPS: fun with handlers
30
- hosts: all tasks: - name: configure nginx template: src=nginx.j2 dest=/etc/nginx.conf notify: restart_nginx_cluster
handlers: - name: restart_nginx_cluster wait_for: seconds=1 changed_when: true notify: [‘restart_uwcgi’, ‘nginx’] - name: restart_uwcgi service: name=uwcgi state=restarted - name: restart_nginx service: name=nginx state=restarted
- Still flexible
- Less repetition
- Needs dummy task
- Also relies on chain
- List can be a variable
listen! new in 2.2
TIPS: fun with handlers
31
- hosts: all tasks: - name: configure nginx template: src=nginx.j2 dest=/etc/nginx.conf notify: restart_nginx_cluster
handlers: - name: restart_uwcgi service: name=uwcgi state=restarted listen: restart_nginx_cluster
- name: restart_nginx service: name=nginx state=restarted listen: restart_nginx_cluster
- Still flexible
- Less repetition
- listen is not unique
- Does not rely on chain
- List can be a variable
include as handler
TIPS: fun with handlers
32
- hosts: all tasks: - name: configure nginx template: src=nginx.j2 dest=/etc/nginx.conf notify: restart_nginx_cluster
handlers:
- name: restart_nginx_cluster include: nginx_cluster_restart.yml static: no
- Only dynamic includes
- Include itself is handler
- File can be variable
- Also toggleable from config