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

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

http://docs.ansible.com/intro_configuration.html

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:


  1. Command line variables have the highest precedence.

  2. 'most everything else' come next.

  3. Variables defined in inventory.

  4. Next comes facts discovered about a system.

  5. 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

fin

Ansible Advanced

By Rackspace University

Ansible Advanced

Slide deck and speaker notes for the 300 level course, Ansible Advanced.

  • 1,592