The course is part of these learning paths
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 firstname.lastname@example.org.
Welcome back. In this lesson, I'll be talking about dealing with errors and how to do some basic debugging.
When a task in Ansible fails, the play fails. And for the most part, that's probably what you're going to want to happen. However, sometimes an error isn't all that important. So, if you don't want an error to stop the playbook, you can choose to ignore that with the "ignore_errors" property.
Let's check it out. I'll create a new playbook to test this out, named errors.yml and I'll paste in just boilerplate stuff. And I'm going to add a new task. This task is going to use the command module which will allow us to call a command on the targeted hosts.
In this case, I'll call the false command. Linux has a true and false command and they return a status of zero for the true command and one for the false command, which means Ansible will see the false command as an error. And I'm going to run this on the command line.
When it completes, you can see that it threw an error. If this was a command that you didn't mind throwing an error, then you could use the "ignore_errors" property and set it to true just like I've done here. Now, if I run this again, when it completes, Ansible made it clear that it did throw an error. However, it ignored the error and it didn't stop the play. So if you run into a scenario where you don't care about the error from the command, you can ignore them.
Now, what if you run a command that can throw multiple errors and some don't matter and others do? As an example, imagine you're trying to add a user account to some system, maybe a database or something like that. And if the user already exists, the error says "user name already exists." However, it's also possible that the command can't connect to that system and it throws an error of "cannot connect."
So there are two possible values and if the user already exists, then it's okay to ignore it. But we want to know if it can't connect. For this, Ansible provides the "failed_when" statement. Let's check it out with a new example. First, I need a command that I can run to test out these failures.
So I've created this very basic bash script that will echo out specific error messages to standard error and return a status of one if the arguments passed in are either exists or connect. If no arguments are passed in, then the command is successful.
Now I've already given this script executable permissions, so let's test it out. If I run it with no arguments, it prints out "success." if I pass in the argument "exists," it's going to return "user already exists." And if I pass in the argument "connection" it returns "cannot connect." Now, let's run this with Ansible. I've already edited the command task to run this bash script with the argument of connection. And I've also changed the host to be local host since the command only exists here on the control machine.
Now, when run, Ansible fails the playbook as you'd expect. If I change the argument to the command to be "exists" then we run it again and it's going to fail again because the command threw an error. Again, this is expected however, it's not what we want to happen. In this scenario, only the connection error is really going to be considered an error.
So, I'm going to use the "failed_when" statement to tell Ansible what a failure should be in this context. Before that, I need to register the output of this task. You've seen that output already even if you didn't think about it. Information here in the terminal is the output from the command module.
By registering the output, you're asking Ansible to store that dictionary of output into a variable that you can use. So I'll use the register statement to store the output in the "command_error" variable. You can name this anything you want as long as it follows Ansible's rules for naming a variable.
Now, I'll use the "failed_when" statement to tell Ansible to only consider this an error if the word connection is found in the standard error key of the output dictionary that is stored in the variable that I just registered. I could use any of these keys. You can see here we have standard error, standard out, the command that was run, et cetera.
The ability to register the output from a task goes well beyond error handling and debugging. It also allows you to fetch info from a remote source, register it in a variable, and then use it in other tasks. Let's see what happens when I run this.
Keep in mind, the current argument being passed in is "exists" which means the error should say "user already exists." And the playbook ran successfully. And that's what you'd expect. Now if I change the argument to "connection," the error message will say "cannot connect." So this should fail because I've told it that if it has the word connect, then that is a legitimate error. And it does, it fails. Perfect.
So this is a useful way to zero in on just the errors that matter. It's worth pointing out, the string I used to check the existence of that string connection in the standard error is a Jinja2 expression. So any valid Jinja2 expression should work here which gives you a lot of flexibility. When it comes to dealing with errors, sometimes it's difficult to figure out why they're happening altogether. That's something that the debug module helps with.
The debug module is an extra module provided by the community, and it allows you to print out a message or a variable and oftentimes that message will have the contents of another module's output or some sort of facts. Let's check this out further.
Let's register the output of a couple of modules and print them out to debug them. I've already modified this playbook offline and I've added a task to use the apt module to install a package named nmap and I'm going to use the stat module to get some information about the test error script that I used earlier.
Both of these tasks register the output to a variable and then I have two tasks to print out the dictionary stored in that variable. Now if I run this, it's gonna take a moment and when it's complete, you can see that it printed out a dictionary for each task. The first shows how apt installed the package, the second shows details about the file. And just like other variables, you can keep drilling into these keys in the dictionary to get just the data that you want.
For example, if you only cared about the checksum, you could print out just the checksum. This is useful when you're trying to understand errors. It's also useful when you're trying to better understand what data a module provides. Okay, that's gonna wrap up this lesson.
In the next lesson, I'll wrap up this course by talking about next steps. So if you're ready to wrap up the course, then let’s get started with the next lesson.
About the Author
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.