Advanced
Learning Objectives
Students will be able to understand
- Configuration Entries
- Different connection methods
- Variable types and precedences
- Dynamic Inventories
- Encrypted data for use within Ansible
- Roles
- Task Delegatation
- Jinja2 Templates
Assessment Questions
Configuration Files
File locations
- ansible.cfg (in the current directory)
- .ansible.cfg (in the home directory)
- /etc/ansible/ansible.cfg
Environment Variables
ANSIBLE_KEEP_ALL_FILES=True ansible -i hosts ...
Environment Variables
- Highest precedence
- Enviroment variables & configuration directives
- constants.py
Configuration Sections
- defaults
- paramiko
- ssh_connection
- accelerate
Configuration Sections
$ cat ansible.cfg
[defaults]
...
Configuration Options
Each section has its own directives
[defaults]
hostfile = hosts
private_key_file = ~/.ssh/id_rsa
...
Configuration Options Documentation
Example Configuration
[defaults]
hostfile = hosts
private_key_file = /Users/username/.ssh/id_rsa
nocows = 1
forks = 50
transport = ssh
remote_user = ansibleuser
ask_sudo_pass = True
ask_vault_pass = True
roles_path = roles
[ssh_connection]
pipelining = True
scp_if_ssh = True
Config File Tour
Connection Types
Smart (SSH auto-detected)
placeholder for diagram to show smart, openssh, and paramiko?
OpenSSH
Pipelining
Paramiko
Accelerated Mode
http://docs.ansible.com/playbooks_acceleration.html
Local Connections
Connection Plugins
Jinja2 Templates
I mustache you a question.
Jinja2 Templates
- Delimiters
- Control Structures
- Jinja2 Environment
- Python Data Types
- Filters and Tests
Delimiters
{{ variable }}
{% for server in groups.webservers %}
Control Structures
- for
- if
- macros
- call
- filters
- assignments
- extends
- block
- include
- import
For Loop
{% for server in groups.webservers %}
{{ hostvars[server].ansible_default_ipv4.address }}
{% endfor %}
If Statement
{% if server == inventory_hostname %}
{{ 127.0.0.1 }}
{% else if server in groups.database %}
{{ hostvars[server].ansible_eth1.address }}
{% else %}
{{ hostvars[server].ansible_default_ipv4.address }}
{% endif %}
Accessing Variables from Other Hosts
The "hostvars" variable contains facts for all hosts that have had facts gathered.
hostvars['web01'].ansible_eth1.address
Manipulating Jinja2 Environment
Ansible configures jinja2 with a set of sane defaults. In some cases these defaults are not optimal, usually in the case of variable_start_string or trim_blocks. The first line of a jinja2 template can include a jinja2 environment configuration line
#jinja2:variable_start_string:'[%' , variable_end_string:'%]'
Jinja2 Variables are Python Data Types
In some cases there will not be Jinja2 filters that do what you want, such as a lack of a "split" filter. This can be achieved using the ".split()" method on a python string object.
{% set servers = "server1,server2,server3" %}
{% for server in servers.split(",") %}
{{ server }}
{% endfor %}
Filters and Tests
Jinja2 provides you with a number of filters and tests to manipulate and test data. Ansible also provides a number of filters.
http://docs.ansible.com/playbooks_variables.html#jinja2-filters
Filters
Filters are invoked similarly to unix shell pipes and manipulate variables and return the results
variable | replace("-", "_")
Tests
Tests can be used to test a variable against a common expression
{% if variable is defined %}
Variable Types and Precedence
What is your variable and where does it go?
Where variables are defined or sourced
- Inventory
- Playbook
- Files and Roles
- Command Line
- Facts
Variable Precedence
Variables will override each other depending on where they are defined:
- Command line variables have the highest precedence.
- 'most everything else' come next.
- Variables defined in inventory.
- Next comes facts discovered about a system.
- Role defaults lose in priority to everything else.
Defining Variables
Let's go over the various locations you can define variables.
Inventory
There are a couple of methods for defining variables in your inventory:
localhost ansible_connection=local
[web]
web1.example.com ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50
web2.example.com ansible_ssh_user=mdehaan
[db]
db01.example.com mysql_max_connections=100
[web:vars]
apache_max_clients=100
Playbook
Here is an example of defining variables in a playbook:
- hosts: webservers
vars:
http_port: 80
Command Line
You can also override variables from the command line:
ansible-playbook release.yml --extra-vars "version=1.23.45 other_var=foo"
Including Variable Files
Here is an example of including a variable file based on a condition:
- name: Includiung OS specific variables
include_vars: '{{ ansible_os_family }}.yml'
Roles
Ansible Roles also have variables that can be defined:
- Rolename/vars contains variables that should stay internal to the role.
- Rolename/defaults contains variables that can be overridden.
Facts
System facts are sourced from the following sources:
- setup module
- set_fact module
- facts.d
Setup Module
Run the setup module against your local machine to see what returns.
ansible localhost -m setup --connection=local
Discovered Facts
Here is a sampling of facts discovered by the setup module:
localhost | success >> {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.1.37",
"172.17.42.1"
],
"ansible_all_ipv6_addresses": [
"fe80::c685:8ff:fe3b:a916"
],
"ansible_architecture": "x86_64",
"ansible_bios_date": "01/29/2013",
"ansible_bios_version": "UX32A.214",
"ansible_cmdline": {
"BOOT_IMAGE": "/vmlinuz-3.13.0-27-generic.efi.signed",
"quiet": true,
"ro": true,
"root": "/dev/mapper/kubuntu--vg-root",
"splash": true,
"vt.handoff": "7"
},
"ansible_date_time": {
"date": "2014-05-30",
"day": "30",
"epoch": "1401460386",
"hour": "09",
"iso8601": "2014-05-30T14:33:06Z",
"iso8601_micro": "2014-05-30T14:33:06.057018Z",
"minute": "33",
"month": "05",
"second": "06",
"time": "09:33:06",
"tz": "CDT",
"tz_offset": "-0500",
"weekday": "Friday",
"year": "2014"
},
"ansible_default_ipv4": {
"address": "192.168.1.37",
"alias": "wlan0",
"gateway": "192.168.1.1",
"interface": "wlan0",
"macaddress": "c4:85:08:3b:a9:16",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "192.168.1.0",
"type": "ether"
},
"ansible_default_ipv6": {},
"ansible_devices": {
"sda": {
"holders": [],
"host": "SATA controller: Intel Corporation 7 Series Chipset Family 6-port SATA Controller [AHCI mode] (rev 04)",
"model": "Hitachi HTS54323",
"partitions": {
"sda1": {
"sectors": "625137282",
"sectorsize": 512,
"size": "298.09 GB",
"start": "63"
}
},
"removable": "0",
"rotational": "1",
"scheduler_mode": "deadline",
"sectors": "625142448",
"sectorsize": "512",
"size": "298.09 GB",
"support_discard": "0",
"vendor": "ATA"
},
"sdb": {
"holders": [],
"host": "SATA controller: Intel Corporation 7 Series Chipset Family 6-port SATA Controller [AHCI mode] (rev 04)",
"model": "SanDisk SSD i100",
"partitions": {
"sdb1": {
"sectors": "997376",
"sectorsize": 512,
"size": "487.00 MB",
"start": "2048"
},
"sdb2": {
"sectors": "499712",
"sectorsize": 512,
"size": "244.00 MB",
"start": "999424"
},
"sdb3": {
"sectors": "45404160",
"sectorsize": 512,
"size": "21.65 GB",
"start": "1499136"
}
},
"removable": "0",
"rotational": "0",
"scheduler_mode": "deadline",
"sectors": "46905264",
"sectorsize": "512",
"size": "22.37 GB",
"support_discard": "512",
"vendor": "ATA"
},
"sdc": {
"holders": [],
"host": "USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #2 (rev 04)",
"model": "xD/SD/M.S.",
"partitions": {},
"removable": "1",
"rotational": "1",
"scheduler_mode": "deadline",
"sectors": "0",
"sectorsize": "512",
"size": "0.00 Bytes",
"support_discard": "0",
"vendor": "Generic-"
}
},
"ansible_distribution": "Ubuntu",
"ansible_distribution_major_version": "14",
"ansible_distribution_release": "trusty",
"ansible_distribution_version": "14.04",
"ansible_docker0": {
"active": false,
"device": "docker0",
"id": "8000.56847afe9799",
"interfaces": [],
"ipv4": {
"address": "172.17.42.1",
"netmask": "255.255.0.0",
"network": "172.17.0.0"
},
"macaddress": "56:84:7a:fe:97:99",
"mtu": 1500,
"promisc": false,
"stp": false,
"type": "bridge"
},
"ansible_domain": "onitato.com",
"ansible_env": {
"COLORFGBG": "15;0",
"DBUS_SESSION_BUS_ADDRESS": "unix:abstract=/tmp/dbus-0Rf33lsHZU",
"DEFAULTS_PATH": "/usr/share/gconf/kde-plasma.default.path",
"DESKTOP_SESSION": "kde-plasma",
"DISPLAY": ":0",
"GDMSESSION": "kde-plasma",
"GDM_LANG": "en_US",
"GNOME_KEYRING_CONTROL": "/run/user/1000/keyring-tJuVCy",
"GNOME_KEYRING_PID": "2128",
"GPG_AGENT_INFO": "/tmp/gpg-c2qDOe/S.gpg-agent:2257:1",
"GS_LIB": "/home/linuturk/.fonts",
"GTK2_RC_FILES": "/etc/gtk-2.0/gtkrc:/home/linuturk/.gtkrc-2.0:/home/linuturk/.kde/share/config/gtkrc-2.0",
"GTK_RC_FILES": "/etc/gtk/gtkrc:/home/linuturk/.gtkrc:/home/linuturk/.kde/share/config/gtkrc",
"HOME": "/home/linuturk",
"IM_CONFIG_PHASE": "1",
"INSTANCE": "",
"JOB": "dbus",
"KDE_FULL_SESSION": "true",
"KDE_MULTIHEAD": "false",
"KDE_SESSION_UID": "1000",
"KDE_SESSION_VERSION": "4",
"KONSOLE_DBUS_SERVICE": ":1.410",
"KONSOLE_DBUS_SESSION": "/Sessions/2",
"KONSOLE_DBUS_WINDOW": "/Windows/1",
"KONSOLE_PROFILE_NAME": "Shell",
"LANG": "en_US.UTF-8",
"LANGUAGE": "en_US:en",
"LC_CTYPE": "en_US.UTF-8",
"LESSCLOSE": "/usr/bin/lesspipe %s %s",
"LESSOPEN": "| /usr/bin/lesspipe %s",
"LOGNAME": "linuturk",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:",
"MANDATORY_PATH": "/usr/share/gconf/kde-plasma.mandatory.path",
"PAM_KWALLET_LOGIN": "/tmp//linuturk.socket",
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games",
"PROFILEHOME": "",
"PWD": "/home/linuturk/github/ansible-sprint/advanced",
"QT_PLUGIN_PATH": "/home/linuturk/.kde/lib/kde4/plugins/:/usr/lib/kde4/plugins/",
"SELINUX_INIT": "YES",
"SESSION": "kde-plasma",
"SESSIONTYPE": "",
"SESSION_MANAGER": "local/arrow:@/tmp/.ICE-unix/2442,unix/arrow:/tmp/.ICE-unix/2442",
"SHELL": "/bin/bash",
"SHELL_SESSION_ID": "559b7b8e474c4000aa16e447d32e4b27",
"SHLVL": "1",
"SSH_AGENT_LAUNCHER": "upstart",
"SSH_AGENT_PID": "2259",
"SSH_AUTH_SOCK": "/tmp/ssh-NgX9DMrVWBek/agent.2253",
"TERM": "xterm",
"TEXTDOMAIN": "im-config",
"TEXTDOMAINDIR": "/usr/share/locale/",
"UPSTART_EVENTS": "started xsession",
"UPSTART_INSTANCE": "",
"UPSTART_JOB": "startkde",
"UPSTART_SESSION": "unix:abstract=/com/ubuntu/upstart-session/1000/2133",
"USER": "linuturk",
"WINDOWID": "71303194",
"XAUTHORITY": "/tmp/kde-linuturk/xauth-1000-_0",
"XCURSOR_THEME": "oxy-white",
"XDG_CONFIG_DIRS": "/etc/xdg/xdg-kde-plasma:/usr/share/upstart/xdg:/etc/xdg",
"XDG_CURRENT_DESKTOP": "KDE",
"XDG_DATA_DIRS": "/usr/share:/usr/share/kde-plasma:/usr/local/share/:/usr/share/",
"XDG_GREETER_DATA_DIR": "/var/lib/lightdm-data/linuturk",
"XDG_RUNTIME_DIR": "/run/user/1000",
"XDG_SEAT": "seat0",
"XDG_SEAT_PATH": "/org/freedesktop/DisplayManager/Seat0",
"XDG_SESSION_ID": "c2",
"XDG_SESSION_PATH": "/org/freedesktop/DisplayManager/Session0",
"XDG_VTNR": "7",
"_": "/usr/local/bin/ansible"
},
"ansible_form_factor": "Notebook",
"ansible_fqdn": "arrow.onitato.com",
"ansible_hostname": "arrow",
"ansible_interfaces": [
"lo",
"docker0",
"wlan0"
],
"ansible_kernel": "3.13.0-27-generic",
"ansible_lo": {
"active": true,
"device": "lo",
"ipv4": {
"address": "127.0.0.1",
"netmask": "255.0.0.0",
"network": "127.0.0.0"
},
"ipv6": [
{
"address": "::1",
"prefix": "128",
"scope": "host"
}
],
"mtu": 65536,
"promisc": false,
"type": "loopback"
},
"ansible_lsb": {
"codename": "trusty",
"description": "Ubuntu 14.04 LTS",
"id": "Ubuntu",
"major_release": "14",
"release": "14.04"
},
"ansible_machine": "x86_64",
"ansible_memfree_mb": 677,
"ansible_memtotal_mb": 9884,
"ansible_mounts": [
{
"device": "/dev/mapper/kubuntu--vg-root",
"fstype": "ext4",
"mount": "/",
"options": "rw,errors=remount-ro",
"size_available": 9636679680,
"size_total": 22740668416
},
{
"device": "/dev/sdb2",
"fstype": "ext2",
"mount": "/boot",
"options": "rw",
"size_available": 132574208,
"size_total": 247772160
},
{
"device": "/dev/sdb1",
"fstype": "vfat",
"mount": "/boot/efi",
"options": "rw",
"size_available": 506130432,
"size_total": 509640704
},
{
"device": "/dev/mapper/kubuntu--home--vg-home",
"fstype": "ext4",
"mount": "/home",
"options": "rw",
"size_available": 186540474368,
"size_total": 304337379328
}
],
"ansible_nodename": "arrow",
"ansible_os_family": "Debian",
"ansible_pkg_mgr": "apt",
"ansible_processor": [
"Intel(R) Core(TM) i3-2367M CPU @ 1.40GHz",
"Intel(R) Core(TM) i3-2367M CPU @ 1.40GHz",
"Intel(R) Core(TM) i3-2367M CPU @ 1.40GHz",
"Intel(R) Core(TM) i3-2367M CPU @ 1.40GHz"
],
"ansible_processor_cores": 2,
"ansible_processor_count": 1,
"ansible_processor_threads_per_core": 2,
"ansible_processor_vcpus": 4,
"ansible_product_name": "UX32A",
"ansible_product_serial": "NA",
"ansible_product_uuid": "NA",
"ansible_product_version": "1.0",
"ansible_python_version": "2.7.6",
"ansible_selinux": false,
"ansible_swapfree_mb": 10211,
"ansible_swaptotal_mb": 10239,
"ansible_system": "Linux",
"ansible_system_vendor": "ASUSTeK COMPUTER INC.",
"ansible_user_id": "linuturk",
"ansible_userspace_architecture": "x86_64",
"ansible_userspace_bits": "64",
"ansible_virtualization_role": "host",
"ansible_virtualization_type": "kvm",
"ansible_wlan0": {
"active": true,
"device": "wlan0",
"ipv4": {
"address": "192.168.1.37",
"netmask": "255.255.255.0",
"network": "192.168.1.0"
},
"ipv6": [
{
"address": "fe80::c685:8ff:fe3b:a916",
"prefix": "64",
"scope": "link"
}
],
"macaddress": "c4:85:08:3b:a9:16",
"module": "iwlwifi",
"mtu": 1500,
"promisc": false,
"type": "ether"
},
"module_setup": true
},
"changed": false
}
Discovered Facts
Here is how you reference these variables:
{{ ansible_devices.sda.model }}
{{ ansible_hostname }}
Setting Facts in a Play
You can set facts manually in a play using the set_facts module:
# Example setting host facts using key=value pairs
- set_fact: one_fact="something" other_fact="{{ local_var * 2 }}"
# Example setting host facts using complex arguments
- set_fact:
one_fact: something
other_fact: "{{ local_var * 2 }}"
Local Facts (Facts.d)
You can place files ending in '.fact' in the /etc/ansible/facts.d directory. These can be JSON, INI, or executable files. Here is an example file:
[general]
asdf=1
bar=2
And here is how you reference the asdf variable.
{{ ansible_local.preferences.general.asdf }}
Using Variables in Jinja2
You've seen several examples of variables being referenced. The same method is used to reference these variables in Jinja2 templates.
{{ variable_name }}
Jinja2 Filters
There are many useful filters you can use in your Jinja2 templates. Here are a few useful ones:
# Combine two lists
{{ list1 | union(list2) }}
# Get a random number
{{ 59 | random }} * * * * root /script/from/cron
# md5sum of a filename
{{ filename | md5 }}
# Comparisons
{{ ansible_distribution_version | version_compare('12.04', '>=') }}
Defaulting Values
You can provide a default value for a variable using the following filter:
{{ some_variable | default("foobar") }}
Magic Variables
Ansible provides information about other hosts though a series of 'magic variables'.
- hostvars
- group_names
- groups
hostvars
Hostvars let you ask about the variables of another host, including facts that have been gathered about that host.
{{ hostvars['test.example.com']['ansible_distribution'] }}
group_names
The group_names variable contains a list of all the groups the current host is in.
{% if 'webserver' in group_names %}
# some part of a configuration file that only applies to webservers
{% endif %}
groups
groups is a list of all the groups (and hosts) in the inventory.
{% for host in groups['app_servers'] %}
# something that applies to all app servers.
{% endfor %}
Lab: Overriding Variables
Given this play, override the local_var variable using the command line flag.
- hosts: localhost
connection: local
vars:
- local_var: "override me"
tasks:
- name: print out the variable
debug: msg="This should not output 'override me' - {{ local_var }}"
Objective: Override the variable without modifying the playbook
Lab Solution
ansible-playbook variable_override.yml --extra-vars "local_var=foobar"
Lab Output
PLAY [localhost] **************************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [print out the variable] ************************************************
ok: [localhost] => {
"msg": "This should not output 'override me' - foobar"
}
PLAY RECAP ********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
Dynamic Inventories
What is Dynamic Inventory?
- Executable script
- Queries a service that holds data about servers
- Returns the data to Ansible (as JSON)
Why Dynamic Inventory?
- Time
- Accuracy
Manual Invocation
$ ./docker.py (--list | --host)
Results
Ansible with Dynamic Inventory
$ ansible -i ./docker.py --list-hosts running
suspicious_ptolemy
ecstatic_albattani
Combining Inventory Types
$ tree
.
├── common.yml
├── prod
│ ├── docker.py
│ └── servers
└── site.yml
1 directory, 4 files
$ ansible-playbook -i prod site.yml
Lab
- Configure pyrax to interact with the Rackspace Cloud
- Place the rax.py script in the proper inventory location
- Using this script to poll dynamic inventory with Ansible
- Create and ping two instances using Ansible
For Ubuntu 14.04 Controller Setup
- apt-get update && apt-get dist-upgrade
- apt-get install python-setuptools gcc python-dev
- easy_install pip
- pip install pip --upgrade
- pip install pyrax six --upgrade
- pip install ansible
- wget -P /etc/ansible/hosts https://raw.github.com/ansible/ansible/devel/plugins/inventory/rax.py
- chmod +x /etc/ansible/hosts/rax.py
- vi ~/.rackspace_cloud_credentials
Example credentials file (~/.rackspace_cloud_credentials):
[rackspace_cloud]
username=cloudaccountname
api_key=$#!TuCnP0rt@l
For CentOS 7 Controller Setup
- yum install ansible gcc python-pip
- pip install pyrax
- cd /etc/ansible
- mv hosts hosts.bak
- mkdir hosts
- cd /etc/ansible/hosts
- wget https://raw.github.com/ansible/ansible/devel/plugins/inventory/rax.py
- chmod +x rax.py
Example credentials file (~/.rackspace_cloud_credentials)
[rackspace_cloud]
username = somenameyouchosetocallyourdamnaccount
api_key = Go0bld13g00k$#!T1337
Ansible Vault
What are you trying to hide?
Ansible Vault
Ansible Vault is a tool to encrypt YAML variables files that may contain sensitive data.
- Password-based
- Separate command line tool
- Encrypts YAML files
Vault Operations
- create
- edit
- rekey
- encrypt
- decrypt
Vault Operations
ansible-vault create secrets.yml
Vault Operations
ansible-vault edit secrets.yml
Vault Operations
ansible-vault rekey secrets.yml
Vault Operations
ansible-vault encrypt secrets.yml
Vault Operations
ansible-vault decrypt secrets.yml
Running a Playbook
ansible-playbook --ask-vault-pass vault-test.yml
ansible-playbook --vault-password-file ../vault-pw vault-test.yml
Lab: Vault
- Create a new encrypted file
- Edit that file
- Re-key the file
- Use an encrypted file in a playbook
Objective: Create a vault-encrypted vars file for a subsequent lab.
Roles
Reusing and sharing Ansible content.
Filesystem Structure
Roles use specific file structures.
Example Role Structure in a Project Folder:
site.yml
roles/
role1/
files/
templates/
tasks/
handlers/
vars/
meta/
Calling a role in a playbook example:
---
- hosts: webservers
roles:
- common
- webservers
Behavior examples of role X:
- If roles/x/tasks/main.yml exists, tasks listed therein will be added to the play
- If roles/x/handlers/main.yml exists, handlers listed therein will be added to the play
- If roles/x/vars/main.yml exists, variables listed therein will be added to the play
- If roles/x/meta/main.yml exists, any role dependencies listed therein will be added to the list of roles
- Any copy tasks can reference files in roles/x/files/
- Any script tasks can reference scripts in roles/x/files/
- Any template tasks can reference files in roles/x/templates/
- Any include tasks can reference files in roles/x/tasks/
Parameterize Your Roles:
---
- hosts: webservers
roles:
- common
- { role: foo, dir: '/opt/a', port: 5000 }
- { role: foo, dir: '/opt/b', port: 5001 }
Call Roles Conditionally:
---
- hosts: webservers
roles:
- { role: foo, when: "ansible_os_family == 'RedHat'" }
Pre and Post Tasks, Use of roles in a top level playbook:
---
- hosts: webservers
pre_tasks:
- shell: echo 'Hello.'
roles:
- { role: some_role }
tasks:
- shell: echo 'I called a role!'
post_tasks:
- shell: echo 'Goodbye.'
Variables Defaults
It is possible to set default variables for your roles.
Role Dependencies
Role Dependencies allow for roles to automatically pull in other roles.
Example role dependency entry in the meta/main.yml file:
---
dependencies:
- { role: common, some_parameter: 3 }
- { role: apache, port: 80 }
- { role: postgres, dbname: blarg, other_parameter: 12 }
Duplicate Role Dependiencies
You can call a role dependency multiple times with various parameters.
---
allow_duplicates: yes
dependencies:
- { role: apache, port: 80 }
- { role: apache, port: 8080 }
Embedding Custom Modules
Role-specific custom modules can be packaged with the role.
Examples of embedding a custom module in a role:
roles/
my_role/
library/
module1
module2
Calling the role with a custom module in a playbook:
- hosts: webservers
roles:
- my_role
Ansible Galaxy
Sharing is caring. visit http://galaxy.ansible.com
One last trick: Galaxy has you covered.
ansible-galaxy init mynewrole
Delegation
I like things done my way but by somebody else.
Delegation
- Local actions and actions on other hosts
- Delegation to a host in inventory
- Delegation to a host not in inventory
- Task execution concurrency with delegation
Local actions and actions on other hosts
Task control keyword: delegate_to
- name: take out of load balancer pool
command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
delegate_to: localhost
- name: update packages
yum: name=acme-web-stack state=latest
- name: add back to load balancer pool
command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
delegate_to: localhost
Delegation to a host in inventory
Delegation most often targets another host in your inventory
Uses connection variable data from delegate target
- ansible_connection
- ansible_ssh_host
- ansible_ssh_port
- ansible_ssh_user
- etc...
Delegation to a host not in inventory
Delegation: not just limited to hosts in your inventory
Make use of add_host to adjust connection details
- name: add delegation host
add_host: name=hubert ansible_ssh_host=192.168.10.2
ansible_ssh_user=fred
Lab: ephemeral host task delegation
Create an ephemeral host and delegate a task to it
- Create a play for localhost
- Create a host using add_host with detailed connection variables
- Create a simple task that is delegated to the added host
- Run playbook with -vvvv to observe the connection details
Objective: Using an ephemeral host to delegate a single task to
Sample Play
---
- name: test play
hosts: localhost
tasks:
- name: add delegation host
add_host: name=hubert ansible_ssh_host=192.168.10.2
ansible_ssh_user=fred
- name: silly echo
command: echo {{ inventory_hostname }}
delegate_to: hubert
$ ansible-playbook test-play.yml -vvvv
Task execution concurrency with delegation
Delegated tasks will run for every host in the loop
Tasks will run with configured forks / serial
Be aware of race conditions and concurrency issues
Lab: multi-host task delegation
- Create a play to loop over 'webservers' group
- Create a task to use mysql_user module to add a user
- Use host specific mysql user / password data
- delegate_to should be used to execute on the first 'databases' server
Objective: Add mysql users to a database for a set of hosts
Sample Play
---
- name: MySQL Users
hosts: webservers
tasks:
- name: add a mysql user for the server
mysql_user: name={{ db_username }} host={{ inventory_hostname }}
password='{{ db_password }}' priv='ansible.*:ALL'
state=present
delegate_to: databases[0]
When ran with -vvvv one can clearly see the connection debugging output which will show the delegation in action.
Jinja2 Templates Labs
Lab
Create playbook and a jinja2 template to achieve the following results
- Template out /etc/sysconfig/iptables
- Only allow access to MySQL (tcp/3306) from servers in the webservers group
- A play to create this file from the template
- The play should have a handler to reload iptables when the file changes
- iptables should be enabled and configured to start on boot
Sample template
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
{% for server in groups['webservers'] %}
-A INPUT -p tcp -s {{ hostvars[server].ansible_eth1.ipv4.address }} -i eth1 -d {{ ansible_eth1.ipv4.address }} --dport 3306 -j ACCEPT
{% endfor %}
{% for server in groups['databases'] %}
{% if server != inventory_hostname %}
-A INPUT -p tcp -s {{ hostvars[server].ansible_eth1.ipv4.address }} -i eth1 -d {{ ansible_eth1.ipv4.address }} --dport 3306 -j ACCEPT
{% endif %}
{% endfor %}
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
Sample play
---
- hosts: webservers
gather_facts: true
- hosts: databases
handlers:
- name: Reload iptables
service: name=iptables state=reloaded
tasks:
- name: Template /etc/sysconfig/iptables
template: src=templates/iptables.j2 dest=/etc/sysconfig/iptables
notify: Reload iptables
- name: Ensure iptables is started and enabled
service: name=iptables state=started enabled=yes
Assessment Questions
fin
Ansible Advanced
By Rackspace University
Ansible Advanced
Slide deck and speaker notes for the 300 level course, Ansible Advanced.
- 1,592