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
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.