Ansible Cheatsheet

Ansible

In this Ansible cheatsheet I have a page with all the playbooks, plus below I detail these files and the various options. Its best you use the Ansible documentation to see if any modules have been updated or any new modules have been created, below is just some of the highlights of what Ansible can do. You can also use the Ansible Galaxy website to see what others have created in regards to roles.

Ansible is an open-source software provisioning, configuration management, and application-deployment tool, it runs on many Unix-like systems, and can be configured for both Unix (Linux, Solaris, HPUX, etc) and Microsoft Windows. The diagram below shows the Ansible architecture


Ansible Server a system where Ansible is installed and configured to connect and execute commands on nodes
Node (Hosts) a server that is controlled by Ansible
Inventory File a file that contains information about the servers and groups Ansible controls, typically located at /etc/ansible/hosts
Play a full Ansible run. A play can have several playbooks and roles, included from a single playbook that acts as entry point
Playbook a file containing a series of tasks to be executed on a remote server. Playbooks exist to run tasks.
Tasks combine an action (a module and its arguments) with a name and optionally some other keywords (like looping directives)
Action An action is a part of a task that specifies which of the modules to run and which arguments to pass to that module
Role a collection of playbooks and other files that are relevant to a goal such as installing a web server
Facts are simply things that are discovered about remote nodes. While they can be used in playbooks and templates just like variables, facts are things that are inferred, rather than set. Facts are automatically discovered by Ansible when running plays by executing the internal setup module on the remote nodes
Modules are the units of work that Ansible ships out to remote machines
Plugins are a piece of code that expends the core functionality of Ansible. There are many useful plugins, and you also can write your own.
CMDB is a type of repository which acts as a data warehouse for the IT installations
Cloud is a network of remote servers on which you can store, manage, and process the data

Ansible is agentless, temporarily connecting remotely via SSH or Windows Remote Management (allowing remote PowerShell execution) to do its tasks.


I will leave you to the web and other youtube videos on how to install the latest version of ansible.

SSH setup

Below is the instructions on how to setup a private/public key pair that can be used to allow the Ansible server to directly SSH into the the client. You should setup the root user to allow SSH to any client without a password. Start by setting up the clients first

Client (Node)
ssh-keygen -t rsa             # you can call it something and specify the directory, you can also use a passphase
Ansible Server
# copy the above clients public key to the Ansible server (you can use scp)
cd ~/.ssh
echo <client public key> >> authorized_keys             # >> means append
ssh <client>                                            # now you should be able to login to the client without a password

Notes
------------------------------------------------------------------------------------------------------
.ssh directory should have permissions of 700 and authorized_keys file should have permissions of 600
Its also a good ides to update the /etc/hosts file with the clients IP address

Ansible Basics

The Inventory will list the ansible hosts plus can have some additional information (connection details). A file (ini or yaml) is used for the inventory, the default location /etc/ansible/hosts

Inventory file (INI)
control.example.com ansible_connection=local             # don't use SSH as control is same host

[webservers]
foo.example.com
bar.example.com

[dbservers]
one.example.com
two.example.com
three.example.com
Inventory file (YAML)
all:
  hosts:
    control.example.com:
  children:
    webservers:
      hosts:
        foo.example.com:
        bar.example.com:
    dbservers:
      hosts:
        one.example.com:
        two.example.com:
        three.example.com:
Default groups

Tasks are nothing more than a call to an ansible module. Tasks are made up of two parts the module and any arguments to that module. When you run a task, the Ansible server SSH into the client and then using the Python module runs the command you have requested (examples are ping, command, etc), all tasks have a return status. There are many modules, below is a list grouped by category, see Ansible docs for latest list


Playbooks

Plays are simply a set of hosts and tasks that need to be executed against those hosts, A Playbook is made up of plays. When running a playbook the display is in four parts


Playbook example (basic (YAML))
---
  - hosts: all
    tasks:
      - name: get server hostname
        command: hostname

There are many playbooks you can create to perform some of the below, generally you can go to the Ansible module page and then select the module that you want, it will give you details on how to use the module, options that are available, etc. You can then use that module inside your playbooks.

I will demostrate some of the modules below, first starting with installing packages.

Packages (simple)
loadbalancer.yml
---------------------------------------------------------
---
- hosts: loadbalancer
  become: true                                                 # use sudo command (you may or may not need)
  tasks:
    - name: install nginx
      apt: name=nginx state=present update_cache=yes
      ## yum: name=nginx state=present                         # there is more to this but using yum instead 


database.yml
---------------------------------------------------------
---
- hosts: database
  become: true                                                 # use sudo command (you may or may not need)
  tasks:
    - name: install mysql-server
      apt: name=mysql-server state=present update_cache=yes
      ## yum: name=mysql-server state=present                  # there is more to this but using yum instead 
Packages (multiple)
---
- hosts: webserver
  become: true
  tasks:
    - name: install web components
      apt: name={{item}} state=present update_cache=yes          # {{item}} use the with_items list (using jinja syntax)
      with_items:
        - apache2
        - libapache2-mod-wsgi
        - python-pip
        - python-virtualenv

Next we look at services

Services (start/enabled)
loadbalancer.yml
---------------------------
---
- hosts: loadbalancer
  become: true
  tasks:
    - name: install nginx
      apt: name=nginx state=present update_cache=yes

    - name: ensure nginx started
      service: name=nginx state=started enabled=yes                     # service module, enable means startup mode


database.yml
--------------------------
---
- hosts: database
  become: true
  tasks:
    - name: install mysql-server
      apt: name=mysql-server state=present update_cache=yes

    - name: ensure mysql started
      service: name=mysql state=started enabled=yes                      # service module, enable means startup mode
Service (stop/restart/start)
---
# Bring stack down
- hosts: loadbalancer
  become: true
  tasks:
    - service: name=nginx state=stopped

- hosts: webserver
  become: true
  tasks:
    - service: name=apache2 state=stopped

# Restart mysql
- hosts: database
  become: true
  tasks:
    - service: name=mysql state=restarted

# Bring stack up
- hosts: webserver
  become: true
  tasks:
    - service: name=apache2 state=started

- hosts: loadbalancer
  become: true
  tasks:
    - service: name=nginx state=started
Service (handlers)
---
- hosts: webserver
  become: true
  tasks:
    - name: install web components
      apt: name={{item}} state=present update_cache=yes
      with_items:
        - apache2
        - libapache2-mod-wsgi
        - python-pip
        - python-virtualenv

    - name: ensure apache2 started
      service: name=apache2 state=started enabled=yes

    - name: ensure mod_wsgi enabled
      apache2_module: state=present name=wsgi
      notify: restart apache2                                    # we notify the handler below (use handler name)

  handlers:                                                      # handler won't do anything unless you notify it
    - name: restart apache2
      service: name=apache2 state=restarted

Now lets take a look at files

Files (copy)
# place this into the webserver playbook
- name: copy demo app source
    copy: src=demo/app/ dest=/var/www/demo mode=0755            # destination directory will be created
    notify: restart apache2                                     # we notify the handler to restart apache

- name: copy apache virtual host config
    copy: src=demo/demo.conf dest=/etc/apache2/sites-available mode=0755
    notify: restart apache2                                     # we notify the handler to restart apache
Files (using pip)
# place this into the webserver playbook
- name: setup python virtualenv
    pip: requirements=/var/www/demo/requirements.txt virtualenv=/var/www/demo/.venv
    notify: restart apache2
Files (symlinks)
# place this into the webserver playbook    
- name: de-activate default apache site
    file: path=/etc/apache2/sites-enabled/000-default.conf state=absent     # remove symlink
    notify: restart apache2

- name: activate demo apache site
    file: src=/etc/apache2/sites-available/demo.conf dest=/etc/apache2/sites-enabled/demo.conf state=link     # create symlink
    notify: restart apache2
Files (template)
# place this into the loadbalancer playbook
- name: configure nginx site
    template: src=templates/nginx.conf.j2 dest=/etc/nginx/sites-available/demo mode=0644
    notify: restart nginx


nginx.conf.j2 (template file (jinja syntax))
--------------------------------------------------------------
upstream demo {
{% for server in groups.webserver %}                     # get all the hosts in the webserver group
    server {{ server }};
{% endfor %}
}

server {
    listen 80;

    location / {
        proxy_pass http://demo;
    }
}
Files (inline)
# place this into the database playbook 
- name: ensure mysql listening on all ports
    lineinfile: dest=/etc/mysql/my.cnf regexp=^bind-address line="bind-address = 0.0.0.0"         # subsitute a line in the my.cnf file
    notify: restart mysql

There are many modules for databases (mysql, postgres, mongodb, etc) which you can use to setup databases, user, etc. I demostrate the MySQL one below

MySQL Module
database.yml file
----------------------------------------------------
---
- hosts: database
  become: true
  tasks:
    - name: install tools
      apt: name={{item}} state=present update_cache=yes
      with_items:
        - python-mysqldb                                                            # we need the python-mysqldb package

    - name: install mysql-server
      apt: name=mysql-server state=present update_cache=yes

    - name: ensure mysql started
      service: name=mysql state=started enabled=yes

    - name: ensure mysql listening on all ports
      lineinfile: dest=/etc/mysql/my.cnf regexp=^bind-address line="bind-address = 0.0.0.0"
      notify: restart mysql

    - name: create demo database
      mysql_db: name=demo state=present                                             # create a database called demo

    - name: create demo user
      mysql_user: name=demo password=demo priv=demo.*:ALL host='%' state=present    # create a user called demo and grant permissions

  handlers:
    - name: restart mysql
      service: name=mysql state=restarted

The shell module is useful to retrieve information from the system for example get a directopry listing

Shell Module
- name: get active sites
    shell: ls -1 /etc/nginx/sites-enabled
    register: active                                             # save return output into variable
  
- name: de-activate sites
    file: path=/etc/nginx/sites-enabled/{{ item }} state=absent  # use below with_items array
    with_items: active.stdout_lines                              # use above variable that was saved
    when: item not in sites
    notify: restart nginx

A good idea is to create a playbook to check the status of the environment, this also highlights some of the other features that are available with Ansible. You can of course could use a monitoring tool as well.

Status playbook
---
- hosts: loadbalancer
  become: true
  tasks:
    - name: verify nginx service
      command: service nginx status                                  # standard command execution to check nginx

    - name: verify nginx is listening on 80
      wait_for: host={{ ansible_eth0.ip4.address }} port=80 timeout=1     # test connection, wait for 1 second to get response (I will cover facts later)

- hosts: control                                                     # run from the control host (Ansible server)
  tasks:
    - name: verify end-to-end index response
      uri: url=http://{{item}} return_content=yes                    # make sure we get a 200 status and also return the contents of the web page 
      with_items: groups.loadbalancer                                # run the above against the loadbalance group
      register: lb_index                                             # save the output so that we can use later (array)

    - fail: msg="index failed to return content"                     # this will check the above output, using the fail module
      when: "'Hello, from sunny' not in item.content"                # check that this is in the web page
      with_items: "{{lb_index.results}}"                             # we use the saved register from above task (lb_index)

    - name: verify end-to-end db response
      uri: url=http://{{item}}/db return_content=yes
      with_items: groups.loadbalancer
      register: lb_db

    - fail: msg="db failed to return content"
      when: "'Database Connected from' not in item.content"
      with_items: "{{lb_db.results}}"

Roles

Roles are ways of automatically loading certain vars_files, tasks, and handlers based on a known file structure, allows for better scaling. Grouping content by roles also allows easy sharing of roles with other users. You can use a tool called Ansible Galaxy to scaffold the directory structure

Galaxy (scaffolding)
mkdir /ansible/roles
cd /ansible/roles
ansible-galaxy init <directory name>                        # use something meaningful for the directory name

ansible-galaxy init control                                 # used for the Ansible server (for example)
ansible-galaxy init nginx                                   # used for nginx configuration (for example)
ansible-galaxy init mysql                                   # used for mysqld configuration (for example)

The directory structure will look something like below, you can see that I have created a directory structure for each part of the project (control, nginx, mysql, app, etc), you can create what ever structure you like based on what you will be using Ansible for.


So now we can use the directory structure and roles for the playbooks, for example lets take the control, below i show the tasks but you will also need to change the handlers, templates, etc.

/ansible/control/tasks/main.yaml
# some of the boiler code can be removed
---
- name: install tools
  apt: name={{item}} state=present update_cache=yes
  with_items:
    - curl
    - python-httplib2
/ansible/control.yml
---
- hosts: control
  become: true
  roles:                                 # point to the control role
    - control

When you run the playbook you will now see the role name and the task


You can create a playbook that runs other playbooks, generally this is called site.yml

site.yml
---
- include: control.yml                                 # include a playbook to run
- include: database.yml
- include: webserver.yml
- include: loadbalancer.yml

Variables can be setup in Ansible that can be used with playbooks, Ansible provides dynamic variables called facts that can be used inside playbooks.

Facts (IP address)
# we can use the ansible_eth0.ipv4.address fact to get the IP address
- name: ensure mysql listening on all ports
    lineinfile: dest=/etc/mysql/my.cnf regexp=^bind-address line="bind-address = {{ ansible_eth0.ipv4.address }}"
    notify: restart mysql

You can use the specific defaults/main.yml (for each project playbook) file to create custom variables that can be used in other files

defaults.yml
---
db_name: myapp
db_user_name: dbuser
db_user_pass: dbpass
db_user_host: localhost

to use the variables
-----------------------------------------------
- name: create database
  mysql_db: name={{ db_name }} state=present

- name: create user
  mysql_user: name={{ db_user_name }} password={{ db_user_pass }} priv={{ db_name }}.*:ALL
              host='{{ db_user_host }}' state=present

you can also use the vars/main.yml

vars/main.yml
---
some_var1: var1
some_var2: var2

You can loop through the elements of a hash using with_dict

with_dict example
---
sites:                             # top level
  myapp:                           # key
    frontend: 80                   # value
    backend: 80                    # value

using with_dict in playbooks
--------------------------------
- name: configure nginx sites
  template: src=nginx.conf.j2 dest=/etc/nginx/sites-available/{{ item.key }} mode=0644
  with_dict: sites
  notify: restart nginx
  
- name: activate nginx sites
  file: src=/etc/nginx/sites-available/{{ item.key }} dest=/etc/nginx/sites-enabled/{{ item.key }} state=link
  with_dict: sites
  notify: restart nginx
  
using with_dict in templates
-------------------------------
upstream {{ item.key }} {
{% for server in groups.webserver %}
    server {{ server }}:{{ item.value.backend }};
{% endfor %}
}

server {
    listen {{ item.value.frontend }};

    location / {
        proxy_pass http://{{ item.key }};
    }
}

You can create a directory at the top level called /ansible/group_vars and then create a file called all, global variables can then be added to this file that can be used across all roles. You can create a file for each group if you wish. You can also create a file called /ansible/all/vars which would do the same thing.

group varibles
# nothing new here file: group_vars/all
---
db_name: demo
db_user: demo
db_pass: demo

You may need to encrypt some variables and we can use Ansible Vault to do this, its better to create the vault file where the global variable file resides. You need to create a vault file see the commands section below

Vault
# nothing new here
---
vault_db_pass: demo

use the vault file
----------------------------------
---
db_name: demo
db_user: demo
db_pass: "{{ vault_db_pass }}"

Ansible has variable precedence as can be seen below which is taken from the Ansible documentation, the top of the list has the lowest priority and the bottom of the list has the highest priority. You can include variables inside the playbooks (site level, playbook top level, etc) but try to keep things simple.


Bits and Pieces

This section covers the remaining bits and pieces of Ansible, performance improvements, optimizations and tidy ups.

.
turn off fact gathering
gather_facts: false

Note: might be a good idea to add when creating files and remove when you need to gather facts.
cache vaild time
tasks:
  -name: update apt cache
   apt: update_cache=yes cache_vaild_time=86400                 # 24 hour cache valid time
limit option
ansible-playbook site.yml --limit <hostname>         # will limit to just that host
tags
- name: install tools
  apt: name={{item}} state=present update_cache=yes
  with_items:
    - curl
    - python-httplib2
  tags: [ 'package' ]                                 # this task is now tagged with the package tag

ansible-playbook site.yml --list-tags                 # list all the tags that are available in the site.yml (and included)
ansible-playbook site.yml --tags "package"            # run the playbook with the tag/s specified
ansible-playbook site.yml --skip-tags "package"       # run the playbook but skip the specified tags
changed_when
tasks:
  - name: verify nginx service
    command: service nginx status
    changed_when:  false                              # we know the outcome so we set to false


change_when: "active.stdout_lines != site.keys()"     # you can do complex code but result must be a boolean
ignore_errors
tasks:
  - name: verify nginx service
    command: service nginx status
    changed_when:  false                              # we know the outcome so we set to false
    ignore_errors: true                               # ignore any errors at this stage and move on to the next step
debug option
- debug: var=active.stdout_lines                 # var will be printed to the console when playbook is run
- debug: var=vars                     # print out all variables

Ansible Commands

Some but not all of the commonly used Ansible commands:

Directories and files
/etc/ansible/hosts                       # default inventory file
/etc/ansible/ansible.cfg                 # ansible configuration file
<own directory>/ansible.cfg              # create own directory for all ansible file

Configuration file options (ansible.cfg)
-------------------------------------------
inventory = <some directory>             # change the default directory of inventory file
Inventory (hosts, groups)
ansible --list-hosts all                                 # list all hosts
ansible -i <inventory file> --list-hosts all             # list all hosts using specific inventory file

ansible --list-hosts "*"                                 # list all hosts using wildcard
ansible --list-hosts <group name>                        # list all hosts in group
ansible --list-hosts <hostname>                          # list all hosts with specific hostname
ansible --list-hosts <string*>                           # list all hosts using search string and wildcard
ansible --list-hosts <control[:|,]database>              # list all hosts using multiple groups , : = old way and , = new way
ansible --list-hosts <webserver[0]>                      # list all hosts using group and indexing
ansible --list-hosts <\!webserver>                       # list hosts except anything in webserver group


Options to inventory file
-------------------------------------------------
ansible_connection=local                 # use local conneection as control is as as running host
vault_password_file = <location>      # set the vault password file location (text file that is locked down)


Note: you can create a ansible.cfg in you own created directory and if you run commands it will use this cfg file.
Tasks (modules)
# useful tasks to check host connectivity
ansible -m ping all                                  # use ping module
ansible -m command -a "hostname" all                 # run the hostname command on all hosts
Playbooks (plays)
ansible-playbook <playbook file>                         # run a playbook
ansible-playbook <site.yml file>                         # run a site file that includes other playbooks

ansible-playbook site.yml --limit <hostname>             # will limit to just that host
ansible-playbook site.yml --limit @<failed file name>    # run playbook only on the failed hosts in the specified fail host file

ansible-playbook --list-tasks                            # lists the tasks in the playbook
ansible-playbook --start-at-task <task name>              # run the playbook starting at task specified

ansible-playbook site.yml --list-tags                    # list all the tags that are available in the site.yml (and included)
ansible-playbook site.yml --tags "package"               # run the playbook with the tag/s specified
ansible-playbook site.yml --skip-tags "package"          # run the playbook but skip the specified tags

ansible-playbook site.yml --limit ? --tags ? --start-at-task ?     # you can mix and match limit, start-at-task and tags

ansible-playbook site.yml --step                         # ansible will prompt/ask each step in the playbook

ansible-playbook --syntax-check <yaml file>              # check the syntax of the yaml file
ansible-playbook --check <yaml file>                     # perform a dry run but don't actually do anything (report only)

ansible-playbook --ask-vault-pass <playbook>             # allows you to enter vault password
ansible-playbook --vault-password-file <location>        # specify the file that has the vault password, text file that is locked down
Facts
ansible -m setup <hostname>                          # list facts for specific hostname in inventory
Vault
ansible-vault create <file name>                     # generally called vault, enter password to encrypt file
ansible-vault edit <file name>                       # view and edit vault file
Performance
time ansible-playbook <playbook>                     # record how long a playbook takes to run (benchmark)