Handlers, Facts, Variables, and Templates
Start course
1h 17m

Getting Started With Ansible

Cloud platforms, on-prem servers, dozens of operating systems, more language and frameworks than you can count, and you have to manage it all!

These days even the "simple" application infrastructures have a lot of moving parts. Managing all of this stuff effectively takes some effort, and configuration management tools such as Ansible can help.

Ansible is an automation engine that can help with provisioning infrastructure, configuring operating systems, deploying applications, and much more.

The goal of this course is to teach you how to get started using Ansible for automation. By then end of this course you should be able to create playbooks to automate basic tasks. You won't know everything there is to know about Ansible, however you'll know enough of the basics to start using Ansible. You'll understand how Ansible manages inventory, how to create simple modules, how to create playbooks, how to deal with errors and more.

Understanding a tool such as Ansible has a lot of value to developers and operations engineers. Especially since it's agentless, because that means you can start managing hosts without needing to install an agent on them first. Well, assuming Python is installed. Developers can use Ansible to automate the creation of development environments that mirror production. And operations can use the same playbooks to automate the creation of staging and production environments. This level of consistency between environments tends to reduce bugs; especially those caused from environmental differences.

One of the features of Ansible that makes it so appealing is that it allows you to create modules with whatever language you want. Another appealing feature is the YAML based playbooks. The reason this is so appealing is that YAML tends to be a very simple format for expressing tasks. And that makes it easier to get started using it.

What You'll Learn

Lecture What you'll learn
Intro What will be covered in this course
What is Ansible? An introduction to Ansible
Concepts An overview of the Ansible concepts
Installation How to install Ansible
Inventory How Ansible knows which servers to manage
Windows How Ansible connects to Windows servers
Modules What modules are and how to create one
Playbook What playbooks are and how to create them
Handlers, Facts, Variables, and Templates Handlers, Facts, Variables, and Templates
Roles How to bundle functionality in a role
Errors and Debugging How to deal with errors and how to use the debug module
Next Steps How to keep learning

If you have thoughts or suggestions for this course, please contact Cloud Academy at


Welcome back. In this lesson, I'll be talking handlers, facts, variables, and templates.

Check out all of these hard-coded paths in the playbook. Look at this, I've listed this path maybe a half dozen times and I've also hard-coded it in the Apache config file. Also, every time I need to restart Apache, I have a task for it and that sort of duplication, it doesn't need to happen.

So let's clean up this playbook. First, I'll clean up these duplicated Apache restarts. And to do that, I'm going to use a handler. A handler is basically a task that is called when it's notified of a change. As an example, you always want to restart Apache whenever the config file changes. So, being able to restart Apache only if that file changes is really useful. Actually, let's implement that. First, I'm going to delete the existing two Apache restarts since I'm not going to need them anymore. And I'm going to create a new section and I'm gonna call it handlers and I'm going to create a task underneath it to restart Apache.

This task is really no different than the previous tasks, it's just living in a different place and it's only going to be called when it's needed. Now, I'll add this notify property to the tasks that copy the Apache config file because if the config file changes, then you need to restart Apache before those changes can take effect. A notifier needs to know the name of the handler to run so that it can execute the handler's task.

Okay, now let's run the playbook again. I'll fast-forward to when it's complete and notice that the last task was the copy the config file. However, it doesn't indicate that there was a change so our handler never ran. This is part of the power of handlers, you can ensure that they only run when they need to. I'm gonna make a change to that template. I'll add in a new line and now if I run the playbook again and fast forward to the end, notice that the final task is the restart handler.

The config file changed since the last time I ran the playbook, so the handler will get called. Handlers can help you reduce that duplicate code and make things more efficient because things can get run only if they need to, only if a change was made.

Alright, I wanna clean up these hard-coded strings that are all over the playbook. To do that, I'll create some variables. If you recall the with_items loop that I showed earlier in the course, then you've already seen a variable being used. In that example, it was the item variable. Variables are a great feature because they allow for more modular code. Variables allow you to set defaults and then over-ride them as needed.

For example, you could set up a default variable for a username inside of a playbook, and then you can override that default on the command line when you run the playbook, and this allows you change things at run time. Ansible can fetch variables from different locations and because of that, it has a very specific order of precedence for which variable will be used when the same name is set in multiple locations.

This is a very useful feature because if you share your plays, role, playbooks, et cetera and you know what the default values should be for most use cases, then you can set those. And the person using your code can change that if they need to.

Okay, let's remove the hard-coded file paths. So I'll start by adding a vars section. This is where you can set the defaults for your variables used in this play. I'm going to paste in the variables and now I can start replacing the hard-coded strings with these variables. So anywhere that I mentioned the path /tmp/webapp will now be app_download_dest. And the app_dest is the place where Apache expects the application to be located. By changing this, the hard-coded values in the Apache config file might not work if someone changes the variables.

Let's edit the config file and add the variables to it. There are two locations where the path is used so I'm gonna swap those out for variables. And now the final variable is to replace the GitHub repo url so I'll swap that out.

Awesome, let's now test it out. And it looks like there's a syntax error. Ansible is kind enough to give us a hint at where the error is located. Let's go back to the app.yml file and check it out. And there it is, an extra quote inside of this string, I'll remove that and save it again and let's test it out. I'll fast-forward to when it's complete and it seems that there were no errors this time, which is good, so I'll reload the page and we'll just make sure that nothing has broken in the browser.

And I'm getting an error message, okay. Maybe you've already determined the cause. The issue is that I've edited the Apache config file to use variables, however I'm using the Ansible copy module to transfer the config file from the control machine to the lamp server and the copy module doesn't know or care about variables. It just copies the file as it is without modification.

So this is where the template module comes into play. Templates are a great feature of Ansible. They allow you to apply some pre-processing to a file before it's copied over to the host. If you have a file, for example a configuration file, and it needs to have the name of the database server, then you could use variables to dynamically populate that inside the template.

I'm going to change the module from copy to template and that should fix our issue. And now if I run the playbook again, it should resolve that. I'm going to fast-forward to when it's complete. Perfect, and I'll reload the browser and it's back to being in a working state. Ansible using the Jinja2 template engine for processing templates. You can use conditionals, loops, and variables to generate a file to have copied out to the remote host. In addition to any variables that you create, templates also have some specific variables that Ansible provides, including things like the template path, the template owner and the template run date, amongst some others.

However, those aren't the only variables that you can use and you're not limited to using them just inside of a template. You can use these variables inside of playbooks, as you've already seen. Ansible uses what it calls facts to provide you with a lot more information about the servers that you're managing.

These facts are obtained by Ansible automatically with the set-up module. And while you can disable facts if you want to, they're enabled by default. Because facts are obtained with the set-up module, that means that you can run that module as an ad hoc command and see all of the variables that you have access to. I find this very helpful when I create plays.

Let's check it out. The command is Ansible followed by the host pattern, in this case the group name lamp. And then the module named setup. When it runs, it goes out and fetches information about the host. I won't go through this entire list, I think it's worth you running through this for yourself to see the scope of the data. However, I will show you how to use these facts inside of a playbook.

All of this data is turned into variables for you making it easy to get details about hosts dynamically. I'm going to create a new playbook called variables.yml for this demonstration. Now let's say that you need to know what environment variables exist on a host. You can fetch that info with facts and then you can use those facts in the same way that you can use any other variables. I'm going to use the debug module to print out this to the console.

I'll explain the debug module later in the course, for now, you should just know it's going to print out a message to standard output. So I'll set the message argument and I'll include in that message the Ansible_emv fact variable and if I run this, you can see that it prints out all of the environment variables.

Well maybe I really just need the home variable. For that, I can use square braces and the name of the property I want to use inside of quotes. And this is the same syntax you'd use to get any sort of information from a dictionary. And if I run this, it's going to return just the value for that home environment variable.

So facts are a great way to get additional dynamic variables for playbooks and templates. Facts and variables are useful in different ways. However, one common use is with conditionals. Ansible allows you to use the when statement to conditionally run a task. Let's check out how to use facts, along with when statements to conditionally run something, depending on the distribution of Linux being used, or maybe the operating system family.

I've edited this playbook here offline. However, I'm going to run through it now to explain it. There are three tasks. The first is using the debug module to print out the host name of the server it's being run against. Notice the when statement at the bottom of the task. It specifies that the task is only run if the distribution is either Debian or Ubuntu. The Ansible_host's name is fetched from the facts that Ansible collects.

The next task runs the window ping module and registers the output to the win ping variable. But only if the operating system family is Windows. And the final task will run the debug module to print out the win ping variable, again only if the OS is Windows-based. Notice the host is set to all, which means that since the inventory contains three Linux servers and one Windows server, this playbook is going to run against all of them.

Now, if I run the playbook, it's going to gather the facts and then it's going to run the first task with the debug modules against the three Linux servers and it's going to skip the Windows server. And the final two tasks are going to be run against the Windows server, only skipping the Linux servers. As you can imagine, conditionals are a great way to create playbooks that can run across multiple distributions of Linux.

As an example, Red Hat and Ubuntu use different package managers, so if you want to install something like the Apache web server, then you'd use a conditional to use yum for Red Hat and apt for Ubuntu.

Okay, I've covered a lot of information in this lesson. So let's summarise what's been covered. Handlers are a great way to reduce code by creating a special type of task that is only run when it's notified by another task.

Variables allow you to avoid hard-coded info and make for more reusable playbooks.

Facts are information about hosts that are stored in variables that can be used anywhere variables can be used.

And templates are files that can be processed by Ansible to dynamically add variables to loop over data and to conditionally add content to the file. Because templates use the Jinja2 engine, they have a lot of features. If you wanna learn more about the features, you can check out the documentation for Jinja at the URL on the screen.

Okay, that's going to wrap up this lesson. In the next lesson, I'll explain how to make playbooks even more reusable by using roles. So if you're ready to keep learning, then let's get started in the next lesson.

About the Author
Learning Paths

Ben Lambert is a software engineer and was previously the lead author for DevOps and Microsoft Azure training content at Cloud Academy. His courses and learning paths covered Cloud Ecosystem technologies such as DC/OS, configuration management tools, and containers. As a software engineer, Ben’s experience includes building highly available web and mobile apps. When he’s not building software, he’s hiking, camping, or creating video games.