Configuration management could not be simpler!
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!
- 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.
Some words you will hear frequently when working with Ansible are:
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
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.
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.
127.0.0.1 variable1=value1 variable2=value2
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:
apt-get install python-pip
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.
pip install ansible
Test it by running:
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:
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.
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.
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).
- 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.
All setup and ready for your first playbook.
Let's take the example above and increment a little:
- hosts: all
- 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
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
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.
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!
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:
Option 2: One key per host
In your inventory file, add a variable to the host:
Option 3: One key per group of hosts
Also in your inventory file, but using group variables:
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 188.8.131.52 *.awesomecompany.ly
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 email@example.com
Replace /etc/ansible/keys/web.pem with the private key used in the remote host.
Replace 184.108.40.206 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:
Replace above with the details from your environment.
Let's create a new playbook to configure web servers:
- hosts: webservers
- 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).
Interestingly, if you run it again, the result is different:
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.
Did you forget sudo: yes? Because I did when writing this.
SSH could not connect to your host, check firewall, IP address and internet.
That's a module error. Package apache does not exist, it's called apache2.
Bonus: Command Line Tricks
It's always a good idea to run with more verbosity.
ansible-playbook -vv webservers.yml
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
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.
Let's suppose your playbook look like this:
- hosts: all
- 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
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 220.127.116.11, webservers.yml
That comma after the IP address is the trick. Without that Ansible looks for a file instead.
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:
Run any shell command
Create files and directories and set permissions
Copy files from control host to remote host
- apt, yum, bower, pip, composer, gem, npm
Install/update packages with the most popular package managers around
- git, mercurial (hg), subversion
Version control systems to checkout projects
Manages starting/stopping services
- And many others! Documentation of all modules
And that’s it for this tutorial.
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!