Ansible

You are currently browsing articles tagged Ansible.

Ansible Roles and Variables

While automation is great, we have to be careful not to recreate past problems. What I mean is that playbooks should be written in a generic fashion that can be applied to more than one host. If we’re writing playbooks that only work on one single host, we aren’t much further ahead than we were before.

Two of the key components of making playbooks reusable are Ansible variables and roles.  Let’s try and define each of them individually and while showing some examples along the way.

Roles
Roles allow you to call a set of variables, tasks, and handlers by simply specifying a defined role.  Roles require the use of a defined file structure in order to work.  Per the Ansible documentation, that structure looks like this…

image 
Roles are really just a way to split up your playbook into smaller reusable parts.  For instance, let’s consider we added another host to our lab…

image 
Now look at this playbook…

Its a modification of the playbook we used in our first post.  It includes the original play, but also a second play that targets only the second Ansible client.  The second play copies an amazing page off the of the well known website www.purple.com and makes it the index page for our 2nd client.  Since both clients need the web server configuration, we can simplify this playbook by making the web server configuration its own role.  To do this, we create a file structure as show above for this role…

Note: Theres an easier way to make this folder structure using Ansible galaxy but that’s out of scope for this post.

While we’re creating all the possible folders required in a role, we’ll only be focusing on the tasks and handlers folders at this point.  The first thing we want to do is create a ‘main.yml’ file in the tasks folder that looks like this…

Notice that this is just the task that was defined in our initial playbook.  Next create another ‘main.yml’ file under the handler directory that looks like this…

Same thing here.  All we did was take the handler information from the original playbook definition and move it into the handler directory.  The last piece is to modify the playbook to call the role rather than the task.  Modify your playbook to look like this…

Petty simple huh?  Now if we want to run the tasks associated with building a web server we can just call that role.  If we put ansibleclient1 back to it’s original default state, running the playbook should look like this…

image
Very similar output to what we saw the last time we ran a playbook.  A quick test of browsing to each server should show the results we’re looking for…

image 
As you can see, roles can come in pretty handy for generic tasks.  Keep in mind that we’ve only scratched the surface of the role construct here to get you up and running.  We’ll be talking more about other aspects of roles in later posts. 

Variables
One of the ways to make playbooks more generic is to use Ansible variables. Variables can be defined in multiple locations.  Let’s walk through each one of them and a quick example…

In the inventory file
Variables can be assigned right along with the host definition in your inventory file.  For instance, look at this example host file…

Note: In many places you’ll see variables assigned in the above manner referred to as ‘host_vars’ and ‘group_vars’.

In the above example, we set variables at both a group and an individual host level.  Here we define that the variable ‘webserver_package’ is httpd for each host.  Then, at the group level, we set the variable ‘http_server_port’ to 80.  We can then change our task definition (main.yml) to look like this…

And our handler definition to look like this…

In both cases, we’re making use of the variables defined as part of the role definition.  While we can certainly define variables as part of the inventory file itself, best practice calls for these variables to be stored in separate files.  Much like many other components of Ansible, this relies on a default folder structure. Assuming you are using the default inventory file (/etc/ansible/hosts) you need to create two additional folders…

For host variables – /etc/ansible/host_vars
For group variables – /etc/ansible/group_vars

Inside of these folders, you place YAML files named after the host or group you wish to define variables for.  For instance, my folder structure might look like this…

image
And the files definitions themselves would look like this…

In the playbook itself
These same variables can also be defined directly within the plays.  If we remove the variables from the host inventory file, we can add them directly into one of the plays in the playbook definition.  Keep in mind, this is in the playbook definition, not in the role definition, we’ll cover that shortly.  So the playbook would look like this…

Notice that we now have a ‘vars:’ section defined at the top of the playbook. 

In the role definition
Variables can also be defined as part of a role.  There are two places this can be done with variables defined in either the ‘vars’ or the ‘default’ directory of the role.  Much in the same way we defined other role functions, we can also define a ‘main.yml’ file in both of these directories that define variable to use as part of the role.  For instance let’s assume that we have the following defined as ‘/etc/ansible/roles/webserver/defaults/main.yml’

and we have the following defined as ‘/etc/ansible/roles/webserver/vars/main.yml’

Above we define the same variables as previous examples, but in the case of ‘webserver_package’ we’ve defined them in both files.  When the playbook is run, we’ll find that the value from ‘vars’ directory is the one that’s used.  So why is that?  The ‘default’ directory stores values that are role default.  That is, they are sane default settings which can be overridden in certain circumstances.  Defaults can be overridden using values from the ‘vars’ directory, group vars, or host vars.  At the time of execution the variables are merged and precedence rules are applied to figure out the winning values for all of the variables.  The default precedence model looks like this…

-Variables defined in role ‘defaults’
-Variables defined as group_vars
-Variables defined as host_vars
-Variables defined in plays
-Variables defined in role ‘vars’
-Variables defined at runtime with –e switch

All of those should be familiar to you now except for the runtime option which we’ll cover last.  Priority increases as you move down the list.  That is the, if you want to override absolutely any other defined variable, you should do it at  runtime with the ‘–e’ flag. 

At runtime
The last, and most powerful, place to define variables is when you execute a playbook.  To prove this, lets go back to our role example and change the variables we defined.  We’ll remove the ‘main.yml’ file from the ‘default’ folder, and change the ‘main.yml’ in the ‘vars’ folder to look like this…

If we run the playbook now, it will fail since these values are the only ones defined (and because this a poorly written example playbook that doesn’t use all the variables in all the right places).  If we want it to work as intended, we can rerun the playbook by specifying variables at runtime.  To do that, we run this command…

Note: Its important to put the single quotes around the variables when passing more than one variable in this manner.

When we do this, the variables we provide at runtime will have precedence over the ones defined in the ‘vars’ directory and we’ll get a successful playbook execution resulting in the web servers working as expected…

image

The more I play with Ansible the more I like it. The role construct coupled with scoped variables is pretty powerful.  I hope you found this look at roles and variables useful, comments always welcome!

Tags:

Ansible up and running

After much delay – I’ve finally found time to take a look at Ansible.  I’ve spent some time looking at possible platforms to automate network deployment and Ansible seems to be a favorite in this arena.  One of the primary reasons for this is that Ansible is ‘clientless’ (I’m putting that in quotes for a reason, more on that in a later post).  So unlike Chef, Puppet, and Salt (Yes – there are proxy modes available in some products) Ansible does not require an installed client on the remote endpoints.  So let’s get right into a basic lab setup.

While the end goal will be to use Ansible to automate network appliances, we’re going to start with the a more standard use case – Linux servers.  The base lab we will start with is two servers, one acting as the Ansible server and the second being a Ansible client or remote server.  Both hosts are CentOS 7 based Linux hosts.  So our base lab looks like this…

image
Pretty exciting right?  I know, it’s not, but I want to start with the basics and build from there…

Note: I’ll refer to ansibleserver as ‘server’ and ansibleclient1 as ‘client’ throughout the remainder of the article.

If you’re following along, my assumption is that you have 2 servers that are similarly IP’d, have resolvable DNS names, and are fully updated CentOS 7 hosts.  That being said, let’s start out by building the Ansible server node.  The install process looks like this…

That’s it.  You’re done.  Do a quick check and see if Ansible is working as expected…

image
So it looks like everything is installed.  The next thing we want to do is configure a means to communicate with the clients.  Ansible’s default means of doing this is with SSH.  So let’s configure an SSH key on the server and send it to the client…

After the key has been installed, test it out by SSHing to the client from the server…

image

The login should work and you shouldn’t need to specify a password for the root user.

Note: In this example Im using the root account on both the server and the client.  By default, Ansible attempts to use the current logged in user to connect to the clients.  If you dont plan on using the root user on the server, you’ll need to tell Ansible to still use root for connectivity. I plan on covering this functionality in a later post.

The next thing we nee to do is to define the clients you want the server to work with.  This is done by defining them in Ansible ‘hosts’ file which is located in ‘/etc/ansible/hosts’.  Out of the box, this file is full of examples.  For the sake of clarity, I’ve removed the examples leaving the file looking like this…

image
Here you can see I’ve added a group named ‘linuxservers’ and in that group I’ve defined a client ‘ansibleclient1’.  Any host that you wish to manage must be specified in this file.  Once defined, it can be referenced either directly by name or as part of a group.  Review some of the default examples in this file to give you an idea of how you can match on different hosts.

So now that we have a client defined, how do we talk to it with Ansible?  The best test is to try and run some basic modules against the clients.  For instance, there’s a ‘ping’ module that lets you see which hosts are online…

image
Notice that you can run the ping module against the client server in a few different ways.  I can reference it by name, by group, or by using the ‘all’ flag which matches all clients defined in the hosts file.  As you’ll see, modules are the key component of Ansible that allow it to perform a wide variety of tasks.  For instance, there’s a module  for SELinux…

image
Above I call the ‘selinux’ module and pass it an argument with the ‘-a’ flag to tell it to disable SELinux.  There’s also a shell module that allows you to run shell commands on clients like I do below with the ‘ip addr’ command…

image

The list of all of the modules can be found on the Ansible website.  So while the modules themselves are powerful, running them in this manner isn’t much better than just executing one module at a time.  The system becomes really powerful when you couple modules with playbooks.

Ansible playbooks are written in YAML and contain one or more ‘plays’.  Let’s look at an example playbook so you can see what I’m talking about…

Here’s a fairly basic playbook that installs and starts an Apache web server.  This playbook defines one play, with one task.  Plays are defined by specifying the hosts to be part of the play followed by a series of tasks to execute against them.  Playbooks can contain multiple plays each with multiple tasks.  In this case, this play has one task which uses the module ‘yum’.  In addition, to the tasks, you can also define handlers.  These are items that you want to run ONLY if a certain task makes successful changes to the system.  So in this case, we tell the task ‘Install Apache Web Server’ to notify the handlers ‘openport80’ and ‘startwebserver’.  If the task results in the system successfully installing Apache, it will notify the handlers defined for the task.  So let’s save this playbook on the server and run it…

image
As you can see, the means to call a playbook is simple.  You just use the ‘ansible-playbook’ command and specify the YAML playbook definition.  As you can see from the run, Ansible successfully ran the task and in turn triggered the two defined handlers.  If we browse to the client on port 80, we should see the Apache start page…

image
Success!  Let’s run the playbook again and see what happens…

image
Note that this time the task completed without making any changes.  Since nothing changed, there’s no need to notify any of the handlers.  Pretty slick huh?

I hope this first look at Ansible has been helpful.  Stay tuned for more posts on other features and ways to automate with Ansible!

Tags: