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