Ansible Constructs
Start course
2h 47m

This course looks at the Ansible Automation Platform. We'll look at how it works, looking at all the components like modules, tasks, and playbooks. We're going to show Ansible commands and how to use the command line tool, Ansible Navigator. You'll also learn about variables, templates and playbook basics. 

Then we'll move on to the automation controller, the web UI and API, and you'll learn where it fits in and who would be using it. We'll also take a look at some enterprise features like Role-based Access Control and workflows.

Learning Objectives

  • Learn the basics of Ansible including its components including modules, tasks, and playbooks
  • Understand Ansible commands and how to use Ansible navigator
  • Learn how to use variables and templates
  • Use Ansible's web UI and API, known as Automation Controller
  • Learn how to build a job template
  • Understand how to use Role-based Access Control and workflows

Intended Audience

This course is intended for anyone who wants to learn more about the Ansible Automation Platform in order to operationalize and put their Ansible workloads into production.


To get the most out of this course, you should have some knowledge of Linux or Red Hat Enterprise Linux. Existing knowledge of Ansible would be beneficial but not essential.


This next section, we're going to cover Ansible constructs. Constructs are kind of little tools we can use with modules and our playbooks and have add additional flexibility and functionality depending on the use case. So specifically, we're going to cover conditionals. So think like if you have any programming experience, it's like an if, if this condition happens do this task. Handlers, which are kind of more unique to Ansible or DevOps tools. And finally, loops and loops I have a hate- love relationship with. I definitely use them all the time, so you should learn them, but I try to avoid loops at any cost because they always have a performance indication with your playbooks. Because they're basically just re-running that same task as many times as you loop and sometimes there's a smarter way to build your playbooks.

So conditionals are most commonly used with variables, so when this variable is set to this or this variables within this list or this variable meets this condition, then I can run this task. So in this little example here it says, when my_mood is happy, you can print this debug, this debug tasks, and I'll say, Yeah, I am happy. A very simple example of how to do this. Alternatively, we can change this a little bit. Ask at your own risk, I'm grumpy. But since, my_ mood is set to happy, it's only going to print this first task. It'll basically just say skipped on this and it'll have a new color for us. It will be kind of like a light blue by default if you don't change it.

So here's a more realistic way. You would use a when or have a little bit fun just as an easy example, is if I'm trying to Install apache, I might have multiple types of Linux devices out there. I might have some Debian, I might have some Ubuntu, I might have some RedHat, I might have some CentOS stream going on. So this is where I can use the yum module versus the app module. And then here I can actually have a when statement so that it's only running that particular task on those systems. So on the RHEL nodes, it will skip this task and on the Debian nodes it will skip this, the second task. So that's where when statements come in handy.

What you'll see is kind of general rule of thumb for me for designing playbooks is when's are really great when you have like 1 task, but as soon as you have like 2 or 3 tasks, you end up using something called the block, which allows you to create kind of a multiple task, but like use one conditional on them. But what I tend to do is separate it into a new playbook and I just do an include. So that I can include that particular playbook based on that same kind of condition. So in this case, if I took that kind of functionality here, I would do include and I would have Debian and I have like a Debian.yml playbook or something, rather than doing it when. And this allows a lot more functionality because whens aren't that resource intensive.

But you can imagine if I had like 25 different types of Operating Systems, which isn't really that common by any means. I would have a ton of tasks here that are always sketched depending on where you're going. Whereas if I just didn't include into that device, that's just a little bit cleaner way to write the playbook. So conditionals are really common. However, as you kind of get more into production, you end up kind of creating little shortcuts around them to make neater playbooks. So here's another really cool construct, which is this idea of a handler. This is kind of going to move up to a handler, but basically we're showing the register, which is a task parameter.

So for this yum task, I'm going to register the results of this task. Now this could be used for all kinds of cool stuff, you could graph like the state. And this is kind of how insights works to, can get information about every task and kind of give you meaningful data. But in this case, we only want to restart httpd when this result has changed. So if this is already on the latest, we're not going to restart httpd. So this is a really cool concept because we can use the state of this task to affect tasks that are later. And we used a conditional here when, that say only when these results change. Alright? So I said that would lead up to handlers, so here's the handler. Instead of using a register, we can use a notify.

Now this basically has the built-in functionality that, that conditional had. It says it's only going to start this notify when this task changes. So, notify says only notify this particular handler, if this task changed. It's just the implicit way it works. This handlers is going to restart a CBD. So this has the exact same functionality as the previous slide. But this is the handler where it kind of takes care of that kind of changed and you don't have to register. So these handlers are really common when you have, imagine you have like 4 or 5 different services for your application that you might need restarted. You can put them all as handlers so that it's less disruptive to your applications. So you're only restarting exactly what you need to restart rather than just restarting at all.

So here's an example, if either of these task changed, the handler will be notified once. So handlers are also really efficient with how they run. And this is where they have an advantage over those when statements, because you might have multiple tasks that are just becoming redundant, as the handler can be run once and it's just the same notify, no matter how many tasks might be affected. So maybe you modify the html files, but then you also updated the CSX and you also updated the configuration file. And any of them you want to notify to restart_httpd. This is just a more efficient way to do it than just doing conditionals. If both tasks, it'll still be notified once. So it's just really efficient, it's not going to restart it twice, as what this slide's trying to make out as handlers are just efficient.

So if it was 25, notifies, as long as one of them triggered, it's going to do it. If neither task does it, obviously, that's where you get the benefit, where it won't run the handler. So finally, we're going to cover loops. So here we have 3 users. It's kind of a silly example. But I think it's a good, Hello World example again, kind of go into that basic level as I have, dev_user, qa_user and prod_user. I want to make sure all 3 of these users are present on my node. It's the same task. You can imagine if I have 25 users, that's pretty annoying. So there's a way we can do this with a loop. You can literally use the task parameter loop, any task, any module.

We'll take this loop whether or not it makes sense for that particular module is, it depends on the module that for this is a great use case where you can have, hey, I want all 3 of these users in a loop. Now a lot of modules can actually take a list. For example, the yum module can take a list of applications to install. It has some built-in efficiency that it can install multiple applications by just handing it a list. And that's a much faster experience in a loop. So at face value, if I showed you this slide and you see loop, you're like, oh, I could have yum. I'm going to have like 25 packages in my loop statement. But that will be a lot slower than if you just handed a list. So it's always good to check the module documentation for that particular module and see if it can take a list.

If you're handing it multiple things or if you have to use the loop. Sometimes there's more efficient ways of doing the thing you want to do with Ansible. In this case, it would run exactly the same way as it did here. It will be 3 tasks worth of work. But here it's just a little bit cleaner. It's this just one task and I have 3 users and that was really cool, as I could store all 3 users in another file. So it doesn't, like you could just have like a a double curly brace that's here. And it points to a list of users. So it's kind of manage just as like a config management, right! I just update that file, but the new user I want and then Ansible takes care of making sure those users are present on all the servers I want them on.

So again, let's move on to a practical example where I can show you this. We're not going to go through every single construct as an example, but I thought showing the handlers would be really important here. So I've already gone ahead and made sure I ran on all 3 web nodes. So back in the playbook we showed with Apcahe, I switch this to web and re-ran. So we have the Apache server running. And what we're going to see here is we have a playbook. This playbook is running on the group web, which includes node1, node2, and node3, which are all RHEL 8 web servers. There's two tasks in this playbook. There's the copy task which will copy our configuration file over. It's not templating, it's not doing anything fancy, it's just a flat file. We can see it right here, just a normal configuration file. This task has a notify, meaning that if this task changes, it will run this handler. We have a handlers group here. So this handler says restart_apache.

So let's go ahead and look at what this looks like because we're kinda doing two things here. We're running on 3 nodes at the same time. And we have 2 task with a notify. And I'll explain what's going on. So we're going to ansible-navigator. We're going to do the interactive mode. Oops! That's not the playbook. So when we go to interactive mode, this is a little more interesting because there's more than 1 node and more than 1 task, but there's 1 play. So we'll zoom into that play by pressing 0. And we can see the tasks running. So you'll actually see there's an implicit first task, gather_facts, because we'd never said gather_facts False. So it's implicitly always True.

The second task is copy, so it copies it over. And then the third task was restart_apache. So all 3 nodes, went in there. Now it's kinda cool that we can go like node2. We can filter with the colon filter node2 and filter just a particular host. We're going to escape out of that, go back into play. We can also zoom in by tasks indicated by that line number, so we can kind of see exactly what happens. So on task on line 3, it'll zoom in and give us that kind of YAML output of that task and everything that happened. And I talked before about color and people being colorblind. As you'll see, everything is programmatic here. So there's actually a key value changed and then true. So you can kind of tune this and make your own kind of output based on the task output as there's always key-value pairs, for this particular task that we can look at.

So, it ran, it configured Apache, it put on the configuration and we already can explain idempotency, but I'm going to rerun this task now to show kind of performance increase by the way we've structured this playbook now. We're going to rerun this in an interactive mode. I'm going to zoom into the play, copy those over but nothing changed. So it didn't run that last task, the handler. So this is where handlers show up quite a bit of a performance increase, is you're not running tasks that you don't need to run. So in this case, it's also not going to be disruptive because that task did a restart. It didn't just start it, it's making sure that it's actually restarting the configuration and applying those changes. And again, we'll run it one more time just in standard mode for people that are familiar with ansible playbook command. If I can actually click the command, right? There we go. It's because I had a - mode versus --mode. And you can see that's what the playbook is, it's wrapping little bit here too so the font size is large, but it's fine.

That's another advantage of the interactive mode as we can kind of zoom in rather than kind of getting this really long horizontal configuration. So with that we can see how these constructs allow us to kind of increase the things we can do with Ansible playbook. We have loops, we have registers, we have handlers and there's kind of these tools in our tool chest, for lack of a better word, that we can use when they make sense.

Now, I like showing handlers because I think handlers are really important, one that people should use. I think loops are kind of like use them if it's a last resort because there's usually a performance hit because you're re-running that task over and over. The other problem with loops is depending on how you write the loop. You can make the playbook look more complicated, which is not helpful as Ansible is meant to be in a position where you can read it and it makes sense. It's self-documenting. And you can make loops easy like that. But what I see is a lot of people abuse it, especially people with programming backgrounds where they have Python. Programming background as they kind of try to recreate programming versus using Ansible, the ansible way. So that's why I kind of tend to gravitate towards just handlers and not using loops. Now you could look through my code and you'll find tons of examples of using loops. But I say, like, don't, don't, I can preach it and teach it. Doesn't mean I always listen to myself. So with that example, let's move back into the slides.

About the Author
Learning Paths

Jeremy is a Content Lead Architect and DevOps SME here at Cloud Academy where he specializes in developing DevOps technical training documentation.

He has a strong background in software engineering, and has been coding with various languages, frameworks, and systems for the past 25+ years. In recent times, Jeremy has been focused on DevOps, Cloud (AWS, Azure, GCP), Security, Kubernetes, and Machine Learning.

Jeremy holds professional certifications for AWS, Azure, GCP, Terraform, Kubernetes (CKA, CKAD, CKS).