Ansible 101

Configuration management could not be simpler!

Allan Denot

--

I work with Ansible for the last 2 years and one thing I repeatedly hear when in comparison with other tools like Puppet and Chef is:

- Ansible is so simple but still powerful!

The learning curve is very gradual and you can get value for it after a few minutes playing with it. I hope to prove it in this series of tutorials. So let’s dig in.

Ansible allows your whole infrastructure to be defined as code, so it can be version controlled, easily replicated and tested, truly DevOps!

Basic Concepts

  • Ansible and it's modules are written in Python
    You don’t need to know Python to use Ansible, though.
  • Ansible uses SSH to connect to hosts
  • Ansible is agentless
    There's no need to install Ansible in the target hosts. It will copy Python code using SSH and run from there. Only requirement is Python.

Terminology

Some words you will hear frequently when working with Ansible are:

Playbook
That’s the entry point of your automation. Files in YAML format that instruct Ansible on what to do, we will touch more on YAML format later, but here is a sneak peak of a .yml playbook:

---
- hosts: all
tasks:
- ping:

Tasks
They define an operation to be performed in the destination host. Could be installing a package or creating a directory. Above you can see ping:, which is a task that doesn't do anything other than check accessibility of the host.

Inventory
Also known as hosts file, it’s an INI file with the list of groups of hosts that Ansible will use to run playbooks. Hosts entries usually include the hostname and few variables you can pass to playbooks.

[localhost]
127.0.0.1 variable1=value1 variable2=value2
[webservers]
webserver.awesomecompany.ly ansible_ssh_host=55.44.33.22

Roles
A collection of tasks. Useful when you need to run a subset of tasks multiple times from different playbooks. Roles can also be parameterized so the behaviour is changed depending on the parameters passed.

Don't worry, each concept will be explained in more details further on.

Setting up the Environment

There are different ways to install Ansible. My preferred method is through PIP, the Python package manager. Other methods usually have outdated versions of Ansible or require complex installations.

If you don’t have PIP installed:

# Debian/Ubuntu:
apt-get install python-pip
# RedHat/CentOS/Fedora:
yum install python-pip
# MacOSX (more info):
sudo easy_install pip
# Windows: Unfortunately Ansible does not work on Windows, but you can easily setup an Linux virtual machine with Vagrant and SSH into it.

Install Ansible:

pip install ansible

Test it by running:

ansible --help

Inventory

By default, Ansible on Linux will look for the inventory file in: /etc/ansible/hosts

Next section will show how to change the location, so let's create it in the current directory:

[localhost]
127.0.0.1

Save it as: hosts

Like an INI file, [localhost] is a group and each line below is a host.

We are going to run our first playbook against localhost, as a test.

Config

To define a different location for your inventory file or pass special SSH parameters, there's a file for that: ansible.cfg

Ansible will look this file in the current directory, or in your home directory (named .ansible.cfg) and finally in /etc/ansible/ansible.cfg.

[defaults]
inventory = hosts
remote_user = ubuntu
host_key_checking = False

You can tell Ansible to look for the inventory file in any directory or in the current directory (as the example above).

Other settings:

  • remote_user = ubuntu
    The default user that Ansible will use to SSH to your target hosts.
  • host_key_checking = false
    Avoids SSH asking for confirmation when logging in a host for the first time.

First Playbook

All setup and ready for your first playbook.

Let's take the example above and increment a little:

---
- hosts: all
connection: local
tasks:
- debug: msg="Hello World!"

Save it as: hello.yml

  • hosts: all
    is telling Ansible to target all hosts in your inventory file. Another option would be hosts: localhost, since we have a group called localhost.
    You cannot use hosts: 127.0.0.1, only groups.
  • connection: local
    is just a hack to make Ansible run without SSH. Since the only host in our inventory is localhost, no SSH is needed in this point.

Format for tasks is:

    - module_name: parameter1=value parameterX=value

The module used above (debug) is useful to print environment variables or simple messages during execution.

Running a Playbook

ansible-playbook hello.yml
Environmentally friendly green

This is the output of an Ansible playbook run.

  • PLAY [all]
    The play is targeting all hosts.
  • GATHERING FACTS
    Ansible is collecting information from the host
  • TASK: [debug msg="Hello World!"]
    The task running. Below (in green) is the output of each host running the task.
  • PLAY RECAP
    For each host, the number of tasks resulting in ok, changed, unreachable and failed (explained later).

If you get this error:

ERROR: Unable to find an inventory file, specify one with -i ?

It's because Ansible couldn't find the inventory file. Check ansible.cfg is pointing to the right inventory or put your inventory file at /etc/ansible/hosts

Going Remote

No much can be done with localhost, right? Now it's time to connect to external hosts and perform some cool configuration there.

Before continuing, a bit of terminology so it doesn't get too confusing:

  • Control host is the machine running Ansible.
  • Remote host is the machine which Ansible will access using SSH to perform configuration (based on playbooks). It does not need to have Ansible installed, only SSH and Python.

In this tutorial, the remote host is an Ubuntu 14.04 created using AWS. If you don't have one, you can create a host using any cloud provider (DigitalOcean, Linode, Azure, Rackspace) or run a local one with VirtualBox, VMware, etc.

Let's now add a real host to the inventory.

[webservers]
55.44.33.22

Now we have a group called webservers with a host on it. Use the IP address or hostname of your host.

Accessing Remote Hosts

You will need password-less SSH access, i.e. the control host have to be able to access the remote host without it asking for a password.

If you use AWS, they "force" you to download a private key to access your remote host, it's the only way. Other providers may allow you to create a password first (DigitalOcean), so you need to configure SSH keys.

Since there are plenty of tutorials online on how to setup, I will post the links and wait here until you're back.

Glad you're back!

SSH Keys

By default, SSH will look for a private key in ˜/.ssh/id_rsa, but let's pretend it doesn't. We want to be able to tell Ansible where to look for the private key of each host, so we have flexibility later when different hosts use different keys.

Option 1: One key for all hosts
Add an option to ansible.cfg file under [defaults] section:

private_key_file=/etc/ansible/keys/access.pem

Option 2: One key per host
In your inventory file, add a variable to the host:

[webservers]
55.44.33.22 ansible_ssh_private_key_file=/etc/ansible/keys/web.pem

Option 3: One key per group of hosts
Also in your inventory file, but using group variables:

[webservers:vars]
ansible_ssh_private_key_file=/etc/ansible/keys/web.pem

Option 4: Use SSH config
Since Ansible uses SSH, you can leave totally up to SSH to decide which key to use in each host.

Host 55.44.33.22 *.awesomecompany.ly
IdentityFile /etc/ansible/keys/access.pem
IdentityFile /etc/ansible/keys/web.pem

Not ideal but the config above would try both keys when connection to the host. You can also specify hostnames and use wildcards, covering multiple hosts at once. More on SSH config here.

Option 5: Use ssh-agent
I never had much luck with ssh-agent, but it's worth knowing this option. More on ssh-agent here.

Option 3 will be used in this tutorial.

Test connectivity from the control host:

ssh -i /etc/ansible/keys/web.pem ubuntu@55.44.33.22

Replace /etc/ansible/keys/web.pem with the private key used in the remote host.
Replace 55.44.33.22 with the IP address or hostname of the remote host.

If you connect without a password being asked or any errors, you're good to go (to the next section).

Targeting Remote Hosts

Our inventory now looks like this:

[webservers]
55.44.33.22
[webservers:vars]
ansible_ssh_private_key_file=/etc/ansible/keys/web.pem

Replace above with the details from your environment.

Let's create a new playbook to configure web servers:

---
- hosts: webservers
sudo: yes
tasks:
- apt: name=apache2 state=present

Save it as: webservers.yml

  • hosts: webservers
    We are targeting only hosts under the webservers group of the inventory.
  • sudo: yes
    Ansible will run tasks of this playbook as root.
  • apt is the module and we are passing parameters name=apache2 and state=present.
    This module will make sure the package apache2 is installed (present).

Run!

ansible-playbook webservers.yml
Oh no, my IP address is revealed!

Interestingly, if you run it again, the result is different:

Idempotent FTW

Tasks are (or should be) idempotent. Meaning that nothing is changed if the desired state is already there.

That's why we say state=present instead of install. Ansible's apt module is going to make sure it is present and install only if it must.

So ideally you can run your playbook as many times as you want, just to make sure the server configuration is in the desired state. Changes only occur if configuration drifts.

Common Issues

sudo all the things!

Did you forget sudo: yes? Because I did when writing this.

Dude, where's my host?

SSH could not connect to your host, check firewall, IP address and internet.

Y U NO

That's a module error. Package apache does not exist, it's called apache2.

Bonus: Command Line Tricks

Verbosity

It's always a good idea to run with more verbosity.

ansible-playbook -vv webservers.yml
Colors!

Now the tasks will print the output of the command underlying the action. In this case was apt-get.

To troubleshoot SSH problems, use:

ansible-playbook -vvvv webservers.yml

Check Mode

ansible-playbook --check webservers.yml

It will still connect to the host but not apply any changes, only try to predict whether a change would occur or not. Not all modules support it.

Limiting Hosts

Let's suppose your playbook look like this:

---
- hosts: all
tasks:
- debug: msg="Hello World!"

And you want to run only targeting the hosts under webservers group, without changing the playbook:

ansible-playbook -l webservers webservers.yml

Different Inventory

You can specify the inventory file when running Ansible:

ansible-playbook -i my_other_inventory webservers.yml

Or even work without an inventory!

ansible-playbook -i 55.44.33.22, webservers.yml

That comma after the IP address is the trick. Without that Ansible looks for a file instead.

Next Steps

Now that you can create and run playbooks, they need to do something useful.

Ansible has many (hundreds?) of modules to perform actions in the operational system.

Most common modules are:

Conclusion

And that’s it for this tutorial.

Feel free to contact me on twitter @denot.
My personal blog with more Ansible stuff is allandenot.com.

I'm preparing a second part of this tutorial, so your input is very important!

If this tutorial helped you in any way
If you want more tutorials like this
Please click Recommend below

Thanks for reading!

--

--