Cluster management with Ansible

Ansible is a configuration management and provisioning tool, similar to Chef, Puppet or Salt.

I've found it to be one of the simplest and the easiest to get started with. A lot of this is because it uses SSH to connect to servers and run the configured Tasks. One nice thing about Ansible is that it's very easy to convert bash scripts, which is still a popular way to accomplish configuration management, into Ansible Tasks. Since it's primarily SSH based, it's not hard to see why this might be the case - Ansible ends up running the same commands. We could just script our own provisioners, but Ansible is much cleaner because it automates the process of getting context before running Tasks. With this context, ansible is able to handle most edge cases which is exactly the kind we usually take care of with longer and increasingly complex scripts. Ansible Tasks are idempotent. Without a lot of extra coding, bash scripts are usually not safe to run again and again. Ansible uses "Facts", which is system and environment information it gathers "context" before running Tasks. Ansible uses these facts to check state and see if it needs to change anything in order to get the desired outcome. This makes it safe to run Ansible Tasks against a server over and over again.

Here I'll show how easy it is to get started with Anible. We'll start basic and then add in more features as we improve upon our configurations.

You can find a subset of my personal collection of ansible playbooks and roles for managing a cluster on github.


Of course we need to start by installing Ansible. Tasks can be run off of any machine Ansible is installed on.

This means there's usually a "central" server running Ansible commands, although there's nothing particularly special about what server Ansible is installed on. Ansible is "agentless" - there's no central agent(s) running. We can even run Ansible from any server; I often run Tasks from my laptop.

Here's how to install Ansible on Ubuntu 14.04. We'll use the easy-to-remember ppa:ansible/ansible repository as per the official docs.

sudo apt-add-repository -y ppa:ansible/ansible
sudo apt-get update
sudo apt-get install -y ansible

Host Management

Ansible has a default inventory file used to define which servers it will be managing. After installation, there's an example one you can reference at /etc/ansible/hosts.

I usually copy and move the default one so I can reference it later: ** sudo cp /etc/ansible/hosts /etc/ansible/hosts.orig **

Now lets create our own hosts inventory.

# A collection of database servers in the 'dbservers' group



Running basic commands

Once we have an inventory configured, we can start running Tasks against the defined servers.

Ansible will assume you have SSH access available to your servers, usually based on SSH-Key. Because Ansible uses SSH, the server it's on needs to be able to SSH into the inventory servers. It will attempt to connect as the current user it is being run as. If I'm running Ansible as user admin, it will attempt to connect as user admin on the other servers.

If Ansible can directly SSH into the managed servers, we can run commands without too much hastle:

ansible all -m ping -k -u admin | success >> {
    "changed": false,
    "ping": "pong"

We can see the output we get from Ansible is some JSON which tells us if the Task made any changes and the result.

If we need to define the user and perhaps some other settings in order to connect to our server, we can. When testing locally, I use the following: ansible localserver -m ping -s -k -u admin

PS: Remember to add all managed host key to the known_hosts file (or just ssh to every single one of them before running ansible).

About the command options

all - Use all defined servers from the inventory file
-m ping - Use the "ping" module, which simply runs the ping command and returns the results
-s - Use "sudo" to run the commands
-k - Ask for a password rather than use key-based authentication
-u vagrant - Log into servers using user vagrant

Ansible Modules

Ansible uses "modules" to accomplish most of its Tasks. Modules can do things like install software, copy files, use templates and much more.

Modules are the way to use Ansible, as they can use available context ("Facts") in order to determine what actions, if any need to be done to accomplish a Task.

If we didn't have modules, we'd be left running arbitrary shell commands like this: ansible all -s -m shell -a 'apt-get install nginx'

Here, the sudo apt-get install nginx command will be run using the "shell" module. The -a flag is used to pass any arguments to the module. I use -s to run this command using sudo.

However this isn't particularly powerful. While it's handy to be able to run these commands on all of our servers at once, we still only accomplish what any bash script might do.

If we used a more appropriate module instead, we can run commands with an assurance of the result. Ansible modules ensure indempotence - we can run the same Tasks over and over without affecting the final result.

For installing software on Debian/Ubuntu servers, the "apt" module will run the same command, but ensure idempotence.

ansible all -s -m apt -a 'pkg=nginx state=installed update_cache=true' | success >> { "changed": false }

Ansible Playbook

Playbooks can run multiple Tasks and provide some more advanced functionality that we would miss out on using ad-hoc commands. Let's move the above Task into a playbook.

Playbooks and Roles in Ansible all use Yaml.


- hosts: localserver
   - name: Install Nginx
     apt: pkg=nginx state=installed update_cache=true

This Task does exactly the same as our ad-hoc command, however I chose to specify my "local" group of servers rather than "all". We can run it with the ansible-playbook command: ansible-playbook -s -k nginx.yml

Ansible Store encrypted Pass

ansible-vault create secret

In the file insert: ansible_sudo_pass: mysudopassword

Create a vault.txt file with the password decrypting the pass (make sure to use the right permissions)

You could also use something like that: [whatever] some-host ansible_sudo_pass='foobar' in the hosts file which is not secure!

Then our module will look something like that:

- hosts: localserver
    - secret
#  sudo: true
#  sudo_user: admin
#  remote_user: admin
   - name: update apt cache
     apt: update_cache=yes
#   - name: Install Nginx
#     apt: pkg=nginx state=removed update_cache=true

To run the module use:

ansible-playbook -s --vault-password-file=vault.txt -vvv ngix-test.yml

Ansible Handlers

A Handler is exactly the same as a Task (it can do anything a Task can), but it will run when called by another Task. You can think of it as part of an Event system; A Handler will take an action when called by an event it listens for.

This is useful for "secondary" actions that might be required after running a Task, such as starting a new service after installation or reloading a service after a configuration change.

- hosts: localserver
   - name: Install Nginx
     apt: pkg=nginx state=installed update_cache=true
      - Start Nginx

   - name: Start Nginx
     service: name=nginx state=started

We can add a notify directive to the installation Task. This notifies any Handler named "Start Nginx" after the Task is run.

Then we can create the Handler called "Start Nginx". This Handler is the Task called when "Start Nginx" is notified.

This particular Handler uses the Service module, which can start, stop, restart, reload (and so on) system services. Here we simply tell Ansible that we want Nginx to be started.

Note that Ansible has us define the state you wish the service to be in, rather than defining the change you want. Ansible will decide if a change is needed, we just tell it the desired result.

Ansible Web interface - Semaphore

Tower is ansible's commertial interface with really nice functionality but for those looking for an open-source solution sempaphore is an ellegent solution.

For installation you could use docker: ### Run redis

docker run -d \
        --name=redisio \
### Run mongodb
docker run -d \
        --name=mongodb \
        -p \
### Run sempaphore
docker run -d \
        --name=semaphore \
        --restart=always \
        --link redisio:redis \
        --link mongodb:mongo \
        -e MONGODB_URL="mongodb://mongo/semaphore" \
        -e REDIS_HOST="redis" \
        -p 80:80 \
### Web interface credentials
Email:          'admin@semaphore.local'
        Password:       'CastawayLabs'

Customising Semaphore Credentials

    show dbs
    use semaphore
    db.users.update({"_id" : ObjectId("555f52cc5e3ce20100b914ff") } , { $set: { 'email': '' }, } )

Ansible Ad-hoc Commands

If you want to install a package or run a command just once there is a more convenient way than creating a new playbook. You can use ansible ad-hock commands (also described above) like:

  • ansilbe local -m apt -a "name-libssl-dev state=present" -u admin -k --sudo
    • # Ensure a package is installed but dont update it
  • anbible databases -s -m apt -a "name=libffi-dev state=present" -k -u admin
    • # Deploy a package to the database cluster
  • ansible database -m shell -a 'sudo apt-get update; sudo pip install -U pip virualenv; echo 'All Done!'' -k -u admin --sudo
    • # Run multiple shell commands

If you have an error mentioning you dont have enough permissions to run a command at a host:

Make sure in /etc/ansible/ansible.cfg there is a commented out setting called ask_sudo_pass, which needs to be uncommented and set to True.

Create a user

If you want to create a user with ad-hoc coommands you need to generate an encrypted password for your user: mkpasswd --method=SHA-512

Then use the command:

  • ansible localserver -m user -a 'name=exampleuser shell=/bin/bash comment="Example User" password=XXXX' -k -u admin --sudo


  3. Github Project Link

Got more questions or comments? Drop me a private message.