Abstract

The authoritative guide on Ansible

Lessons

Chapter 1 - Getting Started with Ansible

Ansible and Infrastructure Management

Inventory files

example is the group of servers you’re managing and www.example.com is the domain name (or IP address) of a server in that group

[example]
www.example.com
# www.example.com:2222 # with port

To ping the server, run the command below.

ansible example -m ping -u

Note: Ansible assumes you’re using passwordless (key-based) login for SSH. If you insist on using passwords, add the --ask-pass, -k, to ansible commands.

Run a command on a remote server:

ansible example -a "free -m" -u [username]

Chapter 2 - Local Infrastructure Development: Ansible and Vagrant

A sample ansible playbook in playbook.yml:

--- 
- hosts:all
  become: yes
  tasks:
  - name: Ensure NTP (for time synchronization) is installed.
    yum: name=ntp state=present
  - name: Ensure NTP is running.
    service: name=ntpd state=started enabled=yes

Vagrant is invisibly using its own Ansible inventory file (instead of the one we created earlier in /etc/ansible/hosts), which just defines the Vagrant VM.

To maintain idempotency and handle error conditions, you’ll have to do a lot more extra work with basic shell scripts than you do with Ansible.

Using the name function and/or adding comments to the YAML is a good practice to document your tasks.

Chapter 3 - Ad-Hoc Commands

But even if you only used Ansible for server management and running individual tasks against groups of servers, and didn’t use Ansible’s playbook functionality at all, you’d still have a great orchestration and deployment tool in Ansible!

An inventory file for multiple servers:

# Application servers
[app]
192.168.60.4
192.168.60.5

# Database server
[db]
192.168.60.6

# Group 'multi' with all servers
[multi:children]
app
db

# Variables that will be applied to all servers
[multi:vars]
ansible_ssh_user=vagrant
ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key

Use ansible with the -a argument ‘hostname’ to run hostname against all the servers: ansible multi -a "hostname". Ansible will run the commands in parallel.

If Ansible reports No hosts matched or returns some other inventory-re- lated error, try setting the ANSIBLE_HOSTS environment variable explicitly: export ANSIBLE_HOSTS=/etc/ansible/hosts

If you get an error like The authenticity of host ‘192.168.60.5’ can’t be established, you can set the environment variable ANSIBLE_HOST_KEY_CHECKING=False.

Add the argument -f 1 to tell Ansible to use only one fork (basically, to perform the command on each server in sequence).

A few other example commands:

When you use Ansible’s modules instead of plain shell commands, you can use the powers of abstraction and idempotency offered by Ansible. Even if you’re running shell commands, you could wrap them in Ansible’s shell or command modules (like ansible multi -m shell -a "date"),

Users and groups

Try to reserve the --limit option for running commands on single servers. One of the most common uses for Ansible’s ad-hoc commands in my day-to-day usage is user and group management.

Running commands in the background

Use the following options to run a job in the background:

-B <seconds>: max job runtime. • -P <seconds>: the amount of time between polling

Logs

Cron jobs

It’s best to leave things be in the crontab itself, and always manage entries via ad-hoc commands or playbooks using Ansible’s cron module.

Deploy a version-controlled application

ControlPersist allows SSH connections to persist so frequent commands run over SSH don’t have to go through the initial handshake over and over again. Beginning in Ansible 1.3, Ansible defaulted to using native OpenSSH connections to connect to servers supporting ControlPersist.

Ansible’s Accelerated mode achieves greater performance for playbooks. Instead of connecting repeatedly via SSH, Ansi- ble connects via SSH initially, then uses the AES key used in the initial connection to communicate further commands and transfers via a separate port (5099). You can enable it for a playbook by adding the option accelerate: true to your playbook.

Chapter 4 - Ansible Playbooks

Playbooks (a list of instructions describing the steps to bring your server to a certain configuration state) that are then played on your servers. It is easy to convert shell scripts (or one-off shell commands) directly into Ansible plays.

A sample Ansible playbook:

---
- hosts: all

tasks:
    - name: Install Apache.
      command: yum install --quiet -y httpd httpd-devel
    - name: Copy configuration files.
      command: >
        cp httpd.conf /etc/httpd/conf/httpd.conf
    - command: >
      cp httpd-vhosts.conf /etc/httpd/conf/httpd-vhosts.conf
    - name: Start Apache and configure it to run at boot.
      command: service httpd start
    - command: chkconfig httpd on

Run it using ansible-playbook playbook.yml

--- 
- hosts:all
  become: yes
  tasks:
    - name: Install Apache.
      yum: name={{ item }} state=present
      with_items:
        - httpd
        - httpd-devel
    - name: Copy configuration files.
      copy:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
        owner: root
        group: root
        mode: 0644
      with_items:
        - src: "httpd.conf"
          dest: "/etc/httpd/conf/httpd.conf"
        - src: "httpd-vhosts.conf"
          dest: "/etc/httpd/conf/httpd-vhosts.conf"
    - name: Make sure Apache is started now and at boot.
      service: name=httpd state=started enabled=yes

The greater-than sign (>) immediately following the command: module directive tells YAML “automatically quote the next set of indented lines as one long string, with each line separated by a space”.

Running the playbook with the --check option (see the next section below) verifies the configuration matches what’s defined in the playbook, without actually running the tasks on the server.

Ansible playbook command options:

Playbook Parameters

Chapter 5 - Ansible Playbooks - Beyond the Basics

To notify multiple handlers from one task, use a list for the notify option:

- name: Rebuild application configuration.
  command: /opt/app/rebuild.sh
  notify:
    - restart apache
    - restart memcached

To have one handler notify another, add a notify option onto the handler—handlers are basically glorified tasks that can be called by the notify option

handlers:
  - name: restart apache
    service: name=apache2 state=restarted
    notify: restart memcached
  - name: restart memcached
    service: name=memcached state=restarted

It’s recommended you use a task’s register option to store the environment variable in a variable Ansible can use later, for example.

-name: Add an environment variable to the remote user's shell.
lineinfile: "dest=~/.bash_profile regexp=^ENV_VAR= line=ENV_VAR=value"

-name: Get the value of the environment variable we just added. 
 shell: 'source ~/.bash_profile && echo $ENV_VAR'
 register: foo

-name: Print the value of the environment variable. 
 debug: msg="The variable is {{ foo.stdout }}"

Linux will also read global environment variables added to /etc/environment:

- name: Add a global environment variable.
  lineinfile: "dest=/etc/environment regexp=^ENV_VAR= \
  line=ENV_VAR=value"
  become: yes

You can also define environment variables per play:

- name: Download a file, using example-proxy as a proxy.
  get_url: url=http://www.example.com/file.tar.gz dest=~/Downloads/
  environment:
    http_proxy: http://example-proxy:80/

You can pass an environment in via a variable in your playbook’s vars section:

vars:
  var_proxy:
    http_proxy: http://example-proxy:80/
    https_proxy: https://example-proxy:443/
    [etc...]
tasks:
- name: Download a file, using example-proxy as a proxy.
  get_url: url=http://www.example.com/file.tar.gz dest=~/Downloads/
  environment: var_proxy

To set environment variables system wide using /etc/environment:

#Inthe'vars'sectionoftheplaybook(setto'absent'todisablepro\ xy):
proxy_state: present

#Inthe'tasks'sectionoftheplaybook: 
- name:Configuretheproxy.
  lineinfile:
    dest: /etc/environment
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
    state: "{{ proxy_state }}"
  with_items:
    - regexp: "^http_proxy="
      line: "http_proxy=http://example-proxy:80/"
    - regexp: "^https_proxy="
      line: "https_proxy=https://example-proxy:443/"
    - regexp: "^ftp_proxy="
      line: "ftp_proxy=http://example-proxy:80/"

To test environment variables: ansible test -m shell -a 'echo $TEST'

Variables

Can be passed in via the commandline using: --extra-vars "foo=bar"

You can set a vars section in the playbook:

vars:
    foo: bar

tasks:
    # Prints "Variable 'foo' is set to bar".
    - debug: msg="Variable 'foo' is set to {{ foo }}"

Or you can use var files in playbooks:

vars_files:
    - vars.yml
tasks:
    - debug: msg="Variable 'foo' is set to {{ foo }}"

Variables may also be added via Ansible inventory files, either inline with a host definition, or after a group. It is not recommended to use inventory files. Use group_vars and host_vars YAML variable files within a specific path instead.

Setting Variables

Ansible allows you to use register to store the output of a particular command in a variable at runtime.

Accessing

Definition:

foo_list:
  - one
  - two
  - three

Accessing:

foo[0]
foo|first

Or:

{{ ansible_eth0.ipv4.address }}
{{ ansible_eth0['ipv4']['address'] }}

Ansible lets you define or override variables on a per-host or per-group basis. This is easiest to do in the inventory file.

Other variables that ansible provides

Facts

Facts are variables derived from host system information. You can turn them off in a playbook by setting: gather_facts: no. Doing so can be helpful for deploys with significant numbers of servers. You can also manually add facts by adding INI or JSON files to /etc/ansible/facts.d/. It’s often better to build your playbooks in a way that doesn’t rely (or care about) specific details of individual hosts.

Ansible Vault

Ansible vault encrypts sensitive data so that it can be committed and stored alongside the rest of your repository.

You include and use the keys in the encrypted yaml file normally and Ansible decrypts them on the fly.

vars_files:
    - vars/api_key.yml # encrypted yaml file

You can supply vault passwords via a password file located at: ∼/.ansible/vault_pass.txt and set strict perms (600). You will need to pass the location using --vault-password-file flag.

Variable Precedence and Sane Defaults

• Roles should provide sane default values via the role’s ‘defaults’ variables. They are the fallback variables. • Playbooks should never define variables but include them via vars_files or via inventory (in that preference order).
• Only host or group specific variables should be defined inventories. • Dynamic and static inventories should be kept to a minimum • Command line variables (-e) should be avoided unless testing locally and the like

Jinja, Python built-ins, and Logic

Delegation, Local Actions, and Pauses

Prompts

You can use prompts to collect sensitive and non-sensitive data. Options include: encrypt, confirm, salt_size, private, and default.

vars_prompt:
    - name: share_user
      prompt: "What is your network username?"
    - name: share_pass
      prompt: "What is your network password?"
      private: yes

Tags

Tags allow you to include or exclude particular roles, files, tasks, and plays. Invoke ansible-playbook using --tags or --skip-tags and give it a comma separated string of tags: "one,two".

Use tags for a larger playbook’s individual roles and plays. However, generally avoid tags for individual tasks or includes.

Blocks

Blocks allow you to group related tasks together, using the same task parameters (with_items, become, etc) and allow you to handle errors inside of those blocks. r

The rescue syntax is like so:

tasks: 
    - block: 
        #  do something here
      rescue:
        # rescue work here
      always: 
        # does what it says 

It may be easier to use failed_when to define acceptable failure conditions.

Chapter 6 - Playbook Organization - Roles, Includes, and Imports

Imports

You can use the import_tasks directive to import tasks via a yaml file.

Ansible will retrieve variables from the global scope or you can specifically define them when calling import:

tasks:
  - import_tasks: user.yml
    vars:
      username: johndoe
      ssh_private_keys:
        - { src: /path/to/johndoe/key1, dest: id_rsa }
        - { src: /path/to/johndoe/key2, dest: id_rsa_2 }

Most of the time it is best to start with a monolithic playbook and then seperate things out into smaller task, handler, and playbooks to accomplish the job.

Roles

Ansible automatically includes main.yml files inside of the role directories. The role directory structure is: role_name as the directory name for the role with sub-directories in that directory of tasks and meta.

--- 
- hosts:all
  roles:
    - role_name