Ansible

I wanted to share an important piece of our continuous integration process here at Isotope11. We use Ansible to configure all our servers.

Named after the fictional communications system from the wonderfully great Enders Game, Ansible can help you deploy some code, configure a server and manage it, plus any one-off tasks you might need in your deployment process.

Ansible uses an agentless-architecture composed of a master and its nodes. The master is either the developers computer or a Jenkins server or a third-party service such as Ansible Towers. Nodes which are listed in an inventory file is essentially a list of IP address or domain names that ansible will configure. Below is a sample inventory file:

mail.example.com

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

[dbservers]
one.example.com
two.example.com
three.example.com

The identifiers within brackets are groupname. This organization allows us to clarify to ansible what we'd like to control at a granular level.

The agentless architecture of Ansible gives us one of its key design goals, minimal in nature. Using Ansible, one only needs to have python installed satisfying the bare requirements. When a master connects to a node, it will upload in one go all the necessary code to configure the node. This reduces net overhead.

Another tenant to Ansible is security, Ansible uses the battle tested OpenSSH library to guarantee secure connections to prevent vulnerable code be uploaded.

So with a good overview in, lets get to how ansible actually performs its magic. It all starts with the ability to run ad-hoc commands. Modules are the unit of work in ansible and there already exists an extensive of library of modules to work with. Modules can be file handlers, running shell commands, managing packages, pulling down git repos, managing users etc...

The typical format for an ad-hoc command would be like:

# create a user call adam with password adamrules
ansible all -m user -a "name=adam password=adamrules"

Breaking this down, we are telling ansible to * all - run this command on all hosts in the inventory file * -m user - use the user module * -a "our string" - passing this attribute to the user module

Normally ansible code is organized into playbooks. These are discreet units that favor being shared, a great site to find common playbooks is Ansible Galaxy. Its a great resource to learn how powerful they can be, need a quick playbook to get a Redis cluster up and running, configure your NGINX properly, or just write a playbook that installs a rsys logger logging how cool you are every hour to mess with your DevOps guy.

Playbooks are written in a YAML format so its very friendly to all starting with ansible. They can be contained to one .yml file or be an entire directory of files if its a complex playbook. All ansible does is parse these YAML files and run ad-hoc commands all while reporting back results.

A super simple playbook:

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: name=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running (and enable it at boot)
    service: name=httpd state=started enabled=yes
  handlers:
    - name: restart apache
      service: name=httpd state=restarted

As you might have guessed it, this playbook will ensure the latest Apache is installed, write a config file, and restart the service; all the while using the sudo user. The vars, tasks, handlers are the basic building blocks of a playbook, the stuff within the blocks are just key value pairs of arbitrary names and module commands. They are run in order, but ansible does allow running this playbook on as many hosts as you wish in parallel.

The last design goal of Ansible is that is idempotent. This means we run a playbook or task multiple times without any ill side-effects on our nodes. Our previous ad-hoc task can be run multiple times and ansible will ensure that the user is created just once and all subsequent calls will be ignored. Being idempotent all ensures that our commands either succeed or fail immediately. This means all our ansible code is essentially the describing the desired state that we desire to exist on our nodes.

Addendum - All Ansible releases are named after Van Halen songs.

Adam is a software developer with nigh on nine-years of experience. He has a B.S. from the University of Central Florida in Computer Science, with a minor in Statistics, rarely using either, He is mostly self-taught. With extensive expertise in the standard stack of web technologies of HTML, CSS, Javascript, Postgresql, Ruby, and Ruby on Rails, he absolutely loves playing with and learning new ones. His most recent forays have been into Golang and Swift. He is the resident Elixir/Erlang expert at Isotope11. When not developing he is a voracious reader and avid drinker - he even homebrews. He is happiest when doing both. He currently resides in Jacksonville, FL with the love of his life Allie, and their army of miniature pinschers and kittens.