The Guide I Wanted When Learning About Ansible

Preface: Most people absolutely hate maintaining snowflake servers. There is nothing worse than having a server somewhere that needs to be restarted/reconfigured and only one person knows the password or the config is placed in a weird places on the server (i.e. someone put the config in /root and didn’t document it, smh)

Cue Red Hat’s tool for deployment: Ansible.

There are already a lot of tools out there that provide for orchestration and deployment of servers (Puppet, Chef, SaltStack). What makes Ansible so special? Answer: Ansible uses SSH to interact with deployment nodes. This is huge.

Instead of having to install special client software on machines, I can spin up random machines and tell my Ansible server about them and start executing commands without any further config.

The major advantage to using such a framework in general is that the server config and deployment are documented in source control. This means if the server goes down or someone forgets what software is on a specific machine, I have an easy-to-read reference on how that machine was deployed.

The configuration files are all written in YAML which is super clean. Additionally, Ansible operations are idempotent (changes are only made to match a desired state). This saves time when you are updating configurations across a wide set of servers and each server might be slightly different, Ansible won’t repeat actions that don’t need to be performed*.

* Certain operations like lineinfile will always be performed depending on the regex that is supplied for replacement.

Playbooks

Tasks

There are a plethora of modules in Ansible for working with the server and even cloud modules to manage cloud infrstructure (similar in nature to AWS CloudFormation or Hashicorp’s Terraform).

- name: Get latest Kubernetes etcd Docker image
  hosts: localhost

  tasks:
    - name: Pull image
      docker_image:
        name: k8s.gcr.io/etcd-amd64
        tag: 3.1.12

Variables

Defined within the playbook/role or its own file and can use jinja2 templating for complex lists or string concatenations.

- name: Check if Kubernetes is running
  hosts: kubernetes-masters

  vars:
    master_addr: 'localhost'

  tasks:
    - name: Get JSON from the Interwebs
      uri:
       url: "http://{{ master_addr }}:8080"
      ignore_errors: True
      register: json_response

    - set_fact:
        kubernetes_running: '{{ json_response.status == 200 }}'

Roles

Can define reusable “playbooks” that have their own isolated tasks, variables, templates. Can even separate out roles for different OS configurations (Red Hat based, Debian based, etc.):

---
roles:
  # Use Java from apt
  - role: java9-apt
    when: ansible_distribution == "Debian"
  # Use Java from yum
  - role: java9-yum
    when: ansible_distribution == "RedHat"
  # Compile Java for Alpine Linux (not available as an apk)
  - role: java9-alpine
    when: ansible_distribution == "Alpine"

- include: apt-docker-install.yml
  when: ansible_distribution == "Ubuntu" and docker_source_type == "package-manager"

- include: generic-install.yml
  when: ansible_distribution != "Debian" and ansible_distribution != "Ubuntu" and not is_coreos and docker_source_type == "package-manager

Blocks

A lifesaver that allows many tasks be group together and apply conditional behavior against specific groups of tasks or roles:

- name: Install Kubernetes masters
  hosts: kubernetes-masters

  tasks:
    - block:
      - name: Install k8s on masters
        roles:
          - etcd
          - flannel
          - master
      when: kubernetes_running == False

Modules

There are a huge number of modules available in Ansible from stat-ing a file, managing AWS Lambda functions, building Docker images, getting data from a URL and many more.

Pull Mode

There is also a way to flip this model from the deployment server pushing the configuration to the nodes pulling the configuration from a specific location: ansible-pull -d ~/playbooks -i localhost -U git://github.com/ansible/ansible-examples

From the Ansible documentation:

A mode called ‘ansible-pull’ can also invert the system and have systems ‘phone home’ via scheduled git checkouts to pull configuration directives from a central repository.

As an alternative to pull mode, what I’ve been doing to bootstrap machines is to seed the playbooks in S3 prior to running, add the ansible-playbook call in the user data of the cloud init: ansible-playbook -i 'localhost,' -c local playbook.yml

Libraries

There is support for adding your own modules. I had to modify the ec2_facts so that I could get the region from the EC2 dynamic meta data (PR merged into Ansible 2.4); to add a custom module, you can add it the path to the module(s) in the ansible.cfg or set it on the command line by setting ANSIBLE_LIBRARY.

Random useful tidbits

Gathering facts at the beginning can be annoying when the machines themselves aren’t actually up. When Ansible gathers facts it will establish a SSH connection to the machine, but will fail if it is still initializing. The way around this is to use a delayed loop to wait for the OpenSSH server to come up so that Ansible can continue. gather_facts should be false for this to work properly.

- hosts: all
  gather_facts: False

  pre_tasks:
    - name: Wait until SSH is available
      local_action:
        module: wait_for
        host: "{{ ansible_ssh_host }}"
        port: 22
        search_regex: OpenSSH
        delay: 10
        state: started
        timeout: 900