Skip to content

Instantly share code, notes, and snippets.

@acbharat
Created April 17, 2019 14:15
Show Gist options
  • Save acbharat/daef348e258d19c2db4f2e6fbba3a6bf to your computer and use it in GitHub Desktop.
Save acbharat/daef348e258d19c2db4f2e6fbba3a6bf to your computer and use it in GitHub Desktop.
Introduction
Hands-on Ansible, where we're going to learn and get our hands dirty about system provisioning, change management, and automation using the open source software, Ansible. Throughout this course we'll be showing you the concepts and fundamentals of the software, along with real world examples and exercises on doing many server-related tasks. My name is Aaron Paxson, and in this module we'll be introducing Ansible, what it can do, and why you'd want to use it.
What Is Ansible?
So really what exactly is Ansible? Well perhaps you heard somebody talking about it or maybe you read about it online. Well the name actually came from an old sci-fi writer back in the '60s which denoted a fictitious device that is supposed to communicate over interstellar distances in like real time, and after that definition, well, you may have thought of Ender Wiggin from the movie Ender's Game. In this movie, Ender actually uses an Ansible to communicate to thousands of ships galaxies away in real time, and incidentally, that's pretty much what the author named his software after. Just as Ender was able to use an Ansible to talk to thousands of ships to give them instructions, well the software Ansible can talk to hundreds, well, if not thousands of servers to give them instructions. So Michael DeHaan, who's the original author of the software, he was also the creator of the software package called Cobbler while he was working at Red Hat, and Cobbler was really kind of meant to bootstrap bare metal systems with an operating system, and he wanted something that could complement that Cobbler software and expand its capabilities beyond that OS installation, keeping it simple and easy to implement, but still complex enough to handle any requirements when needed. In my opinion, that's not really an easy task. So in his spare time at home and on his couch, he just kind of decided to start coding. And at this point I'm kind of picturing him in the wee hours of the morning with like lots of energy drinks lying around and bloodshot eyes trying to get that last test case to work. If you're a developer, you know what I'm talking. And however he got it done, pretty soon he started to like what he was seeing, and so much so he got other community coders together and started testing and coding on top of it, and well, over time a community was starting to build and the code was coming together really nicely, so much so that the software was actually downloaded more than a million times last year alone. So, I still haven't actually answered the question yet, what exactly is Ansible? It's actually a few different things that kind of work together as a single system. Ansible is really good at four different functions, change management, provisioning, automation, and orchestration. Now keep in mind that these aren't really standalone functions, it's not like you can do change management without automation, and you can't really do orchestration without automation either. Same thing with change management and provisioning kind of go hand in hand. So they don't really stand alone, but we are going to take apart each one of these functions to see how Ansible can actually use each one of them. So let's see and take a quick minute to discuss each of these as it pertains to Ansible.
Change Management
Change management is probably the largest function out of all the rest. All the other functions kind of build on top of this core idea, and it starts at defining what the system is supposed to look like. This is where you define the system state. In other words, you write up what the system is supposed to look like under normal circumstances, and then make sure that's what it is. If it's not, well then you need to enforce it. For example, maybe a web server is supposed to have Apache installed, and let's say you wanted to be at version 2. 5, and it's supposed to be in a running state. Anything else that would deviate from this state that you define would dictate a change event. If the system has deviated from that state, well you're going to want to put the system back where it's supposed to be and then mark that system as changed. Now marking the system as changed may or may not be important to you. It would certainly be important to you if it's, let's say, a production system, and this is because, well, production systems shouldn't be changing under normal circumstances, they should only be changing when you're supposed to change them, let's say in a scheduled maintenance window or something. If it wasn't scheduled, you're going to want to know why a production system had to be changed in the first place. You'll ask yourself questions like, well did the process fail? Did somebody not verify that the system was correct during that last maintenance window? Maybe somebody was making changes to the wrong system. But then again, on the other hand, it could be a system that you're building, in which case it's an expected event because you're going from a state of nothing to a state that you've defined, so it's not as important to you. Identifying whether a change is actually made is called... Idempotence. Now that actually took me some effort to pronounce correctly, and I even had to look it up in the dictionary to make sure I was pronouncing it correctly, but what it basically means is that if the current system state of the server that Ansible is looking at would still be the same after it applied the change, well then the system is idempotent, so it's not even going to try and make that change. Most of the time it's more important to identify the state that you want the system in rather than dictate the process to make that change. I kind of like to think of it as driving directions or when you get into a taxi cab, the state that you define is just getting from point A to point B, you don't really care about the how or the actions or directions to take to get there, you just want to get there, you kind of let the taxi cab driver make those decisions for you, making sure that it's the fastest way possible, the most efficient way possible, and hopefully the cheapest was possible. You're kind of giving those kind of steps to the taxi cab driver, or in this case to Ansible to define how to make those steps and those actions, all you want is the end result, let the process make the determination as to how to do it.
Provisioning
Provisioning is the second function that we talked about when we talked about the different functions of Ansible. Provisioning is really just another idea that's kind of built on top of change management, but it's really focused on a role that you're trying to accomplish. In its most basic definition, provisioning really means that you're transitioning from one system state to another system state that's expected, and is usually used when you're starting out building servers to perform a specific function. For those that are used to using virtual machine technology of cloning, this is similar to that process. The only difference is that Ansible's provisioning process is actually installing and configuring the software each time, rather than just copying that image. Many consider this to be a cleaner method as it removes any possibilities of corrupted files or maybe some server-unique identifiers that came from that original virtual image. It's also easier to make changes to the process, let's say a different version of the software that you want to test, or other changes that are unique to this one instance. If you make those changes to that original virtual image, then everybody is going to get those changes on that next copy. For example, you may want to have a server that has a basic operating system on it and turn it into, let's say an FTP server, or maybe an email server or possibly a database server, or perhaps you just want to create a test development environment so that you can test all your code on and then destroy that server or servers. The steps for provisioning are pretty basic. You first start with a basic operating system, let's say you want to create a, I don't know, let's say a web server. Well, the steps that you want to automate would be first you'd want to install the web server, then secondly after that web server software is installed, you're going to want to copy your configurations over to it, and then any other web files or maybe your test coding over to that server as well. Finally, you're going to want to install your security updates, and then start the service. Those are kind of the step by step instructions that Ansible would send to a server in order to provision it into whatever aspect that you want to turn it into.
Automation
Ansible is an automation tool. If you were to break down Ansible to its most basic components, you'll see automation at the lower layers. It's not really a separate function, but is heavily used in Ansible to do its tasks, ordered operations, and describes what Ansible does. I'm separating it out here to show you that you can use it for your ad hoc tasks. Ansible uses ordered tasks and logic controls to determine what to run and when beyond change management and provisioning, and you can take advantage of that. You can update files, change network devices, even updating your systems from that heart bleed security exploit that we had a couple years ago, and you could have done it with ease. Let's build an example test case. Let's say your boss tells you that you have a major security exploit, and we need to update 100 web servers right away. Oh, and he forgot to tell you, he wants it done in the next hour. While most people would be sweating bullets right about now, but you're cool and collected knowing that Ansible is ready to go. All you really need to do is run the playbook, such as update web servers with the latest security patch, go get yourself a cup of coffee, and sit back down feeling productive and accomplished knowing that it's already been done and you can see the results of that automation run on those servers that were updated. If any servers didn't get updated, well then you can remove them from the load balancer and then identify the cause.
Orchestration
And finally you have orchestration. Orchestration was that fourth and final piece that we talked about the different functions when we were trying to describe what Ansible is. A lot of people get automation and orchestration confused. They are not the same, but they are related. Where automation is used on a single system, orchestration takes the automation and coordinates the process between multiple systems, usually because one system may need to be dependent on another system before it can accomplish its tasks. You cannot have orchestration without automation. For example, you may have a task that has to go to System 1, and then another task that runs on System 2, and then another task that runs on System 3, and then you can go back to system run to run that fourth task, which may have to have Systems 2 and 3 dependent before the fourth task can be done. The way I picture orchestration is with, well, an image of an orchestra. Each instrument has its own tasks, which are musical notes, that need to be done in a certain order, but they do work together with other instruments by direction of a conductor. If we were to superimpose systems from this image, you can see that instead of trumpets, we now have web servers, instead of percussion we now have middleware servers, instead of flutes, our database servers, and controlling all of it, is instead of a conductor, we now have Ansible conducting this symphony of tasks. So Ansible can make sure that our database servers are set up and running before automating the middleware servers since they usually need a database in order for them to start up, and our web servers may need firewalls ports open, or added to a load balancer group before they can go into production. All in all, orchestration is the coordination of automation between systems, whereas automation itself is just the executing of tasks automatically within a single system.
Why Use Ansible?
So now that we know what Ansible is, why would we want to use it? I mean, Ansible isn't the only software package out there that can do the things that it does. Well the first reason is also what makes Ansible so unique. It's clean. It's like this green, ecofriendly server management system out there, the less dependencies it has allows for Ansible to not only be more stable because it has less moving parts, but it also makes it very fast to implement. There are also no agents that need to be installed on your remote systems. An agent is a program that must be installed on the remote machine in order for your system to work with it. Now I'm not saying that all agents are bad, they do have their place, and there are advantages and disadvantages to using them, but for this topic, no agents allows you to get up and running very quickly, which is a huge timesaver. There also isn't a database requirement, making Ansible very portable. You can move it from system to system and not have to worry about setting up a database. You also don't need a DBA to manage performance, indexes or backups, or dealing with software upgrades when you're trying to upgrade or update it. Another reason is for its clean nature, is that after you run your plays or playbooks on your remote systems, the software needed to run those plays is gone, poof, there's nothing else running left on that system unless you declare it to be in your play. When you run your commands and run your modules and they have to deploy stuff, once you're done, those modules and software that was running on it is now gone after it completes its task. And because of lack of other systems' dependencies, upgrading Ansible is super easy. There's no schema updates on a database server or deployments of agent upgrades, you simply upgrade the Ansible code on your control system and you are done.
YAML
So beyond how clean Ansible is, its execution is just as elegant. It uses a format structure called YAML to run its actions. Well what does that mean to you? Well it means that you don't have to learn a programming language or open a graphical interface to drag, drop, and connect actions together. You simply define your actions in a text file, and you run it. So what is YAML? Well I can tell you what it's not. It's not a Markup language. Some people think YAML stands for Yet Another Markup Language, but it's not. A Markup language is where you define the kind of text or the data that you're entering. Well that's not so for YAML. YAML is actually a recursive acronym that stands for YAML Ain't a Markup Language. And for any of you grammar people out there who just cringed at the word ain't, well you'll just have to get used to it, that is its name. So YAML isn't a Markup, it's more of the structure of the formatting of the text than anything else. Probably your hardest challenge in writing YAML is just that you have to be very careful of whitespace, but we'll get into that in other module. Overall, YAML makes it super easy to tell Ansible what you want it to do. If you've never used Ansible before, you can look at a playbook and pretty much get the gist of what it's trying to do without actually knowing what YAML is.
Built-in Security
Nowadays, you really can't work in IT without seeing, implementing or using security. It's now built into anything and everything that we use, and Ansible is no stranger to it either. It uses SSH as its primary means of communication. And now you're thinking, well, that's nice. Well it's more than nice, because chances are all of your Linux and UNIX boxes that you're already maintaining, well they already have it, that's one more thing that you don't have to configure to use. Ansible also understands the need for using Root, and in those systems that can't use Root, I think such as Ubuntu, well then using sudo to run its plays is built in. Fairly recently, Ansible has also created an Ansible vault. You'll see the importance of this in later modules, but for now, what I'll tell you is it allows you to encrypt your sensitive variables such as passwords and other confidential information while still allowing Ansible to use it. So because Ansible uses SSH by default as its primary means of delivering plays, there is really no need for an extensive PKI, you don't need a private key infrastructure or any kind of security infrastructure built it. I'm not saying it can't use it, it certainly can, but when you're starting out with Ansible, it makes it really easy to implement.
Extendable
And my final point on why to use Ansible is that it's extendable. If you're apt to doing more with Ansible for your environment than what's been predefined, it's easy to do. You can use URL calls to do things such as creating a trouble ticket, or maybe to access an existing management system through REST APIs. Or maybe you want Ansible to plug in to an existing custom application. You can use shell commands to launch or execute actions that aren't defined in Ansible. But not just shell commands, any script can be launched from your OS that can be used in Ansible. And if you need to use information from your scripts to return to Ansible to use, well just reformat the return in JSON and you can reuse it in Ansible playbooks for future tasks. And finally, Ansible has created a community-based repository called Ansible-Galaxy that you can use, download, and browse to use other members' playbooks, actions, and roles, saving you time to create them from scratch.
Conclusion
We've covered a pretty large range of topics in this module, stating what Ansible is. We've shown how we can define states to implement change management, and that using change management we can provision new systems to our needs. Automation is also extremely useful to run those ad hoc tasks across numerous systems, such as rolling updates while using orchestration to automate the tasks and dependencies across multiple and different systems. But beyond that, Ansible is also easy to implement as there are few moving parts to set up. It's also to really easy to program using YAML to define our tasks to deploy, and because of SSH and Ansible vault, it's inherently secure. And finally, if the 250 modules that comes with Ansible isn't enough for you, it's easy to extend as you plug in your own custom code, scripts or systems.
Architecture and Process Flow
Architecture Introduction
So before we start using Ansible, it's going to be helpful to know and understand the fundamental architecture underneath everything. Having a good foundation of these Ansible concepts will make you a better automation engineer, and it's going to give you those skills that you need to better integrate Ansible into your existing system environment. By knowing its strengths, and even its limitations, you can better plan for your implementation of Ansible. Sometimes even knowing what the limitations are, you can normally get around it by putting something else in. Ansible has been thoroughly thought out and tested using these architectures and processes. Some of it might be familiar to you, but some of it may not be. Up until now we've been using terms like tasks and actions, so in this module we're going to start using Ansible terms, like plays and playbooks. Let's get started with a look under the hood of Ansible's architecture and how it processes its own execution.
System Requirements
So the most basic process starts by having an Ansible control server talking to a remote system, so obviously there's two parts to this topology. You've got that remote server, which is the server that you want to manage and maintain, and you have the control server. Now the control server is a primary system that deploys all of your plays and playbooks, and it has Ansible installed on it, and it's this control server that you do all of your Ansible work on. Now keep in mind, I keep calling it a server, but it doesn't have to be a server, commonly it's actually your laptop or desktop, especially if you want to do all those ad hoc tasks that you want to, say manage and do a quick update on something that you don't really want to go to a server and do. Now before you can set up and plan for your environment, you first need to know what the minimum requirements are, so we're going to talk about the control server here. First, Ansible is Python-based, and Python is an interpreted programming language, it is required. You must have Python 2. 6 or greater installed on your system in order for Ansible to run correctly. Second, the operating system must be a UNIX or Linux type of environment. This also includes Macs since OS X is built on UNIX. At this point, Windows admin guys are saying well, what about us, you said only UNIX and Linux? Well, that's right. From a control server perspective, Windows isn't supported. You might be able to get Ansible to run on a Windows system with a lot of workarounds and some Python libraries, but it's not supported, nor is it recommended, and chances are you're probably not going to be able to use everything about Ansible on that system. Now that we have the control server requirements identified, well what kinds of remote systems can we manage? Well to be honest, there's actually a lot more than I can list here, there's modules out there that'll allow you to manage switches and firewalls, routers and load balancers, many other systems out there, but in regard to operating systems, there's going to be a few requirements. For a UNIX and Linux type of system, well you need to have a minimum of Python 2. 5 installed. Remember, Ansible is a Python-based system, but you don't have to worry because Python is usually added to any kind of UNIX or Linux system by default, unless you specifically told it not to. And if you have an older system or lots of older systems out there that are still running Python 2. 4, well then you don't have to worry about that either, it will still work, but you do need to install the simple JSON library. And yes, you can have Ansible help you do that as well. And the last requirement for these systems? Well, it does have to have SSH installed and running, and we'll explain why later. But like Python, that's also usually the default and very little you need to do to get it to work. But don't worry Windows guys, from a manage system perspective, Windows is now supported. Ansible has finally released support for Windows operating systems using PowerShell. All you need to do is have remote PowerShell enabled. Now if memory serves me, I'm pretty sure remote PowerShell came with PowerShell 2. 0, so as long as you can have PowerShell 2 or greater, you should be able to enable remote PowerShell. Now those are the basic requirements for the control and the remote servers, but if you're not very familiar with Python, I do need to make you aware of something. We keep saying Python version 2. something or greater, but you do need to know that Python 3. x is not an upgrade to Python 2. x. Now, this remark may get some Python developers a little confused, so let me clarify. Many people do consider Python version 3 to be an upgrade, and that's usually because it's got active development and many other features out there. So from a code perspective, it is considered an upgrade. But what I'm talking about here is from a process standpoint. You can't upgrade Python 2 to Python 3 and still have everything work like normal, and this is because Python 3 doesn't really carry a lot of backward compatibility to the Python 2 code. So what does this mean? Well it means that Python 3. x isn't supported, at least not yet. If you have a system that's running Python 3, don't uninstall it to get it to work, doing so could cause some serious problems in the operations of your server. Instead, later on I'm going to show you in a future module how we can get past that issue.
Components Overview
As we go through this training series, we're going to reference this architecture map. Now there's five basic components of Ansible's processing. We're going to touch briefly on each one of these components here, but we're going to dive into far more depth and detail on these in future modules. First you have the inventory component. Now this is just a text file that describes your servers and your systems. It's here where you get to define your host-level variables and your groups and your roles. You can also define your user accounts used to access these certain systems, you don't always have the same user account for every system out there, so you can actually define it here. And because this file can either be a static text file or it could be also an executable script to get your managed systems, you don't need a database for Ansible. A lot of people consider this like the Ansible hosts file. Modules are the command center of the system. Without modules you really can't perform any kind of work. Each module has a set of actions that you can perform. Now, some modules are built into the core of the product, meaning Ansible developers will fully support them, and they're called core modules. Other modules are considered extras, meaning it comes with Ansible, but it's been developed by other people. And still others are available using what's called Ansible-Galaxy, which is a community-based catalog. So a module is a programmed unit of work to be done. For example, if you wanted to install a software package on a Red Hat system, you'd use the yum module. However, yum won't work on a Debian system, and in those cases you're going to be using the apt module. For each group of actions, you would call the module and tell the module what you'd want it to do. And now there are playbooks. If modules are the command center of Ansible, well, playbooks would be that glue that kind of brings everything together. Playbooks are the files that you're going to create to accomplish a task. In the previous module we talked about YAML and how it's used to configure the execution of our workflow. This is where it's used. Inside of a playbooks are, well, individual plays. As you can see in the diagram, each play is grouped together in a playbook, and this is how we can build our automation and orchestration by defining individual plays to be executed in a predefined order. A play can be as simple as a single task or an action that we want to have executed, such as install an application or copy a file, or starting a service. It can also be a set of tasks that's assigned to a group of system. So if a play contains a task executed on a set of hosts that would make a playbook a grouping of individual plays. Each working together with different systems to orchestrate your automation. As you can see the Ansible team really just adopted the sports terminology to describe its execution. Using a playbook you can install an application, copy over a configuration file and then switch over to your database servers and configure the databases in that order. But playbooks are much more than that. In later modules we're going to show you how you can add logic to your playbooks to identify whether you should even run a play or not, giving it far more power. Things like checking if a drive has enough free space to installing before even running the install. Or maybe you want to check if it has a certain version of an operating system. Next you have the Ansible configuration file. Now this sets up the global configuration variables for Ansible, and allows you to change its defaults. Items like how many parallel operations can your system perform at the same time, or whether you want Ansible to tell you if a host was skipped or not. Those are just a couple of sample things that you can configure here. And because it's a text file, the Ansible team created kind of like an inheritance order, or an order of operations so you can override the default based on the location of this file, which makes it really portable. Maybe you want to have a specific configuration inside of a specific project folder, but you may want to have a different configuration in a different folder somewhere else. And finally, the last component for Ansible, well it's Python. Now Python doesn't really come with Ansible, but I'm showing it here to express to you the flow of the process. Ansible takes all of those components and kind of pushes it through the Python component to build a package to be delivered to the remote systems. Using the Python framework also helps in building variables. Now variables are a really crucial part to making an advanced independent orchestration and automation system. There are three kinds of variables that you can use. Host variables are the first. Now, you define these variables in the inventory file where you define your host, and these are variables used for that host, like maybe defining the sudo account for that system. The second variable type is fact variables. Now these are variables that are created when you gather fact data on the host. Every time you would run a play, it can actually gather, or a playbook rather, it would actually gather fact data or environmental data of that system, like memory usage, IP addresses, operating system names, or even if you want, CPU speeds. You can use those variables inside of your playbooks. The last variable that you have is dynamic variables. Now these are variables that are created during the course of your playbook and then destroyed afterwards. You might create a variable based on the results of a play, and then change that variable later on or just use it in another play.
Process of Execution and Flow
So now that we have the basic requirements out of the way, let's talk about the process that Ansible takes in order to talk to remote server. Now the first thing that it's going to do is evaluate the playbook that you've created. Now this playbook identifies the systems to deploy to, and the modules to grab. Once it identifies and does that evaluation, it packages everything up into a neat little Python package. After it identifies the systems to deploy to, to deploy the package to using the inventory module, it's going to establish an SSH session to the remote system and deploy the package according to the Ansible configuration parameters that you've given, which is really nothing more than just copying that package to a created temp directory on the remote machine. Once it's on the remote system, the Python framework on the remote system will then execute that package, and then identify which steps completed successfully. At the end of the execution, the remote system will return the results back to Ansible using JSON and to report on the status of that execution. And before the SSH connection is terminated, that Python package that was delivered before is now deleted from that temp directory on the remote system, keeping everything really nice and clean. Ansible will then move on to the next play in that playbook. Now, that last discussion that we had was really just for one system to one system, but chances are, you're not going to be working with that system, you're probably going to be working with lots of systems, and as you can see, the playbooks here are targeting different systems at a time. Mail servers, file servers, database servers, whatever system that you want to deploy to, Ansible can deploy to at the same time. Some plays may be dependent on others like provisioning, while other plays could be standalone like maybe a system update. So here this animation is really showing that some systems are failing and some aren't. Well maybe the updates that you're pushing out have already been done on some of them, which means it's going to fail. Maybe the changes that you're requesting aren't really needed because of certain logic that you've put into the system. But regardless on whether it completed or not, you can here see that Ansible can be used to target multiple machines at the exact same time.
Execution Types
When executing your playbooks, it's important to note that there's two execution types. Now so far we've been showing the default execution type of remote. This is the most common, and it means that Ansible is executing that package on the remote system. The package is deployed to that remote system and then executed on that remote system, but the other execution type is local. Now this can be really handy at times. This means that the local Ansible server is actually executing the package, not the remote system that you're targeting. So let's look at that a little bit more in detail, but first let's review what a remote system looks like. So here's the process that we've already seen. A Python package is delivered to the remote system, it's executed there, and the results are returned. So let's look at what the difference is with local execution. So local execution is used when you need the Ansible control server to execute a task because the system that you're targeting, well, it can't receive a Python module. The most common reason for this is if you need to perform, let's say a web service call or a RESTful call. Here you can see that the control server is actually executing the package locally, in this case it's a URI module, and then making an HTTP request remotely. It determines whether that play was successful or not based on those response codes that it got. Now, keep in mind that even though it's more uncommon in web APIs, well that's not the only time to use it. You can use it to call other systems as well like configuring network equipment like Juniper or Arista, or maybe you want to do an IRC call. We'll go into more further detail as to how to use this in playbook module later on further down this course.
Architecture Conclusion
As we wrap up this module on Ansible architecture, there's a few things that we should review. First, the inventory component is what maps our host to Ansible. This component really cares about the host's environment, and this is where we define our remote systems, grouping them together in categories, and we can also identify any variables that those specific hosts may need. We also talked about the configuration component. Now this is where we can override the default configurations of Ansible to kind of shape it to our needs. What's super cool about this component is that it has that order of operations inheritance, which means that you can override the configurations based on the location of this file. The modules component is where actions are built and where they're identified. Each module is built kind of with a specific goal in mind, and you would call those modules to execute the actions. Playbooks will also tie everything together and identify your specific processes that you want to have completed, and this is where you would use YAML to build your executions. And once the playbook has been parsed and Ansible now knows what to do and where to do it, it uses Python to build the package to be executed. And finally, Ansible will then create an SSH connection to the remote host to deliver that playbook package, execute it, and receive the results of that execution. The final point to remember as we leave is that there's two execution types. Now you can execute the package remotely, which is the most common and is the default if you don't define it otherwise, but the other option is to run the playbook locally on the control server itself, and this is usually needed when the remote system can't execute that Python package, and that's where you can kind of consume either notification methods or consume web service calls.
Creating Environment
Introduction
Throughout the rest of this course, we're now going to start running various Ansible plays against our test lab servers. From here on out we're actually going to get our hands dirty in doing things with Ansible, and if you're like me, this is what you've been waiting on. In this module, we're actually going to be creating a test lab environment, and by the end of this module, we'll be running some Ansible commands against those systems to not only make sure that our environment is working, but also go over some of those basic commands. Now I do realize that some of you guys may already have test labs ready to go, and if you do, that's great, you may want to skip the Vagrant and VirtualBox lessons and go straight to the Ansible install. But if you want to be able to follow along and do many of our examples, I encourage you to view our set up of our test environment. First let's identify the components of our test lab. We're going to be installing Vagrant to help us with getting our server started up and running, we're going to be installing VirtualBox so that they have an environment to run in, and then finally we're going to be installing Ansible. So let's see what each of these brings to the table. Vagrant is our system controller, it's going to be used to download and provision our servers so that we don't have to. It's going to define our host names and our IP addresses and the user accounts that we can use, leaving more time for us to be able to do actual Ansible work. VirtualBox is going to be our VM hypervisor that the servers are going to be running in. Now you may be asking, well why don't I show VMware Fusion or Parallels or some other hypervisor out there? Well I'm trying to find and use a system that's going to be able to be run for everyone, and since VirtualBox can run on Windows, Linux, and Mac, I thought this would be a lot easier and would mean a faster course since I don't have to go over all the other different configurations for the other hypervisors. Now, this doesn't meant that VirtualBox is better than others, it just means that it's a common denominator for all the OSs out there that you may be on. And finally we have Ansible. Well there's not much good to have an Ansible course if we don't actually install it, now would it? So we're going to install Ansible, and then finally run the test systems with a few basic commands.
Installing Vagrant and VirtualBox
So now we want to install Vagrant. Now Vagrant's going to be our system controller, it's actually going to be the one that actually downloads all of our system images so that we can spin up our servers very quickly. So let's go ahead and go to Vagrant's website, download the software, and install it. Okay, what we're going to do is we're going to open up our browser and bring it over, and then I'm going to go to www. vagrantup. com. This is going to load up the Vagrant website, and the first thing you're going to want to do is go to the Downloads section. Now download the version that you're actually running on. I'm running on Mac OS X, so I'm going to be downloading this image right here. But if you're running Windows, Debian or maybe CentOS, or any kind of Fedora system, you can download the RPM and the DEB versions as well. I'm going to download the Mac OS X version. (Downloading) Okay, the download is completed. We're going to click on our download and open it, and we're going to execute the image. Now for the Mac OS X it's going to be opening up a. dmg image, which looks like this. Now depending on which operating system you're on, whether it's Windows or Debian, or maybe Fedora, you can just install the package that you downloaded. So the package that I downloaded here is called Vagrant. pkg. We're going to open that up, and then we're going to go through the installation steps. It's very simple, we do not have to configure it, we just click on Continue, it's going to take up that much space, I'm going to say Install. I'm going to put in my login password, (Installing) and now it's installed. That was how easy it is to install Vagrant. Now that we've installed Vagrant, which is our virtual machine controller, now we need to install our hypervisor or our virtual machine environment. We're going to do this using VirtualBox, so let's go to our demo and download and install VirtualBox. Okay, so I've got my browser open and I'm going to go to the address bar, and we're going to choose virtualbox. org. On the left side you're going to see Downloads, and then you can choose the download binary that you're going to want for the operating system you're on. So since I am on OS X, I'm going to click on this binary. If you have Linux or Windows, choose the appropriate one for you. (Downloading) Okay, now that VirtualBox is downloaded, I'm going to open it up, and this is what it looks like. So from here I'm just going to double-click on the VirtualBox package and run it, choose whichever package that you've downloaded for your operating system. Now it's going to determine whether it can be installed, at least for the OS X version, and I just take all the defaults like normal, put in my password, and now it's installed. Now before we move on and actually install Ansible, the first thing that we're going to want to do is set up our environment, because I'm going to want to install Ansible on one of our virtual machines. Now do I have to install Ansible on our virtual machines? No, I can install it on my OS X version, but depending on what operating system you're on, I wanted to make something a little bit more fluid and easy to see, so we're actually going to be installing Ansible on one of our systems that we're going to spin up.
Configure Vagrant
Okay, now that we have Vagrant and VirtualBox installed, let's go ahead and start setting up our environment. Let me move over to my terminal here, and I'm going to be working from Terminal. If you're on Linux or Mac you can use Terminal or any kind of other command line that you have, I'm using iTerm here. If you're using Windows, just open up the command prompt. From this terminal the first thing that I want to do is I want to make sure that Vagrant and VirtualBox installed correctly. So I'm going to go ahead and type in the command vagrant. Now if it didn't install correctly or if that command is not available, it's going to tell me. And there we go, we've got all of our options here. Let me go ahead and expand this window out just a little bit so you can see more of the data, and I'm going to clear this, and now I want to check to see if VirtualBox installed, that command, when VirtualBox is installed correctly, the command is put in as vboxmanage, and that command is available as well. So now we have Vagrant and VirtualBox installed and ready to go. The first thing I want to do is let's create a directory that we're going to be doing all of our environmental stuff test data in. So I'm going to create directory, you can create it whatever you want, I'm going to create it as hands-on-ansible. Now that I have this directory let's go ahead and change directory into it. Now if I do a directory listing right now, you can see I don't have anything in there, and the first thing that I'm going to want to do is I want to create a Vagrantfile so that it can download and start up and set up all of our virtual machines. That command that you're going to want to use is vagrant init, and what that's going to do is it's going to use this directory to initialize a Vagrantfile. When I do that, basically the only thing it does is it puts a Vagrantfile in there that's kind of preconfigured. But we don't want to use the default configurations, we're going to want to edit this file to use it to our needs. So I'm going to use Sublime, you can use whatever text editor you'd like, maybe it's Notepad, WordPad, TextWrangler, any kind of text editor that you want to use. Let's go ahead and open up that Vagrantfile, and it opened up in my other window, let me move it over. Okay, so there's our Vagrantfile. Now the first thing that you're going to want to look at is you notice it's written in Ruby. Now you don't have to know Ruby to be able to configure this, you can just follow along, but what it's saying is that it's using version 2 of Vagrant. configure to run or do the object called config, and this object down here is where it's actually running. It's kind of like a for loop or a do loop. So what we want to do is we want to go ahead and wipe out everything down here, all that commented out options to try and keep things nice and clean, so then we have a configure and an end, that's all you should have. Now we want to configure this to use it to our needs, so in our environment we're going to set up three servers. We're going to set up an ACS server, or an Ansible Control Server, we're going to set up a pretend web server, and we're going to set up, well it's not really pretend because it's actually running, a web server and also a database server. So let's go ahead and set up that configuration now. Now because I'm actually using the config variable up here as the object, I'm going to go ahead and start my options with config, and I'm going to define, I'm going to say vm. define, and I'm going to create a sub-configuration, we're going to call this acs and we're going to say do acs. And you can see that with the acs I put it in pipe characters. And when I hit Enter here it's going to, well, Sublime will automatically do an indentation because I'm now configuring inside of this object. And because I'm calling it acs in the pipe commands, all of my commands from here on out inside this object needs to start with acs. So I'm going to say acs. vm. box. Now this is the image, or the box that we're wanting to configure. So we're going to say =, and we want to use the box image of ubuntu/trusty64. So I want to use Ubuntu to run this server. And at this point you're basically saying oh Aaron, where did you get that name, how did you know that that's what the name of that image is, did you just happen to know it, is there a pattern? Well let me tell you how I got that. Let's open up a browser, I'm going to move it to my screen here, and we're going to go to the website of www. vagrantcloud. com, and what this is going to do is open up an online repository of all the different Vagrant boxes that you can download, it's a very large list. If we scroll down in say Discover, we can do a search for say centos, and it's going to show us all the different images that have the name CentOS in it, and you can see here that we have several images of different versions that have chef installed, maybe with puppetlabs installed, one that's used for parallels, but if I do a search for ubuntu, if I were to save it correctly, this is the image that I'm downloading, this ubuntu/trusty64. You can see that it's Ubuntu Server 14. 04, or another name for it, Trusty Tahr. So that is what we're going to be downloading and installing for this image. Okay, so now that we have that defined and we've identified how we got that name, let's go ahead and continue on with our configurations. We want to go ahead and set up the host name inside this server as well, so we're going to say acs. vm. hostname = acs. Now this is actually the host name inside the operating system, when the operating system boots up, Vagrant is going to reconfigure the host name inside of that Linux box. The last thing that we want to do is we want to configure the network for it, because we're going to be attaching to this server through an IP address, so let's go ahead and automatically create a network, we're going to say acs. vm. network, we're going to identify the private network object, and we're going to give it an IP address of 192. 168. 33. 10. Now that we're done with this configuration we're going to say end because we're now done with this object configuration, but we still have two more servers to configure, so let's start with that again. We're going to say config, because that's the name of the top-level object that we're setting up, and we're going to say vm and we're going to define another system, this one we're going to call web, and we're going to say do web and put web inside the pipe commands. Now inside this object everything that we start with is going to say web because that's what we put into the do object at the top. Now we're going to say web. vm. box=. Now this one, let's be a little bit different, let's go ahead and make this as a CentOS box, so we're going to say nrel/CentOS, if I were to spell this correctly, Cent capital O-S, -6. 5-x86_64. Again, if you don't know where I got that name, just go to vagrantcloud. com and pull up all the image names that you want from there. So we're also going to set the host name for this, and we're going to call it web, and we're going to set up the network, woops, I said acs, I need to say web. vm. network, and we're going to identify the private network with the IP address of 192. 168. 33. 20 this time. Now keep in mind, this is a web server, we may want to test out whether we can actually see web pages or not, and if I have multiple web servers running inside of a virtual machine, if I go to port 80, well, half of them or most of them, all but 1, won't be able to start up because that address is already in use or that port is already in use, so we want to do some port forwarding here. So we're going to say web. vm. network again, but this time we're going to identify the forwarded_port, and the guest port is going to be 80, and the host port is going to be 8080. So whenever I access port 8080 on my machine, it's going to be forwarded to port 80 inside of this host. And because we're done, we're going to say end. Now we still have one more virtual machine, let's say this web server needs to be able to talk to a database server, so we need to create a database server. So we're going to say config. vm. define one more time, and this time we're going to define our db server. So we'll say db. vm. box =, we'll make it CentOS again, we'll make it kind of standard. Again, it doesn't matter whether I use CentOS or Linux, use whatever images that you want to be able to use inside of your environment, Ubuntu, Debian, CentOS, Red Hat, Fedora, whatever you want to use. Now inside of our environment I'm choosing to use these, but there's really no reason why I'm choosing them. (Typing) And we're going to go ahead and give it the host name, and we're going to set up a network IP, private_network, ip, and this time we'll give it 192. 168. 33. 30. Now because I'm probably not going to be accessing this database from my machine, I don't have to worry about a forwarded port, all we would need to make sure of is that the web server can talk to this database server within those two private IPs that I gave it. And at the end we're going to finish off with the end. Now let's go ahead and save our file, and now we have our Vagrantfile completed and ready to go. The next thing we need to do is we need to start everything up. So when I do a startup here, what's going to happen is that it's going to look for those images if I have it inside my repository, as if I've already started this before and I already have that image available from pervious runs. Now if you don't have that image available from previous runs, if this is the first time you're ever running it, it's going to take some time and a few minutes so that they can go out to vagrantcloud. com and download those images before it starts. So to be able to start it up we're going to say vagrant up, and what that's going to do is it's going to bring up all of those virtual machines that we've defined inside of that Vagrantfile. (Loading) Okay, so that command has already ran, it's already started up and running all of our virtual machines, let's double check that. What we're going to do is we're now going to say the vboxmanage command, and this item we're going to want to say list, and we're going to say runningvms, we want to see to what's running in our VirtualBox right now. And you can see here that it is running our hands-on-ansible command for acs, for the web server, and for the database server. So so far what we've accomplished is that we've basically built our Vagrantfile, we configured and identified three different virtual machines, we ran vagrant up, and now it automatically configured and started those virtual machines for us to use.
Installing Ansible
Now that we've got everything running and we've verified that they are running correctly, now let's go ahead and get into our acs server so that we can do our final install of Ansible. So what we're going to do next is our next command is going to say vagrant ssh, and we're going to go into acs server. Because we called it acs, all I'm doing is just typing in vagrant ssh acs, and when I run that command we should have a secure shell into that system, and here we are. So the first thing that we're going to want to do is, well, let's install Ansible. Okay, so when you're installing Ansible on a Debian system, it's a really simple command. All you want to do is you're going to want to say sudo, because we're actually installed as a regular user, we're going to use sudo, and we're going to say apt-get install ansible. We do want to continue to install all those dependent packages, and now we have Ansible installed and running. Well I wouldn't say it's running, but it is installed. We can verify that by typing in just the command ansible, and we have all of our options available to us. So now you may be asking yourself well that's great, that tells me how to install Ansible on an Ubuntu or a Debian box, but what if my box is a CentOS box or a Red Hat Enterprise Linux box, what do I do with that? Well let's go ahead and install it on a CentOS box so that you can see what it looks like. We're going to exit out of our Ubuntu box. This time we're going to go ahead and remote into our web server because it's a CentOS box. We're going to say vagrant ssh, and go into our web box. Now once we're logged in, the first thing that you're going to want to do is you're going to want to install the enterprise package, or enterprise release package for CentOS. Now that's actually really simple, you're just going to say sudo yum install epel-release. And what this is going to do is it's going to install that repository that has all of the Enterprise Linux systems or software on it. We're going to say yes, it's okay to install. And now that extra repository has been installed, now we can install Ansible by saying sudo yum install ansible. We want to go ahead and accept all the defaults, or all the dependencies, and it's going to install all those dependencies for Ansible, and then install Ansible on top of that. And now Ansible has been installed. We can verify that by typing in the command ansible, and there's all of our options there. So up until now, we've actually installed Ansible on both a Debian or an Ubuntu box, and we've installed it on a CentOS box or a Red Hat Enterprise box, but it doesn't make a lot of sense for me to install every type of operating system out there and show you how to install Ansible that way. Now one way that you can do it is take a look at your repositories based on the operating system you have, whether it's a Macintosh or whether it's FreeBSD or Gentoo or any other distribution out there, but you can also go to Ansible's website and go to their download and install directory and view how to install it on those systems. Now what I'd like to do right now is show you how to install Ansible by compiling the Python code. So at this point what we're going to do is we're going to exit out of our web server, and right now Ansible is installed on both our acs server and our web server, so I'm going to go to that db server, and I'm going to install Ansible, but I'm going to compile it, compile the code so I don't have to use a repository if you run into a system where you can't get it or you can't find it in your repository. So I'm going to do a vagrant ssh and go into our db server. And now I'm accessing our db server. The first thing that I'm going to want to do is I want to install, or at least make sure that the GCC, or the GNU compiler is installed. Most of the time this is installed with your system, but I'm going to go ahead and make sure. Now in CentOS I'm going to be doing sudo yum install, and we're just going to say gcc, but use whatever repository you have on your operating system. And it looks like I do have it installed, but it does want to do an update, so I'm going to go ahead and say yeah, let's go ahead and update it. Okay, so we have GCC installed, and that's basically what we're going to be using the compile all the C code in order for Python to work, because Python is built into its own programming language, but it does have a lot of C libraries to make it a little bit faster. Now what we want to do is we want to install the Python setuptools, because this is where we're going to be using the Python index packages to go out and download Ansible. So the first thing we want to do is we're going to say sudo yum install python-setuptools, and this is going to install the setuptools for us to be able to download and install Ansible's source code. So I've installed and found the dependencies, there weren't really any dependencies there, and then installed the package. Now what we want to do is we want to install the Python index packager, or this is basically the repository that it's going to go out and get. So we're going to say sudo easy_install. Now the easy_install command came from the python-setuptools. Easy_install, and we're going to say pip. Now it installed the pip egg, which is another, kind of like a little object inside of Python for it to know what to do and how to do it and install. So now that we've done that, the last thing that we want to do is we want to install the Python development library, and this is where the headers are actually going to be downloaded, the source code headers to be downloaded. So we're going to say sudo yum install python-devel. Now again remember, I'm using yum, but you may want to use apt-get or ports if you're on FreeBSD or anything else that has these packages available. And now the development libraries have been installed. The last thing that we're going to be doing is, well, now we want to install Ansible. So now we're going to say sudo pip install ansible, and this is going to go out to the Python index packager, it's going to download the source code for Ansible, and then compile it. And now we have Ansible installed on this system. We can verify that by typing in, what command? That's right, ansible, and now we have that command available to us to be used.
Testing Lab with Your First Ansible Commands
Now that we have Ansible installed on our acs server, and we have two available systems, let's start using Ansible. And you guys are probably thinking, finally, after all of those fundamentals, after all those processes and components and understanding what Ansible does, we finally get to use it. So what we're going to want to do is we're in the database server now, we're going to want to exit out of that database server and we're going to go into our acs server. That's because all of our Ansible work is going to be done on our Ansible control system. So we're going to say vagrant ssh acs, and now we're going to be connected into our acs system. Now to try and keep things segregated, what I'm going to do is I'm going to create a directory, and I'm going to call this exercise1. Now you can see I've just created the directory, there's nothing in there. If you guys remember, back in a previous module we identified the inventory component. If Ansible does not have an inventory file, it has no idea what kind of systems that it can manage. So what we want to do is we want to create a really basic inventory file as we run our test commands. Now we're going to be talking about the inventory component in later module and go into all the details about it, but we just want to create a really basic one identifying our two systems. So we're going to say inventory, that's the name of the file that we're going to be calling it, so we're going to put in our IP address as 192. 168. 33. 20 and 33. 30, and we're going to save that. So we just called the inventory file inventory, creatively enough, nobody could ever say that I was a creative kind of guy, very logical. So when we look at that inventory file we have the inventory. If I just take a look at it, it's got our two IP addresses on there, really basic stuff. So the command that we want to run is going to be a ping command. Now ping is really basic, all it is it's just a program or a module that when it executes, it just returns a value called pong. That's it, it just returns a value. It doesn't do anything on your remote systems, it's not going to harm them or do anything about it, it just executes and returns a value back. So that's what we want to do. So our command is going to be ansible, we're using the ansible binary here. And we want to tell Ansible what system we want. Now we could say all, and that kind of tells Ansible to run it on all those systems in the inventory file, we could also just put a star there, but what I want to do is I just want to talk to one system, and that's going to be the 33. 20 system. Now we have to tell Ansible where that inventory file is, which inventory file do we want to use. We could have multiple inventory files if you want if that's how you want to scale it out. We're just going to say -i as the option or parameter for the inventory file, and we're going to say the inventory file. Now we have to have a user to log in to. Remember, we're using SSH to log in to that remote system, so we have to have a user. So we're going to say the user of vagrant, we're going to say the module is going to be the ping module. Lots of modules out there, but we're just going to choose ping, and then we're going to do a -k. Now what a -k does is it basically prompts you interactively for the password, because I don't have the password saved anywhere, so I need to be able to pipe it in somehow. Now, this is the very first time we're connecting to this system, and if you're not very familiar with SSH, I think you kind of get an idea of what could happen. Let's try. Now I know for a fact that the username is vagrant and the password is vagrant, because, well, we're using Vagrant images and that's been built in. So I'm going to type in the password of vagrant, and there we go. So if you're not very familiar with SSH, this is kind of what it's saying. Well, what it does say is, using an SSH password instead of a key is impossible because Host Key checking is enabled. Now Host Key checking is enabled by default. So we're going to talk about the Host Key checking value when we go into the configuration module. Now for right now we need to get around this, so how do we connect to it? Well, let's talk about SSH for a second. If you're not very familiar with SSH, basically what it does is it has to connect to a remote system, and if I've never connected to that remote system before, it doesn't really know if that really is the system that I want to talk to. Think of it this way, let's say my boss tells me hey Aaron, you need to go down to room 409 and ask for the project file from John. Now I don't know who John is, but I know where he's supposed to be, so I go down to room 409, knock on the door, and there's a gentleman there that says his name is John. Now, is he John? Well I don't know. He says his name is John, I'm just going to have to take his word for it. Now I could ask John for his credentials to prove that he's John, like a driver's license or a passport or some other method to authenticate him, just like with SSH I can verify that they are who they are say they say they are using, say, SSH keys. But right now, all I really need to do is just talk to him. So John tells me, my name is John, I'm going to just have to take his word for it. It's kind of the same thing with SSH. I'm going to connect into this system and I just have to take their word for it that that is who they are. So what we need to do is we need to create an SSH fingerprint in our known_hosts. In order to do that, well, we just need to establish an SSH connection. So I want to say ssh, I'm going to use my vagrant user, and I'm going to want to talk to 192. 168. 33. 20. Now here's where it's actually identifying who that system is. It's saying hey, that system's fingerprint is cb:8e:2e:e9, blah, blah, blah, blah. So what we have to do is we have to just accept their word for it and say yes, we want to continue. Now I'm just going to go ahead and cancel out of this because all that did was just add their fingerprint to our known_hosts file. If I take a look at that known_hosts file, that's going to be in the local home directory called. ssh. I need to go to my home directory and look at. ssh. You can see I have that known_hosts file there. If I take a look at that known_hosts file, there's my directory, this is system 1, so this proves that that system is who it is, at least as of right now. So let's go ahead and run that command again. Remember, we're going to type in ansible, the 33. 20 address, which is our web server, -i for the inventory file, -u for the vagrant user, ping is the module, and we want it to prompt us for a password. Now if we type in our password, it's going to SSH in to that system, and because I have that system in my known_hosts file, it goes ahead and connects to it. Now you can see here that it returns a value of pong. I sent a value of ping and it returns a value of pong. Now this is a successful connection. That tells me that that system is actually responding back. Now what we want to do is we want to do the same thing for 33. 30, that's the database server. So we're going to want to ssh into it so we can add that system to our known_hosts file, yes we do, but now I can just cancel out because it just already added it to the known_hosts. Now what we want to do is let's do it for both, let's say ansible, we're going to say all systems. We only have two IP addresses in there, so it's not a huge amount. Our inventory file is going to be called inventory. Our username is going to vagrant, we're going to use the module ping, and we want it to prompt for a password. Type in our password, now both systems come back. So we've already validated and verified that Ansible can talk to those two remote systems. Now if you're like me, you kind of want to verify everything's working the way it's supposed to be working. I mean, this does show that it's supposed to be working and we do get a value back, but sometimes it's just nice to verify what's all going on, and you would use this using verbose mode, or what most people just call debug mode. So if we were to run that command again, and we're only going to do it on one system because we don't want to get a whole lot of information, and we're going to say our inventory file, we're going to say our username, we're going to do our module, we're going to do our password. Now here's where we're going to go into debug mode. You could just do -v, and that's going to be verbose mode or debug mode. Now just doing a -v is going to be a level 1 debugging. Now it depends on your module, some modules may have level 1 debugging turned on or not. What I'm going to do is we're going to just do one, but you can see here that ping doesn't have level 1 debugging, so let's go ahead and do a little bit more deeper debugging. So now I want to say -vv, now we're doing double v debugging. We're going to run it again, well, now we got a little bit more information, not a huge amount, it basically just tells us that it's compiling the remote module called ping. Well I want to see more than that, let's go ahead and prove the connections that it's actually trying to do. So I'm going to do a vvv, or a triple v. Type in my password, there we go, now I've got a lot more information here. So let's step through this debugging information. The first thing it's going to do is it's going to establish that connection through SSH. Remember, that's what we talked about when we talked about the process that Ansible uses. Here is where it's compiling that ping module, and this is really just a Python module that it compiles to make it executable. The next thing it does is on this system it's going to run this command, which is making the directory, and that's the directory name it's making. The next thing that it does is it's going to change the mode to make everything inside of this directory executable. And the final thing that it does is it's going to echo back that directory path back to Ansible. Now Ansible knows what the directory name is that it created. Now it's going to put that module that it compiled into that directory that it already created. And the last step it's going to do is it's going to right here, it's going to run this command, python, it's going to use the python binary, and it's going to execute that ping module that we put on the remote machine, and then obviously it removes that module after that. So here's where we can actually identify all the different processes that Ansible is doing to run that mode, or that module. And this is really helpful if you're trying to debug modules. If something just doesn't seem to be working right, you can go into debug mode and see all the different things that it's trying to do. Now that we're talking about the ansible binary and running these ad hoc commands, let's talk about running ad hoc commands. Now Ansible is really useful at running ad hoc commands. If you're just doing it for one system, okay, probably not a big enough deal for you to be doing it, maybe one or two systems, using Ansible may or may not be important to you, but if you're doing 10, 20, 100, 1000 machines, let's say you just want to do a massive update to all your machines all at once, well that's where an ad hoc command can come in really handy. What we can do is we can just say ansible, we can say all systems, we're going to use our inventory file, which is really just two machines, we're going to use our username of vagrant, but now the module that we want to use, we want to use the command module. Now the command module is really an executable basically allowing you to run any kind of command on the remote system. Now if we're going to use a command module, we need to give it some kind of options, right? Because we need to be able to tell Ansible, well, what command do we want to run? So we're going to say a for the option a to be able to allow us to put parameters in to the command module. And let's say we want to do sbin, I don't know, let's say reboot. Now we can actually reboot all of our production machines all at the same time, that's not a bad idea, is it? Well I hope you don't do that, but maybe, this would be a really handy command if maybe let's say you're on a holiday and you want to shut down your lab and you can just run down your shutdown command, just halt the system if you want. You could do maybe an update, you can do sbin, I think it's under sbin, maybe it's under usr/sbin, we can say yum update, and then do a -y. So we can say yes to all the options and just do a massive update to all these systems all at once. So you can kind of see how running ad hoc commands could be really useful if you want to do a massive update or a shutdown or a reboot, or whatever it is that you want to run. Any kind of command that's on those remote systems you can run through these ad hoc commands. Now one thing I wanted to tell you is the command module is the default, so you don't have to type in the -m command, you can really just say -a and then type in your command, sbin/reboot. So as a default, the command module is the default, so we can actually just do a -a and completely remove the -m command. Now if you're looking through the modules, and we're going to talk more about modules in a future module, if that makes any sense, in a future training module, we're going to be talking about modules for Ansible in far more detail, but there's one thing that I wanted to show you is, if you're looking through the different modules in Ansible, you might want to know the difference between what's called the command module and this shell module. Both of them run shell commands, so why would we have two different systems? Well the command module, what it's doing is it's running an executable inside of Python. Now the shell module actually runs it inside of a shell. The biggest difference for that is you can use shell variables inside of the shell module. If you're just using the command module, you're not going to be able to use shell variables, let's say the $home or $path, or any other kind of environmental variables that you have. You can do that inside this shell module. So most of the time your work is probably going to be done in the command module, but if you need something a little bit more extensible and be able to use more of a shell environment, you may want to use the shell command.
Conclusion
So as we wrap up this module on installing and running basic Ansible commands, there's a few things that we want you to take away from. First let's just do a really quick review as to what we've done. We used vagrant as our environment controller. It's downloading and preconfiguring these virtual machines, ready to go for us when we need them. Well if there are virtual machines, well they have to run somewhere, and that's where we have the VirtualBox running our virtual machines in. Now I could have chosen all kinds of other different kinds of hypervisors, but I wanted to keep something that was fairly common, a common denominator across all the different operating systems that you may have, and VirtualBox is free and works really well. And finally, we really can't have an Ansible course without running a few Ansible commands, which we did at the very end to verify that our installations were working as they should. So at the end of the day, this is what our lab looks like. We have our Ansible control server, and it's talking to our web server and database server. So when we're installing Ansible, we showed it to you three different ways. Well, the first way was on a Debian-based system like Ubuntu, and that was certainly the easiest. It was just a single one-liner, sudo apt-get install ansible. After a few seconds it will download it, install it, and you're ready to go. We also showed you how to use it on a CentOS system. Now a CentOS system is fairly common because it's the free form of Red Hat Enterprise, and it's just basically two liners. The one installs the repository that the Ansible installer resides in, and then the second one has the Ansible install. So once you install the repository, CentOS knows where to check and then will download Ansible. And finally, the third option was for all those other systems that you may not have a package manager that understands what Ansible is, or maybe there's not an Ansible in any of your repositories, well you can install it from the source code. First you need to install, or at least verify that you have the libraries, like gcc or python-devel, then install the python-setuptools. Once you have the setuptools for Python, you can install Ansible from the source code. And finally, we showed you how to use the basic Ansible command, mostly just to get familiar with it and to make sure that our lab environment was working. And we used the binary command ansible to do that. With using the ansible command we'd used the system to target against, we used a -i to identify what our inventory file is so that Ansible knows where that system is, or at least look at up, the -m identified the modules that we're using, most of the time it was ping, but we used a shell module, -u for the username because, well, we are SSH-ing into that box and so we have to use it as a user, and then the -k will prompt us for that password. And while it's not critical, it's really useful to learn or get familiar with the debug modules, or the debug commands like logging, or the verbose commands, which is why it's v. Dash v is your level 1, -vv is your level 2, -vvv is your level 3, and so on.
Ansible Inventory and Configuration
Introduction to Inventory and Configuration
Now that we have our environment and tested that Ansible is in fact working successfully, it's time to do some deep dives into Ansible's core framework. Now in this module we're going to be discussing the inventory component, as well as Ansible's configuration. We're going to be showing you some of the basic and advanced features that they provide. Now if you think that the inventory element is merely just a lookup file for Ansible, well then I'm going to encourage you to watch this module as we learn some of the cool aspects that this file can bring to your Ansible automation. We're also going to be showing you how to describe your environment using systems, groups, and then also assigning them variables. We're going to end with configuring Ansible for your environment.
Inventory Fundamentals
As we've already discussed, the inventory component is what feeds your plays. Ansible needs this lookup process to be able to learn about the systems that it's going to be talking to, but it's much more than just a simple lookup table, it actually defines and describes your environment the way you see it, because everybody's environment is different. Now, the inventory file can be located anywhere on the file system, so long as the path is specified when you're calling your play. Now we're going to be talking a little bit more about this down the line, but it's really useful to know that you may have really large teams, let's say you have a team for test and a team for development, well you can actually have two separate inventory files for your test and development teams, you don't have to create one big long inventory file to be able to maintain all of it. There's many features that we can take advantage of inside the inventory file, and among these features that we're going to be discussing are behavior parameters. Now these are configurations that we can apply to a system, or even a group of systems. Things like SSH users and passwords, or private key files, or maybe even the path to a Python 2. 6 interpreter on those Python 3-based systems. We can also group our systems together, things like test database servers or production web servers. And if we can group our systems together, well, we can also create groups of groups. Things like datacenter-west or datacenter-east, or maybe all production servers or all test servers. These are our super, or our parent groups, that we can attach subgroups to. We can also assign variables to our systems and groups. For example, you might want to set up a variable called ntp server, or maybe even a syslog server and apply it to an entire datacenter. You may not want system and datacenter 1 to be able to send syslog to a syslog server in datacenter 2, you may want to keep it local to them. So you'll create a variable, and then attach it to that group so that you don't have to write it out 100 times for 100 servers. And in large environments, editing a super long text file can be, well not only tedious, but also a bit overwhelming, so I'm going to show you how you can scale out your inventory definitions to using multiple files for a lot easier maintenance. And finally, your inventory file can either be static or dynamic. Now as I've mentioned before, this course is going to be talking about a static inventory file. However, if you have a system that defines all of your other systems, you may want to make this file executable, which will tell Ansible that it's a script and it will execute that file, and whatever that data that the script returns, Ansible can use that to interact with its systems. Now, let's take a look at an example inventory file and pick out some of the basics. Okay, so here's an inventory file, but there's really nothing new here. We have a group called db with 2 servers, db1 and db2. Well, you can change the behavior of Ansible on a per-host basis. Per-host behavior parameters are done in-line with each system definition, like so. Here, we're defining the parameter ssh_user and ssh_pass, or password, for the system db1. Now this means that db2 is still going to be using the default login. Now looky here. I've actually added an additional parameter for db2. What we're doing here is we're telling Ansible that for db2, and for only db2, we want it to use the Python interpreter under the /usr/bin/python executable. And why is this important? Well, this is the parameter that you're going to want to use if you have a Python 3-based system such as Arch Linux. If you install Python 2 on those systems, you can tell Ansible where that binary is. And here, I've created another group called datacenter-west, but I've added a keyword at the end of it, the children keyword, and with a group name called db underneath it. If I had removed that and I just called it datacenter-west, well then Ansible is going to be considering datacenter-west to be a group and db to be a system. So, it's actually going to try and resolve the name db and it's going to say hey, failure, because it can't look up the db system. But if we add that :children keyword to it, then that's going to tell Ansible that this is a parent group, and whatever is listed below that are actual groups that it can look up. So if I say datacenter-west, it's actually going to be using systems db1 and db2 because it looked up the db group. And finally, we can create variables based on a group or system, and here's where it gets really useful, you may want to have an NTP server for the entire datacenter-west environment. Well, rather than trying to type in npt-server = IP address whatever 100 times for each host, you can put all those hosts into a group and then assign that variable to the entire group. And now on into our demo. Now here we're actually going to be creating an inventory file, but we're going to get a little bit more detailed with it. We're going to add some behavior parameters to it, we're going to create some host-based variables, creating a group, and then assigning some variables to those groups.
Demo: Inventory Basics
Okay, we're in our demo environment right now, and the first thing I want to do is I want to make sure that everything is up and running the way it should be, that's just going to be vagrant up. If you were following along in a previous module, you already have your Vagrant environment running, if not and you're using your own environment, you're going to want to make that those are running. My Vagrant environment is running, but if you're using a VirtualBox environment, you would just type in vboxmanage list runningvms, and that will also tell you if everything is running. For me it is, so I'm going to log in to our acs box, because well, everything is going to be using the Ansible control system, so vagrant ssh acs. And I'm now logged in to my acs box. So let me get into our exercise file, and we're here. The very first thing that we're going to want to do is let's created an inventory file, we need something for Ansible to run on. So we're going to say vim inventory, and our very first system we're going to create is web1. Now, by just typing in web1 right here, Ansible is going to try and resolve that web1 name. If you don't have a fully qualified name in there, it's going to attach the operating system's domain suffix, so if your domain suffix was assigned the mycompany. com, then it's going to automatically assume web1. mycompany. com, and it's going to keep on putting in all those, if you have multiple search domains it's going to keep on trying one after one after one. So my recommendation is always have a fully qualified domain name in here, but for me I don't have a DNS or any kind of resolution in my test environment, so I'm going to use the behavior parameter called ansible_ssh_host, I'm going to say equals there, and the host name is 192. 168. 33. 20. Now, I've defined the host name, but I want to go a little bit further, I actually want to define, well first off let's just to this. We're just going to create the host, and I like proving things wrong first and then fixing them later. So what we're going to do is we're going to go ahead and run the Ansible ad hoc command on web1, and let's define our inventory file, and we're going to run the ping command again because we just love ping. So you can see here hey, I can't log in, I didn't give it a username. So, we're going to go back into our inventory file and we are going to add a behavior parameter called ansible_ssh_user, and our username is vagrant, and we're going to do an ansible_ssh_password, and that's also going to be vagrant. Now I do want to kind of take a quick 2 seconds here to identify that, well, putting passwords, clear text passwords in your inventory file probably isn't the best idea. If it's a test environment and you're playing around with it, great, but if you're in a production environment you probably don't want your passwords kind of floating around in there in clear text. My recommendation is either using private key files or using Ansible vault, which we will be talking about in a future module. For right now, we're just going to be testing with the clear text. So, let's go ahead and save this. Now, let's run that same command again. Using our inventory file, we're hitting web1, and using the module ping, and it works. That just verified that hey, putting in that username and password as a behavior parameter does in fact work. Now, the next thing that we want to do is let's go ahead and create a group, because we love creating groups. So let's go to the end of the file here and create a group, we're going to call this webservers. And inside this group I'm going to put the web1 server name in there, and let's go ahead and run it against that group, just to make sure, hey, is everything still working. So instead of web1, we're going to run it on the group called webservers, and it still works. Web1 still had a success. So if I had multiple servers in here, web1, web2, web3, that kind of thing, you'll see all the successes of each server inside of that group. I don't have multiple web servers, but I do have a database server that we can pretend is a web server. So, we're going to do the same thing. We're going to identify a file, or a system called db1, and the same thing, we're going to say ansible_ssh_host, 30, and the same users. Now it is a pain in the butt to keep on typing in this over and over and over again, so I'm going to show you how we can attach variables and parameters to groups here in just one second. So, now we have that defined, and we're going to put db1 in the webservers group as well. There is nothing wrong with putting servers in multiple groups. The web1 group could be in the test webserver group, it could be in the production webservers group, you can have systems in multiple groups, that is not going to affect Ansible whatsoever. So now we're going to do the same thing, ansible webservers, but it should be running on both web1 and db1, and you can see here everything came back successful. This is where automation really, really can come into play. So, going back into our inventory file we're going to go ahead and delete the db1 because it's not really a web server. And, let's go ahead and create another group called dbservers, and we're going to put our db1 system in there. Now we have two systems defined, and we have two groups defined. Now we can create our playbooks and all of our commands against the groups or the individual servers themselves. But now let's go ahead and create another group called datacenter. Now if I create a group called datacenter, and I type in webservers and dbservers, the problem with this is, as we mentioned before, it's actually going to be looking for a system called webservers. We don't want it to look for systems, we want it to look for actual groups, so that's where the keyword children comes in. So now we've created a group called datacenter, and it's actually a parent group, its subgroups are webservers and dbservers. So let's save this, and now we're going to run it on datacenter. And it should run against both groups, datacenter, and it does. Web1 and db1 still ran fine, and that's because we have the two groups in there and in each group we have each server. Now as I said, it's kind of a pain in the butt to keep typing this in over and over and over again. It would be kind of nice to be able to type in individual or the shared parameters for each one. Now we can't do it against the host because that is actually very system-specific, but we can do it for the username and passwords because that is shared. Now, let's go ahead and create a variable group, and we're going to create these variables and assign them to our group. Right now we're going to say the datacenter group. We could say the webservers group or the dbservers group, but I'm going to say datacenter, and I'm going to use the keyword of vars. Now this is actually defining hey, variables for the group datacenter. Here is where I'm going to type in the user, that would be ssh_user, and in another line, ssh_pass. Okay. Did I type that right? No I did not. And that's what you get for trying to type quickly. I've got to come back in here and change this, as you can see I'm trying to go fast and I'm not really using my vim shortcuts here. All right, save. Now if we run it again against the datacenter, everything should still work fine because what we've done is we've actually moved those parameters per system, we assigned it to the group, and now everything inside of that group is actually being applied those variables.
Scaling-out with Multiple Files
As your environment starts getting larger and larger, or even if your environment stays the same, your inventory file may get larger and larger as you keep on adding systems and groups and variables to it. And as you do that, you may come to a breaking point where maintaining such a large file is going to be very difficult to do, especially if you have more than one person editing and using those inventory files as well, because when they start editing those files, you may start finding duplicate group names or typos in your variables and when you start troubleshooting your inventory file, you're just wasting a lot of time there, so we want to try and break it up and make it a little bit easier to maintain and use. And the way we do that is by the use of directories. Now there's some directories that are kind of built in to the pattern of Ansible, and there's other directories that are a little bit more flexible for you to start creating yourself. We do have a basic directory structure, and most of that structure is based on variable files. So you have a single inventory file that lists all of your groups and your systems, and then you can break out your variable files into other directories, making it a little bit more manageable. Now to make it even more manageable, most of the time people will want to break up their inventory based on other reasons, say maybe an environment or maybe by geographical location, it could be even by function if you want to break it up. You cannot easily merge multiple inventory files into a single one, at least Ansible can't do that, you'd have to create a script that will do a merge of multiple inventory files to feed into Ansible, which is not exactly a very easy thing to do and it's just one more piece that could fail. So usually people just have one single inventory file that feeds everything else. All the other directories, the group variables and the host variables have to be relative to the path that the inventory is at. Inside of this directory structure you can see we have two inventory files, I have an inventory for production and I have an inventory for test. Both of those inventories are in the same path or the same relative directory as the group variables are, which means that those two files can share the same group variables and host variables. Now that may or may not be how you'd like to do it. A lot of times your test variables and production variables are usually different anyway. So we can actually expand this out into a little bit more of a complex structure, but a little bit easier to understand, and that's where we get here. You can see that I have two directories, a production directory and a test directory, and inside those directories I have my individual inventory files for them. So as you're navigating your directories for Ansible, it's fairly clear to understand hey, this section over here is for production and this section over here is only for test. It makes it a little bit easier. Now inside those directories you can create your own playbooks and roles and everything else inside of them. Now as you're adding your variables, there is a certain order of operations, or a precedence level that your variables will actually be assigned as it's being loaded into Ansible. So as you're building variables in your group variables directory, anything in the all file is going to be applied. So if I have inside that all directory if I have a group variable called ntp-server, then every single system out there has access to that variable. Now, on down the line, the second level of precedence, or what gets a higher level of precedence, is the group name. So after all, if we have a specific group name, let's say our webservers, and we put some kind of variable in it, then it's going to take precedence over that same variable name that's in my all group directory. And then the third level of precedence, or the highest level of precedence inside of our variables directory, now Ansible will take care of precedence in like the command line and inventory files as more precedence than this, but inside of the directories, inside of these variable directories, the host name will take highest precedence over the other three. It's kind of like the layer of specificity, if I can say that right. The most specific version of that variable is actually going to take the highest level of precedence. Now let's take a look at an example of an actual variable file. Now these variable files are written in YAML, and the way to dictate or define that it's a YAML file are using the three dashes. Now Ansible is a little bit flexible on this, if you don't have those three dashes, usually Ansible will just move on and it will be okay, but we should get into the practice or habit of actually defining our YAML files with those three dashes. And now if we wanted to create comments, we'd use it with the pound or the hash tag sign. Usually this is very good practice to do to enforce the readability of our variable files. If we have certain variables, sometimes we want to be very specific as to why we're calling those variables, or what those variables are there for. And then with our variables themselves, they are done with a key value pair. The name of the variable, then delimited by a colon, and then the value of that variable. Here you can see that I have a variable name called ntp, and then the actual value is the ntp server address. So if I'm using a variable ntp in one of my playbooks or in a command and I say hey, assign ntp as the address, it's going to pull this address out and put it into that playbook. Now let's go ahead and move on to our demo, and we're going to create a variable file. We're going to go ahead and look at our directories, we're going to create our variable files written in YAML, and then we're going to show the order of precedence as we go down the line into a higher precedent value.
Demo: Scaling-out with Multiple Files
Okay, we're in our demo environment right now, and we're in our exercise directory, so let's take a look at what our directory looks like. And as we talked about before, we have two directories, or top-level directories, one called production and one called test. Each one of these directories is defining an environment that I want to declare here. And we're going to be working in the production directory, so let's go ahead and go into production, and as we look here, let's take a look at this inventory file and you'll notice that it is the same inventory file that we left on last time. So we're actually going to be building on this inventory file, and we're going to be working with the web1 server again. So as we start looking here, we notice that we have our group_vars directory and our host_vars directory. So we want to look at all groups. So we're going to define that file first, well the fine is already defined, we're actually going to define variables inside of that file. So we're going to go into our group_vars and we're going to edit the file called all. Okay, so we're in our file called all. Remember, this is a YAML file, so we're going to dictate it with three dashes, and we're going to go ahead and create a comment saying This is our user. So what we're going to do in this demo is we're going to create users using Ansible, but the usernames are going to be based on the web server, or the group that we've assigned that variable to, and that's going to help show the precedent level on what we're doing with these groups. So, my username here is going to be all_username, because we're assigning this to the all group, it's going to be a little bit easier for us to understand, right? So, let's go ahead and go back up our tree, and we're going to call Ansible, and we're going to do the webservers group because what we've done is we've assigned the variable to the all group, so this variable is assigned to all groups. So we have to actually run this against a group in order for that all file to hit. So, ansible webservers, our inventory file is going to be inventory_prod, our module is going to be user, and we're going to be adding some parameters to the user. The first parameter is going to be the name. The name of this file, and the way we're going to dictate variables, and we'll talk more about this in playbooks, is we're going to use two braces on each side to denote the variable name, and our variable name was username, that's actually the variable name that we defined in our file. And then the password we'll just say is 12345, if anybody of you love that password, it is on everybody's luggage. So, there is our command, ansible webservers inventory creating that user. Now if I hit Enter here, we're going to find out what that username was. Oh, because I went up too many, I went up way too many directories, didn't I? Let me go here, yeah, I should be in the production, I went up far too many. Okay, let's do this again. I'm in the production data now and we're going to run this same command again. That's because I created, I just touched the web1 file, it's not actually a YAML file, so we're going to go ahead and remove that web1 file, I just did that just to show you what the file structure looks like. So let's go ahead and remove that, and we're going to recreate it again here in a second. So I removed it, let's run this command again. Permission denied. Now if you're not very familiar with Linux, if you're watching this Ansible you probably are, this is because I'm connecting as a user called vagrant, and chances are the user vagrant does not have root permissions, but that is really easy to fix. At the very end of our command we're going to do a --sudo, and this is going to give us sudo rights into that box so that we can actually get root level permission. And there we go. So you can see here that the username that was created is the all_username, that's the username that was actually created inside of that operating system. So, let's move on to our group directory now. Now we don't have a group directory called webservers yet, and if I were to take a look at the inventory file, you'll remember that webservers is the group name that's assigned, so that's actually what we're going to be using too. So, let's go ahead and create a group file called webservers. Now, we're in this file, again it's a YAML file, three dashes, and let's go ahead and create a comment, This is good habit to have, and then if I'm going to be talking about good habits, let's use good grammar too, right? This is a good habit to have. Now, the username here, the exact same username, or the exact same variable name that we're using here, only now we're going to say group_user. The last one was the all_user, which dictated hey, it's the all file. Now we're actually going to create a group_user here and we're going to save this. Now we're going to run the exact same file again for Ansible with the sudo command at the end. Now, let's run through this in our heads. We're running it against a variable called username. Right now we have two variables with the same name on them. One of them is in the all group and one of them is in the group, the webservers group. Logically if we're talking about the order of precedence, the group directory for webservers is going to take precedence over the all, which means now this username should say group_user. So let's go ahead and run this and see what user is created. And again I'm running this in the wrong directory, why do I keep doing that, let's just do this. We're going to say.. /inventory_prod. Let's run this now. And you can see, changed is true, we've now created a user called group_user. It didn't create a user called all_user, it created a user called group_user, and that's because the group directory took precedence. Now finally, let's go ahead and do it for the specific system. So there we're going to move out of the group_vars because we're not going to be defining a group variable, we're going to be defining a host-level variable. So we're going to get out of here and we're going to go into our host_vars directory. Now remember I deleted that web1 because it wasn't a valid YAML file, so, let's go ahead and create a server called web1 because that is the system we're running it against, ---, This is a comment, and now, and obviously I'm typing in comments because it's a good habit, you don't have to create them, username. Now we're going to call this web1_user. Okay, let's re-run this command again, and look at that. We ran it against the webservers, but because the web1 box is what we're actually running against, it replaced it with the web1 user. So we've discussed and showed the different directories, the different variable directories that we can create, and the order of precedence as we use those variables.
Ansible Configuration Basics
The Ansible configuration component of the overall architecture is what defines how Ansible works. As I've mentioned before, one of the more common attributes that you're probably going to want to change here is how many parallel operations you're going to want running at any given point in time. You can also change things like how Ansible handles unknown SSH keys, and there's many other attributes as well. Because the Ansible Team knew that their product was going to be run by multiple people in the same environment, they set up an order of operations rule, or a lookup rule, to load its configuration. Now the first place that Ansible is going to look for its configuration is checking the environment variable called ANSIBLE_CONFIG. If this variable exists, Ansible is going to load the file that it points to first. The second place that Ansible is going to search for is in the current directory that you're executing in, wherever that playbook file or wherever the ad hoc command is, it's going to be looking up the ansible. cfg file in the current directory. This is commonly used when you're sharing plays and playbooks between multiple members or your team using, let's say, a version repository like Git. If they're running Ansible on their local machines and using Git to synchronize all their data, you can add the configuration file with that playbook, and the configuration will move with it. The third place that Ansible is going to look for is in the home directory of your current logged in account, looking for that hidden file called. ansible. cfg. This place is best used when you're using a shared system and you want to run your Ansible plays, but you want to configure it for each user specification. And finally, the last place to look for is in what I call the global config file. This file is considered like the last resort to look up settings that it hasn't learned from the other three locations. Now, keep in mind that this file will only be created if you've used pip, or used like say your package manager to install Ansible. If you installed Ansible and building by source, this file won't exist, but you can still put it in there. Now, the last item that I want you to pay attention to is that configuration files are not merged. Once Ansible finds a configuration file, it's going to stop looking, but you can override specific configuration settings using the environment variables. All you have to do is prefix the setting name with ANSIBLE, all capital letters, and an underscore. Ansible is going to load that environment variable first, then it's going to look for the configuration file next. You can export the variables on the fly right from your command line as shown on the right with the export command, or, you can create the variables in say your profile login, so that they're always there each time you log in. Now, I'm not going to go over each setting that Ansible offers, that's far too long of a list, and I know you're pretty anxious to keep moving. So instead, I'm just going to go over a few of the more common changes you're probably going to want to make. Under the default configuration option, the forks setting identifies how many executions of the same task that you want to run concurrently. The default is set to 5, but in production environments chances are you're probably going to want to increase this number. Now there really isn't really a magic number for everyone, it just depends on how large your box is and what you're trying to accomplish. So this may take some trial and error for you, but I usually start out at 20 and see how it works. Now, does that mean that you shouldn't start out at 25, why just 20? Well, no reason, that's just how I roll, I just think 20 is a nice, round number. If you want to jump up to 25 or even 50, yeah, you can try that, and then go down or up based on the performance of your box. Now Host Key checking is used to validate whether the system to be managed is valid and authorized. Now this is highly recommended for production environments, you always want to make sure that you're talking to the right box, but in development environments or if you're just testing with Ansible, you may want to turn this off so that it may make things easier to be able to connect to them. So if you were to change this to false, then you don't have to worry about that you have all the different fingerprints added to your SSH keys. Ansible also has a logging file that it can be set to. Now by default it's set to null, there is no logging path, but you may want to have, let's say, an Ansible auditing file, or at least know what Ansible is doing over time, in which case you may want to create a log path to set Ansible to be logging its operations. Now when you do this you just need to make sure that the users that are actually executing Ansible have write permissions to that file. Okay, now it's time for demo mode. Here we're actually going to be showing how we define settings in a configuration file, and we're going to prove that overriding settings in environment variables work.
Demo: Ansible Configuration
Okay, now we're in the configuration element demo for Ansible. Now before I actually go into to actually configuring Ansible, I think it'd be a good chance to spend about 20 seconds or so to show you all the different options that you can override or configure for Ansible. There is a slew of options out there, and there's far too many for me to go over one by one, and you would probably get bored near the end of this demo, but let's go ahead and show you where you can find all the different options that you can override. So let's open up our browser here, and we're going to go to the address docs. ansible. com, and from here you're going to go from, under Popular Topics, go to the Getting Started section and then here you have the Configuration file. If you scroll down you're going to see all the different configuration options. It's going to tell you what the defaults are and what they do. Now, if you don't want to go through the whole docs. ansible. com, yeah, you can always ask Mr. Google, he's always really nice and always gives us really good answers. So, if we were to just do a Google search for configuration Ansible, you'll see that most of the time, depending on your search history, and Google always does that kind of stuff, it should be at the top; if you just click on the Configuration file for Ansible, you go right there too. So let's move our demo environment right into our main screen here, and you can see that we're in our production directory. If I do a tree listing here, you're going to see that this looks exactly like our inventory production that we left off from our previous demo. So the first thing that we want to do is let's make sure that Ansible is going to fail. The best way I think we're going to do this is when we're configuring our Ansible, we're going to use the Host Key checking because it's the easiest way to prove whether Ansible is working or not, or at least in my option. So, what we want to do is, because we're checking with Host Key checking, we're going to remove our SSH fingerprint from our SSH known hosts. So, let's go into our. ssh/known_hosts, and we're going to delete our fingerprint that we already have stored there. So now if we run Ansible with all the defaults, it should fail. So let's go ahead and do that just to make sure. We're going to do ansible web1, inventory_prod, and we're going to use ping because we all love ping. Now, it does fail. Why is that? Well because the default for Host Key checking is true, it's always going to check. So let's change that. We're going to use the current configuration, or the current directory configuration file, and that is called ansible. cfg in our current directory. Now, the Host Key checking option is under the option category defaults. So we're going to under defaults change host_key_checking, and we're going to change it to false. Now, right now I should have nothing in our known_hosts file, right, because I actually haven't made any connections to it yet, so let's verify, there's nothing there. So, if I run this now, it should in fact work because it's going to pull the current configuration from the directory. And it works. So we know that it's pulling from the current directory and it's saying hey, don't checking for the known_hosts file. If we look at that known_hosts file, it already added the fingerprint in it. So the way I see Host Key checking, it's kind of like hey, instead of saying disable host key checking, just in the background when you see the option or the message saying are you sure you want to connect, have Ansible just automatically type yes and I don't have to ever worry about it, that's kind of what it's doing in my mind, because it automatically added that fingerprint in here so I can run it each and every time after that. So Host Key checking is actually a really handy way to do a massive, if you just want to do a ping of your entire environment, this may or not be a good idea, you make that decision yourself, but rather than trying to build all of those fingerprints manually, well, now you can actually just say hey, I want to go to my entire environment and just ping every single server with Host Key checking turned off. Now once you do that, it may take a few minutes depending on how big your environment is, but then you're going to have all the fingerprints in there. Now you can change Host Key checking back to true so that if it ever changes, you're going to know, but you already have all those fingerprints automatically added, really handy tip if you want to go that way. It is a little bit risky because you could already have a compromised system out there, but assuming you have no compromised systems, that's an easy and fast way to automatically prepopulate your fingerprints for SSH. Okay, so now I want to use the order of operations, I want to override this configuration file and I want to show you how to do that. So what I'm going to do is I'm actually going to use an environment variable, because the environment variable is always going to take precedence. But rather than using the environment variable to point to another configuration file, I'm only going to override a single option in the environment variable. This is the best way for you to make changes to single options while still maintaining a configuration file for, let's say, your current directory. So again, I'm going to be using Host Key checking because I feel that is the easiest way, so I'm going to go back in to our. ssh/known_hosts and delete that file again. So now it's set to false, so if I run it right now, it should in fact work. But I don't want to set it to false, I want to set it to true. Right now if I were to look at our ansible. cfg, it is set to false, so if I run it, it should still work. So what I want to do is I want to export a configuration option called host_key_checking and put it in my environment variables, set that to true. So, it should override this configuration option. Let's find out. So I'm going to export the configuration name ANSIBLE, all caps, _HOST, if I can type here, _KEY_CHECKING=True. So now I have this environment variable, let's find out if it's still there. ANSIBLE_HOST_KEY_CHECKING, and it's set to True. So now let's try and run Ansible to see if it's actually going to fail or not, because in my current directory I have the configuration file set to false. It does fail, because the environment variable is taking precedence over the configuration option set in my current directory.
Demo: Working with Python3-based Systems
Now before we finish up the module for inventory and configuration, I thought it'd be really helpful to show you how you can get your Python 3 systems working with Ansible. So what I've done is I've actually downloaded and got running an Arch Linux system, which has Python 3 automatically built in to the OS. So I'm in my acs system right now, but let's go ahead and log in to our Arch Linux box to see that Python environment. So I'm going to say ssh vagrant, and I'm going to go to 33. 50, now I'm in my Arch Linux system. So let's take a look at our Python environment. If I just type in Python right here, you can see hey, I'm running Python 3. 5, but I don't want to be using Python 3. 5, Ansible requires 2. x instead of 3, it won't work with 3. So what I could do is I could uninstall Python 3 and install Python 2. No. You don't want to do that, because there's going to be a lot of dependencies in the operating system that requires Python 3. If you want to install Python 3, it's actually going to cause a lot of problems in your environment. So what we want to do is we want to install Python 2 and let those 2 run side by side, which is exactly what I've done here. If I get out of Python I've actually installed Python 2 using its package manager. Now, if I were to look at which Python I'm running, woops, which Python I'm running, it's going to be usr/bin/python, which is the same executable that Ansible tries to run. So by default, it's going to be running version 3, but I don't want it running version 3, so where is Python 2? So I'm going to say where is Python, now I have this executable here called python2. 7. Oh, that might be what I wanted. So let's go ahead and type in python2. 7, now I'm in my Python 2 environment. So this is the executable that I actually want Ansible to use, not the standard Python executable. So let's remember that as we exit out. Now, we're going to go ahead and go back to our Ansible environment here, and I have two inventory files. If I look at the regular inventory file, all I'm doing is declaring my Arch Linux system with keeping all the Ansible defaults. Now, what I could do is in my inventory with Python file, you can see that I have a behavior parameter here called the python_interpreter, and I'm pointing it to the python2. 7 code. Now, let's go ahead and run it without that python2. 7 code to see how Ansible uses, or how it runs against the Python 3 system. So, I'm going to say ansible, we're going to use the 192. 168. 33. 50 with the inventory file of inventory, and I just like making things fail so that you know that the changes that we make are actually working. Alright, so my module is going to be ping, and it's just a standard ping module, and I'm going to be using my user vagrant, and I'm going to prompt for a password because I don't have those behavior parameters in here. Now I'm going to say vagrant, and it does fail, syntax error. Well, what could be invalid? I didn't have any invalid syntax, I just called the module ping. But it's those differences between the Python 2 and Python 3 environments. So, I want to run that exact same command again, but this time I'm going to say the inventory-with-python file, which has the behavior parameter in here for the executable of the Python 2. 7 code. Now let's go ahead and run it with my password, and it does in fact work. So just by putting in this one behavior parameter in your inventory file for your Python 3 systems, you can now have Ansible working with them. It may be a good idea to create inside of your inventory file a group called Python 3, and then for all the systems in that group, assign a group variable called python_interpreter and point it to the python2. 7 interpreter, or whatever version that you had installed for Python 2.
Ansible Modules
Ansible Modules Introduction
Ansible modules are the building blocks that make automation. They're the different actions that you can perform on systems. You can kind of think of them as like a script repository, each module is built for a specific task and you use parameters to change the behavior of that task. Now as of right now, there are 462 different modules that are included in the Ansible distribution, with more always being added, and countless others that are being developed by other companies and communities. Chances are, if you need it, it probably exists, and if it doesn't, and you have familiarity with programming in some kind of language, you can create your own, all Ansible really requires is just something that can be executed on the remote host, and sending JSON back to Ansible. In this course module we're going to be doing a deep dive into reviewing what modules are out there, pulling out some of the more common modules to use, and then at the end we're going to be using some of those modules.
Ansible Modules Fundamentals
Modules are used inside your plays, your playbooks, and your ad hoc commands. They provide the necessary steps for Ansible to follow in order to accomplish something. As mentioned before, I consider this component to be kind of like a command center, it holds all the possibilities in a single repository for you to pick and use whenever you need it. Now, there are three kinds of modules. Core modules are modules that are supported by Ansible themselves. Extras are modules that are created by external communities or companies, but are included into the distribution, and may not be supported by Ansible. But it's also not unheard of for an extra module to be promoted to a core module for Ansible. Now deprecated modules are identified when either a new module is going to replace it or a new module is actually more preferred. Now a deprecated module you need to be aware of because at some point that deprecated module may be pulled from the distribution, and it may no longer be available. Now, out of that 462 modules, it's going to be really hard to know all the modules that are available to you and how to use each and every one of them. So, Ansible does have module documentation installed locally that you can browse. You do have local access to this. Don't have internet access where you're working from or is the internet down? Well it's not a problem because there is one command that you can use. First, you can browse all the modules available to you in the Ansible library by using the ansible-doc command and adding the -l parameter to list all the modules. This displays all your modules in like a man page kind of output. But if you want to know more information about a specific module, such as the parameter names and examples on how to use that module, you can use the ansible-doc command with the name of the module afterwards. You can even get playbook examples on how to use the module using the -s parameter for playbook snippets. These commands make quick work of finding necessary information needed to running a task. Even if you do have the internet, it does take a lot more clicks and page loads and you may have patience for it. I know I prefer to actually use these module doc commands because I'm already in the command line and I just need a quick reference to take a look at it. Now as you're browsing the modules online from Ansible's documentation website, you're going to notice that there's probably like maybe 15 categories that Ansible all loads up their modules into. Now we all know that some of the modules that we can run on is to use to manage servers and deploy configurations. Well it's kind of like Ansible's, that is what Ansible is initially built for, but there is a lot of other categories that you may not be aware of that you can use, such as configuring network equipment like Juniper, Arista or maybe even Cisco. You can maintain your virtual servers in a virtual environment. You can also maintain and manage databases and tables. I've actually had to use a playbook to actually create users and assign them permissions to databases using Ansible's module. And of course you can do load balancer configurations as well, really handy to do because you may be doing some work on a web server, you may want to pull it out of a load balancer group, do your work on it, and then go back to the load balancer and bring that group back up so people can use it again. Now, out of all the modules that are out there, 462 of them as of today, well, that's just a lot of modules, there's no way I'm going to be able to ever talk about each and every one of them, this training course will never get done and you'd probably be a skeleton waiting on that AOL dial-up as I get done through it. So we're going to go over just a few modules that are probably going to be more common than most. First we're going to be talking about a copy module. Now what a copy module does is that it copies the file from the local Ansible box to a remote system. A lot of times I use this when I'm deploying configurations. Now if I'm overriding a file on the remote system, I can also back it up using that same module so that if I accidentally override a file that I shouldn't have, well, I can go back and get that backup. I can also do validation. There's a lot of programs out there that can validate configurations. For example, the Apache web server is what comes to mind. I can actually deploy a configuration there and I can run an apache command that will actually go through the configuration and kind of, once they sanitize it, but just validate to make sure that everything checks out. And I can actually include that into this copy module so that after it copies it's going to say hey, hey you check out, I'm going to leave it here. Now if the copy module allows me to put a file from the local machine of Ansible to a remote machine, well the fetch module is actually going to do the exact opposite. I can pull a file from a remote host to the local system. This really helps for automation workflows if you're trying to grab data and put it somewhere else. Many times you're going to find yourself installing applications, so there is an apt module for Debian-based systems so we can install, update or delete packages. And we can also use the apt module to update the entire system, say with security patches, and if we can do that with Debian-based systems, that means that we have a yum module that we can do it on Red Hat-based systems, same thing, but just with Red Hat systems. Now you may be asking yourself, well why have two different modules for each system, why not just have an installer or an application module, and if it checks to see if it's a Red Hat system, it uses yum in the back end, why have two separate modules? Well that's because each package manager for each operating system has its own advantages that the other one may not. So you may want to use the yum module to take advantage of those specific things that yum offers in apt for the same thing. See if we were to try and abstract it for both of them, then we're going to be only using the commonalities of both systems, and we're going to lose a lot of those features that each one may provide. Now the service module is really handy, after you install an application it may have a back-end service, so once we install an application, or let's say we update a configuration for an application, we may want to restart that service so it can reload that configuration, and that's exactly what the service module will do.
Demo: Using Module Docs and Yum Module
Now we're going to move over to our demo environment. Here we're actually going to start using some modules to install and start some applications. Now we're actually going to be doing something. First off we're going to browse our module documentation and start using our ansible-doc commands to identify what parameters we should be using for those modules. Then we're going to install a web server and a DB server and make sure all the services are started. So, let's go ahead and move over to our lab environment and let's get started. Okay, we're moved over to our lab environment here. If I were to look at our directory we have an inventory file. If I were to take a look at that inventory file it's the same inventory file that we've been using the past few modules. So, let's go ahead and get started. Now, the first thing that we're going to want to do is let's go ahead and start using our module documentation. That's going to be ansible-doc, and if I were to just do an l here, it's going to list all the available modules that are available to me in this installation, and because it is kind of like a man page type of format, I can use my cursors to go up and down as I scroll, or I can use the spacebar to kind of page down. Now that's great, that gives us a list of all the modules, but it really doesn't tell us all the parameters that each module can have, or what those examples are. So, we want to start using the yum module so we can start installing some applications. So I'm going to say ansible-doc, and we're just going to say yum. Here's where it actually gives me the details of that module. Anything that has an equal sign on it is required. So you can see here that I have a name that's required. Well that kind of makes sense, right? I mean without a name how does it know what to do with it? And then as I scroll down a little bit, you'll see that I also have a state parameter. Well this is to tell Ansible what to do or what to check for the name of this installation package. For us, we want to make sure that it is installed, so we're actually going to be using the term present. But, if it's already installed and we want to make sure it's the latest version, or even if it's not installed and we want to make sure it's the latest versions, we're going to say latest, and if we wanted to make it's not there, or uninstall it, we would use the term absent. Well we're going to use present for now. So, let's go ahead and clear this out, and let's install Apache. So, our command is going to be ansible, we're going to be dealing with our webservers here, there's only one in it, but if there was 50 it would install it on all 50, our inventory file, and the module is going to be called yum. Now we're going to send some parameters to the yum module, so we're going to say -a, and in quotes because we're going to send this all as one string to that module for the parameters, we're going to say name=http, because that's the name of the Apache web server on CentOS, and we're going to say state=present. So what we're doing here is we're telling Ansible we want to make sure that the application named httpd is present on this system. Now, if I go ahead and run this now, you'll see that I've got an error message, I have to be root to perform this command. Well, that makes a lot of sense, I have to have root level privileges in order to install the application. So I'm going to run that same command again, but at the very end, I'm going to say --sudo. This is going to give me sudo access into that box so that I can perform all these actions as root. And it's been completed. A little bit of verbosity for the results here, but what's really important is here it says we're talking to web1, the command was successful, and changed equals true. And what that means is that the state that we told it to be in, present, has actually changed, which means it was not in that state prior to us running it. If I were to re-run this command again, you'll see that it was still successful on web1, but changed equals false, and that's because the state is already present. And this is where that term idempotence comes in from what we talked about earlier in this course. Idempotence is actually what identifies or is the term that is used to identify whether something has changed or not. Okay great. Now that we've got it installed, well, we need to make sure it's running. Just installing it is not going to start it up. So, let's go ahead and start our Apache web service. We're going to be talking to our webservers here using our inventory file, but this time we're going to be using the service module. Well wait a minute, if I'm using the service module, what parameters do I need to take? So let's go ahead and take a look at the documentation for service. Okay, here's all the details for the service module. You can see here that the name, again, is required. Well again, that makes kind of sense, we've got to have a name to be able to know what action to be performing on that service. There's a couple other things that we want to look at here. We have an enabled here. Well, what does that parameter mean? Well that parameter means that on boot time do we want it to automatically start or not? Yeah, yeah we kind of do. So we're going to use this enabled parameter. So we have the name parameter and the enabled parameter. Now what else can we see here? Oh, there's our state parameter as well. Now we need to tell Ansible what state we want this service to be in, do we want it to be in started, do we want it to be stopped, restarted or reloaded. Now reloaded has, there's some applications, not all applications handle the reloaded option, but the reloaded means that it's not actually going to restart the whole service, it's just going to reload those configurations. But we want to actually make sure it's started, so let's go ahead and use that command. Alright, we're going to say ansible, again, we're talking to our webservers here with the inventory file, but this time we're going to be using our service module and we're going to be sending that service module some parameters. The first parameter is going to be the name. The name is the httpd service. We want to make sure that it's enabled, so we're going to say enabled=yes, that's going to allow the service to start at boot, and then we're going to say, well what's the state? Well, the state needs to be started. And again, we're dealing with services here, so we want to make sure we have root level permissions. And as we read these results, web1 was successful, and changed equals true, which means it was not in the state of started when we ran this command. Okay, now we have http started, well we have it installed, and we have it started. So let's go ahead and work on our database server now. We want to do the exact same thing, only we're going to do it with MySQL on the database servers. So the same commands, just with different targets. We're going to say dbservers using our inventory file, the module is going to be called yum, and we're going to send some parameters to that yum module, and the parameters are going to be name=mysql-server, and the state is going to be present, we want to make sure that it's on there. Okay, again, some verbosity on the results, but what's really important here is that db1 was successful and changed equals true, which means it was not in that state when we first ran it. Now, let's go ahead and start that service. Before we start it, let's make sure that it is in fact working. I have a command line over here that's actually connected to our database server, and let's do a service mysqld, and let's take a look at the status. Okay, it is stopped, and that's expected, we just installed it and we haven't started it yet. So let's move this back over to the side, and let's start that service. Dbservers with the inventory file, the module is service, parameters to that service, the name is going to be msqld, and the state is going to be started. Let's see if that starts up. Db1 was successful, and it was changed. So, now let's go back to our services, let's bring MySQL back over, and let's re-run this command again. Oh look, now it's running. So we've already installed the webserver, the dbserver, and we've started both services. Let's go ahead and try and run and see if we can access that web page. Okay, we've got our browser up and running, let's go ahead and go to our webserver and see what it says. Oh, it can't connect to the server. You know what that probably is, is that the firewall probably is still running. Now, Ansible does have an iptables module to where we can actually put in rules to say hey, allow port 80, but what we're going to do is we're just going to go ahead and stop that service altogether on all systems, we don't want it running on our dbservers, and we don't want it running on our webservers, so let's go ahead and stop those services. Okay, back into our lab environment here. We're going to say ansible, and we're going to target our webservers, dbservers with the inventory file of inventory, the module is going to be service, and parameters are going to be name=iptables, the state is going to be, if I can do that equal sign, stopped, and again with sudo permissions. And here we go. So we did both systems at the exact same time. Web1 was successful with a change of true, and db1 was successful with a change of true, which means that iptables was in fact running and now we've got them stopped. Now let's go ahead and check to see if that web server has actually responded. Oh, there we go, now we have a web page. Outstanding. Ansible is doing exactly what we wanted it to do. Now if you noticed, I did do something here on purpose that you've never seen before. What is with this colon right here? I have webservers:dbservers. Well what exactly does that mean? Well, let's talk about our target patterns for a second. We can actually start using patterns to identify more than one system and kind of filter out exactly what we're looking for. There are a few different patterns that we can choose from. The first one is we can use the OR pattern, which is exactly what we used when stopped iptables. We basically said we want to talk to all hosts that are in group1 OR group2. Another target pattern that you might want to be able to use is the NOT command. You may want to say hey, I want to talk to all systems that are NOT in group2, and that's where we use the exclamation, or the! symbol, to be able to remove or to isolate hosts that are not in a single group. We can also use wildcards where we were using the asterisk to fill in any kind of wildcard characters. Here we're saying anything that starts with web, but ends with ex. com, I want to talk to all those hosts. And another option is if we want to use regular expressions. If you want to use a regular expression to identify certain hosts, you're going to want to start the pattern with the tilde on it, and then go ahead and type in your pattern. In this example, we're basically saying we want to talk to all systems that have web and have a number at the end. So this would match either web1 or web009. Now let's talk about some complex patterns here. We're going to talk about the AND symbol. Now, AND technically isn't very complex, but I wanted to pick out the idea that it's not exactly what you're thinking of. If we say we want to talk to all hosts that are in group1 AND group2, we're not talking about all 6 of these hosts, we're talking about the inner section between these two groups. Remember, we're talking about hosts here, we want it to talk to only hosts that are in group1 AND group2, which means we're only talking to host2 and host5 because it's in both of those groups. A lot of people think that doing the AND means we're going to do all six hosts. Well that's what the OR symbol is for, the AND symbol is so that we can talk about that inner section. Now why is that useful, when would you ever want to use this? Well this is when you'd start using groups to kind of tag your systems. For example, you may have a system in here that's both webservers and tagged in a group called production. If I do webservers and production, now I'm only talking to production webservers. I could also say the same thing for, let's say development. I could say webservers and development. Well now I'm only talking to those systems and not all systems altogether. And you can make more complex patterns. You can actually put in lots of different patterns together. Here, we're talking about we want to talk to all webservers that are also production, but are not in the Python3 group. So you can kind of daisy chain all of your patterns to make a far more complex pattern and use that in your playbooks and ad hoc commands.
Demo: Using Setup Module
Now, for our last demo here I want to talk about the setup module and I want to kind of focus specifically on this module. This is actually going to be a really good segue into our next course module called Playbooks. Here, we're going to be talking about gathering facts on a remote system. Okay, we're back in our lab environment right now, and I want to start running and executing the module setup on one of our systems. The setup module is actually kind of like an inventory module, or what Ansible calls a fact module. It brings in a huge amount of facts all about the operating system and other things, the environment, of that system we're talking to. Now, the best way for me to describe is, well let's just run it. So I'm going to say ansible on web1 with our inventory file, and we're going to run the setup module. No parameters, no commands, just setup. Let's see what happens. Okay, you can see here that we have a huge amount of inventory facts that were executed in the remote host and then returned back. You can use all of these facts as variables if you want to. Now, as I scroll up, you can see that we have a huge amount of data that was just returned back, all about the environment of this system. We can identify what kind of mounts we have installed, what are the IP addresses, what users are set up on this box? So that's a lot of data, let's go ahead and check this out for the setup module, or basically exporting all this data together. Let's say I want to look at only the network interfaces on all my systems. Well let's talk with just web1 for right now. We're going to say ansible with web1, and we're going to use our inventory file with the module setup, but this time we're going to send a parameter here, and we're going to say filter=ansible_eth*. So this is going to look for any facts that start with ansible_eth, this is going to give me my eth0, eth1, all of my Ethernet interfaces that are on this box. So let's go ahead and run this. There we go. As I scroll up I can see here that I have two interfaces, I have eth0, it's active, wow, it's active, and here's my IP address. I also have an eth1 here that's also active with this IP address. So I can actually grab all the specific network information that I want from this system. Perhaps I want to see what the mounts are, so I can use that same filter, or the same command, but instead of the eth filter, I want to look at ansible_mounts, and this is going to tell me my hard drives that I have running on this system. I have two mounts here, I have this hard drive, sda1 that's mounted to boot, and I have this much data available on it, and I have this logical volume mounted to my root directory, and I have this amount of free space available. This is really handy, if you want to try and install an application or do a massive file transfer, you want to make sure that there's enough free space available so you can actually run this command to see how much free space is available before you run the next command of, let's say installing an application. Now finally, let's go ahead and just dump all this data together into some files. So what I'm going to say is I'm going to say ansible, and I'm going to look at all my systems, I only have two, and we're going to say the module is setup again, but this time I'm going to use the parameter of tree and I'm going to give it a path to where I want to send it. So I'm going to send it to a directory called setup, and I'm going run this, no host match because I did not give it an inventory file, that could be helpful. Alright. Okay, so it dumped all my data down, but if I looked at my directory now, I have a directory called setup, because that's where I told it to put it. So if I go into setup, you can see that I have my two systems here, and if I were to look at them, there is my data for each one of these systems. So kind of handy to just dump all the data, or you can actually filter it and filter all the, let's say network interfaces and mounts, and dump them to a text file so that you can review later or run it against a script to put into your, let's say you have a change management system or some kind of external asset management system, and use that to populate with.
Plays and Playbooks
Introduction
Playbooks are the most common way to execute your automation workflows. They bring together all of your plays to orchestrate whatever process that you're trying to execute. They're easy to use, but they can be incredibly powerful and extendable. I could probably take an entire course dedicated to playbooks, but unfortunately we just don't have the time to do that. Instead, I'm going to give you enough information to get a really good head start. We're going to be going over basic usage, error handling, and logic controls to give your playbooks enough power to kick start your journey with Ansible. We're going to go over a lot of information, and proving that information in our demos. So if you're ready, let's go ahead and get started.
Plays and Playbook Basics
We briefly talked about this in our fundamentals module that playbooks are kind of the glue that brings everything together. It maps module actions to hosts in the inventory, and then it uses the configuration file to define the behavior as it's deploying those actions. Now each playbook holds, well, you guessed it, individual plays. Now, each play maps a host or a set of hosts to a set of tasks, and a play can have many tasks assigned to it. Now a common misconception is that a play can only do one single task, and maybe that's usually because the term play itself is singular, implying a single action. But in reality, a play can have lots of different things going on. What sets it apart is the hosts that it runs against. A playbook can have multiple plays inside of it. Now let's see what this looks like. We've mentioned before that playbooks are written in YAML, and you can see that here. All the tasks included are considered inside this playbook. Now, you can see that inside this playbook there are two different sets of hosts. At the top, we're attacking our webservers, and at the bottom we're attacking the dbservers. Well this would mean that we have two separate plays inside of this playbook, because we have two separate sets of systems that we're targeting. YAML is very picky about whitespace, just as Python is. You can see that not only are we increasing the whitespace indentation as we defined our hosts and our tasks, but we're also doing it as we define our key value pairs. Each time you use the dash or the colon, you have to add a space after that. Without the proper whitespace, you're going to get syntax errors as you execute the playbook, but don't worry, because you're going to get proficient enough with this after you get like two or three of those syntax errors. I'm going to warn you though, if you are not going to get used to this, then your frustration levels may get the best of your during this learning curve. So let's zoom in on a single play, so that we can break it down. The top level of each play will always have a host declaration and a tasks declaration. Otherwise, what good is having the play, right? I don't know if there's truly an official term for this area, but I call it the global declaration, or the global play declaration, because anything defined here is used for the entirety of that play, unless you specifically override it in a single task. Let's go over some of these items before we start tackling tasks and other functions. Okay, well there's nothing new here. This is where we define our targets, just as we're defining our targets previously with our ad hoc commands. You can use hosts, groups or you can use those target patterns that we talked about here. You can also define your variables in this area that your play can use. Sometimes you need to run tasks as a specific user. Maybe it's due to file permissions or maybe profile settings. Here you can choose yes to sudo, and then choose which user to sudo to. Then all those tasks in the play, unless you override it, are going to execute as that user. Now gathering facts in a playbook is always going to happen by default, since gathering facts, which is that setup module that we showed you in the previous module, offers so much valuable data to us, why wouldn't we want it to run? Well, running this module is extremely expensive in time, and if you don't need to use any of the variables that this module provides, you can shave quite a bit of time off your executions by turning this off. Now as you're building your plays and your playbooks, it's important to know that tasks are executed in order from the top down, and tasks use modules for its actions. In this playbook snippet, we're going to define two tasks. You will always want to make sure that you name your tasks for good readability. These names are displayed as you're executing your playbooks, and it gives you a good idea of what's currently being executed. To execute your playbooks, you just use the single command of ansible-playbook. When you're using that command you follow it up with the playbook that you want it to run. Now this command that you're seeing here is assuming that the inventory file is already predefined inside of your Ansible configuration file. If it's not, you do need to add the -i with the inventory file that you want it to use so that it can look up those hosts. Now, if a host does fail a task, that host is no longer going to have tasks executed. This means that if a host fails on the fourth of 10 tasks, it's not going to get tasks 5 through 10, and this is because Ansible is going to pull it out of that rotation on that first failure. But it's okay, because when a host fails, it's going to get added to a retry file. Earlier versions of Ansible puts this retry file in your home directory, but a lot of people complained about this and were saying that these files really kind of cluttered up their directory, and some people don't always check it. So newer versions of Ansible allow you to change this directory inside of that configuration file. When a host fails, you can identify and resolve that problem, but then what, so you fixed the problem, do you just execute the playbook on all those hosts again? Well there's not really a need because the host name that failed is added to a retry file and you can specify that file using the --limit option on that playbook command, and it's going to limit the execution of the playbook to just those hosts inside that file.
Demo: Basic Playbook
Okay, here's where I'm going to move on to our demo, and in this demo we're actually going to be writing our very first playbook. Now this playbook is going to basically redo everything that we've done in previous modules with the ad hoc commands. We're going to write up our playbook and what it's going to do is install our webserver, install our dbserver, and start all of the services, all in a single file. Then we're going to start everything up. What we're doing after that is we're going to fail a play so we can see what it looks like when something fails, and then we're going to retry to play it. Okay, we're back in our lab environment, we're set up on our acs system, and we're in our exercise folder, we're going to take a look at our directory contents. We have an inventory file, and we have a configuration file. Now in our configuration file, if I were to take a quick look at it, basically what it's saying is our host file is going to be the inventory file, basically the way I'm doing it here is so I don't have to use the -i inventory every time I run it. Now you may be asking yourself, well if it was that easy, why haven't we been doing this the whole time? Well with ad hoc commands, I always like to get in the habit of specifying an inventory file, because an ad hoc command can be ran from anywhere in the command line, whereas here with a playbook, we already have to be in a specific directory to run that playbook, so it's a lot easier to build a configuration file inside the same directory as the playbooks that we're executing. Now if I were to take a quick look at our inventory file, it's the same file that we've been using before, so nothing's new here. Now let's go ahead and create our playbook file. The first thing that we're going to do is we want to create a file name of something, so we'll say web and dbservers, and we'll call it. yaml. Now, some people say y-a-m-l as the extension, some people call it y-m-l, you can also just call it anything you want, you can call it a playbook, but most people like the. yaml file extension to basically say it is a YAML file. A lot of times if you're opening it up in a text editor, if it has the. wml, or the. waml file extension, it'll automatically load it up with the syntax coloring of that file. Okay, so we're going to go ahead and just create our file, it's going to be web_db. yaml. Now as I've said before, the very first thing that you need to do to dictate that it's a YAML file, three dashes. Well that was easy. Alright, so, we're going to identify our very first play. To do that we do one dash and do a space, you have to have that space after the dash, you cannot type in host right there, it will not work and it'll give you a syntax error. So, space and then hosts, and then what host do we want to attack first? We'll we're going to be doing the webservers first, so let's go ahead and say webservers. Now in our next declaration inside this playbook, or the play rather, if you remember when we were doing our ad hoc commands, it had to run as sudo because we had to have root level permissions to install applications and start services. So we're going to say sudo is yes. Now I'm going to go ahead and start declaring my tasks, I always like to put a return line in there, some whitespace, you don't have to. We're going to say tasks. Now, inside this task you can see I automatically indented it by two spaces. It could be two spaces, it could be five spaces, it doesn't matter how many spaces you put in here, as long as you're consistent all the way down. So two is just a number that's just, I'm used to doing and so that's what I do. You may do four, it's up to you. Alright, we're going to do a dash here and another space, because now we're going to start creating a list of tasks, and the first declaration or the first item in this list is going to be our name. Remember, you want to do a name for every action that you do inside of a task, or for every task, because it's going to make it really easy to understand as you're seeing the execution of that playbook. And you can see that I also put a space after the colon. If I were to say name and say ensure, as you can see with my vim editor, it's not going to work, you need to have a space after that colon. So we want to Ensure, if I can spell right, Ensure that Apache is installed. So if that's the name of our task, what do you think our module is going to be? Yeah, it's going to be yum, and for yum, the name of the application that we want to install is going to be httpd, and the state that we want it in, if you said present, give yourself a high five. The next line that we want to do is, well we want to make sure that it's, you know, the service has started and that we want to enable it at boot. So, we're going to say name and we're going to say Start Apache Services, because when you install it, it doesn't automatically run. So, our module is going to be service, and name is going to be httpd, enabled is going to be yes because we want it to automatically start on boot, and the state=started. Okay. We are done with this play for webservers. So, because a playbook can have multiple plays, our next play is going to be attacking the dbservers. So we're going to go back to our play declaration, and we're going to say our new, another list of play is going to be hosts:, and now this time we're going to be attacking the dbservers. I have no idea why I say attacking servers, it's not like we're going to do some harm to them, but it is what I say, so my bad. Also, sudo is going to be yes, and another whitespace line just because I like it, and we're going to start defining our tasks. Now, one, two, I do my little indention there, and do another dash. The very first thing that we want to talk to is the name will be Ensure MySQL is installed, and that service, I cannot type, that service is going to be name is mysql-server, and then the state is going to be present. And the next task after that, we want to start it, Start MySQL. Actually I said service up here, didn't I? That should be yum. I am getting my tasks mixed up, that should be yum. If you caught me while I was typing this, good job for you. Alright, service: name=mysql-server, no that's going to be mysqld is the name of the service, and the state=started. Now the last thing that we want to do is we want to make sure that both the webservers and the dbservers all have iptables turned off because we want to make sure those ports are available. So, our next play is going to be hosts, and just like I did in the ad hoc command, I want both webservers or I want hosts in webservers or in dbservers. Again, sudo is going to be yes, tasks, two spaces, and start on our task name, name is going to be Stop IPTABLES NOW!!!, because I like to have a little bit of fun. Alright. And now the service: name=iptables state, boy I'm just not typing right, state=stopped. Okay. Looks good to me. I hope I didn't mess anything up because I was doing this a little quickly here. Now if you remember, our command is ansible-playbooks, we are no longer doing the ansible command, because if we do the ansible command it's expecting arguments. So with the playbook command it's actually going to parse out that YAML so it's a separate command. So, ansible-playbook, I do not have to do the i, because in this directory I have an ansible configuration file, and if you remember in the configuration module as we talked about it, it's doing a lookup in the current directory to find that file, and since I've already declared the inventory file inside that configuration, I don't have to declare it my inventory. All I'm going to do is just run web_db. yaml. Let's run this bad boy and see what happens. Okay, the playbook has run. Let's see if we can scroll through and go through each one of these items as it's talking about here. Okay, so here's my play webservers. Now I didn't actually name the play, that's actually another good habit to get into is naming plays, if you don't name your play, it's basically going to just show you the target host that you're attaching to. So the play here is for webservers, it initially gathers facts because I didn't tell it not to gather facts, so it's going to gather facts by default for every host that I am running against. Now the first task that we're running is here's the name that I actually gave it, Ensure that Apache is installed. If you're the only one that's running this playbook, you may say to yourself, well, I don't have to name it. Well, you're probably right because you already understand how that playbook runs, but it could be that you haven't ran it in a few months, and when you run it again, you don't remember everything that's going through it. So it's a really good idea to name your tasks there. Also, if you're sharing this playbook with other people, they may not know what your playbook is doing, so again, naming your tasks helps for good readability. So we said ensure that Apache is installed, that was changed. The next task for starting services, that was also changed. Moving on down to the next play, which was for dbservers. Again, I didn't tell it not to gather facts, so it gathered facts on db1. Since I'm not using any of those facts or variables for my playbook, I could have turned this off, because it does add about maybe 15-20 seconds, depending on your system, it could be shorter, it could be longer, but about 15 or 20 seconds per host. Well, do the math. If you're attaching to 100 hosts, that's a lot of fact gathering that it has to do. Okay, moving on. So we have Ensure MySQL is installed, that was changed, Start MySQL was also changed, and then our very last play, which was all hosts for both of these, well, so it gathered facts for both systems and the tasks was changed for both systems here for stopping IPTABLES. And at the very end we do have a recap. This tells us everything that went on in the playbook. We were able to scroll through and kind of do a quick glance because we only had three plays and each play only had like 1 or 2 tasks, so it was really easy to kind of follow, but the longer your playbooks are and the more complicated they are, this play recap really comes in handy. So for db1 we saw that there were 5 tasks that were good, 3 tasks that were changed, and those 5 tasks that were good were mostly the gathering facts. And now we have for web1 the same thing, 5 tasks were okay, 3 tasks were changed. So, this tells us that everything was started. We could verify it, but I think we've already verified enough that these playbooks and tasks are working, so I'm not going to go back and verify it. Now we want to fail a play. So what we're going to do is we're actually going to remove the authentication piece for db1. If we remove the authentication piece, then the playbook can't connect into db1 and it won't be able to finish those tasks. So let's go ahead and do that. We're going to go into our inventory file, and we're going to remove these variables for the datacenter. Now if I remove it here, db1 is going to fail because I do not have authentication parameters for db1 up here. You can see that I do have authentication parameters for web1, but I don't have them for db1. So by removing them from the datacenter:vars and because dbservers is in the datacenter group, by removing the datacenter variables it should not be able to connect to db1 anymore. So, let's go ahead and run it. And it failed. Failure db1 SSH encountered a problem. Well of course it did. It can't get SSH into it because we didn't have the username there. But here is where you can see to retry, use the --limit option and here is the file that it puts in here. If I were to just take a look at that file and just do a cat, and we're going to look at home/vagrant/web_db. yaml. retry, you can see here it's just got 1 line called db1. Now obviously if I had 3 or 4 hosts fail, then there will be 3 or 4 host names inside of this file, but because it was just db1, that just failed. So, we're going to run that playbook again, but before we do we need to fix our inventory file so it can reconnect again. Okay, well before I clear it we'll just say this, we'll say ansible-playbook, we're going to call the playbook, we're actually going to be running, what was it, web, I think, db, there we go, and now we want to say the --limit option, and we're going to point it to the file that it created for our retry. Well oops, I seem to have forgotten the @ symbol here, we'll retype that again, playbook web_db. yaml --limit @/home/vagrant/web_db. yaml. retry. And of course I did list and not limit, list is actually a really handy command by the way, we will discuss that here shortly. There we go. No hosts matched for webservers because we're only running it for the db1 system. So you can see here that for the webservers, it skipped that entire play because db1 was not in that group, but db1 did hit the group for dbservers, and it did hit the group for webservers and dbservers, so it did already run. Now obviously because we already ran it beforehand, nothing was changed, but this does show you how you can retry the file so that you can re-run against hosts that didn't get it instead of having to re-run it across all your hosts again.
Playbook Logic and More
Okay, so now that we've gone over the basic features of a playbook, let's start talking more about the advanced features. Again, we just don't have time to talk about all the cool features that playbooks offer, but we can discuss the more common ones. Playbooks offer an include directive that you can call. This offers many benefits, including breaking up long playbooks into more manageable containers, or even encouraging the re-use of other playbooks. You simply use the module include, and then you identify the file that you want included. Now, it doesn't have to stop there, it doesn't have to just be a playbook, we can also include other variables as well. So in that YAML file we can just declare variables and then use the include_vars module to bring those variables in. Sometimes you just need to use the output of one task to use in another task, or maybe you want to make a decision on the execution of your play based on the result of the previous task. This is where the register clause can come in. By using register, you can now store the output, or the result of the task into a variable to use later. Most of the time this is used to identify the success or failure of the previous task, which we'll look at shortly. The debug module is not the same thing as debugging the Ansible communication process that we showed at the beginning of this course with that -vvv option. This module allows us to help find problems by putting in specific information throughout the execution of that play to give us more information as to what it's doing, or we can use it to tell the user that's executing the play more helpful information. The debug module can receive two parameters. You can either use the msg, or the message parameter, to output a full string message to the user, or you can use the var parameter to just simply output the result of a variable. Playbooks don't have to be static. In other words, doing the same thing over and over and over again with no changes. If you have a playbook to create, let's say a user on five webservers, you really don't want to have to edit the file and update that variable each time you run it. You can introduce human error into it, and what if the user that's executing it isn't that familiar with YAML? So you can prompt the user for input during the execution, and then store that into a variable for later use in the play. Here, we declare a vars_prompt and create a variable called sitename. The prompt that's shown to the user is, well, What is the new site name? Now keep in mind that the prompt does not echo back your keystrokes, you may think your keyboard is broken, but it's kind of like a password field, your typing isn't shown, but it is being put in and as soon as you hit Enter, it'll create that variable. Now handlers are an efficient way of dealing with tasks that only need to be called during certain circumstances. Let's say you update the configuration file of some service. You usually have to restart that service in order to load that configuration file, but wouldn't it be great if we could tell Ansible to only restart the service if that file actually changed? Because if it didn't change, why would we want to interrupt that service? Well that's what handlers do. You can inform a handler to execute a task only if the state of that task had actually changed. But there's a few things to note. Handler tasks are only executed after all the other tasks in the play have executed. And also, handler tasks are only executed once, no matter how many times you call it. So if you change the configuration file three times in a play and you told that handler to restart three times, well the handler task is only going to restart that service once. So let's see what this looks like. You define these tasks using the handlers directive in a play. The syntax and operation of a handler is exactly like the tasks directive, only they only execute once and only if they're needed. Now we're starting to get a bit deeper into the playbook execution. We can now start building logic into our plays on when a task should be ran using the when clause. So let's see how this works. Here we have two tasks. The first task is a yum install, but I've added a when parameter that says only run when the host fact ansible_os_family == RedHat, which means if that if this is a Debian system, that task is not going to run. That's a good thing, because we don't want yum running on a Debian system. The next task will only run if the OS family is Debian. Well that's a great thing, because that's apt. So this is truly helpful. Now we don't have to write two separate playbooks, or at least have two separate plays and have one play fail because it don't work. But it doesn't have to be used like this, there are countless other ways that we can use this. Another way is by taking the result of a previous task and making a decision on it. Here you can see that I'm executing a remote command to list the directory contents of a directory that doesn't exist, so it's going to error out. Now, I'm ignoring the errors here so I can move on to the next task. If I don't ignore those errors, it's just going to fail and stop running. So by ignoring the errors, it's going to go on to that next task of debug, and my debug module is only going to output failure when the result is failed. Now, other status options you can use for other registered results are going to be either success and skipped. Templates are super easy to use, but they are incredibly powerful. Using the Jinja2 Engine, you can insert variables from your plays into files. Now most commonly these are used for configuration files, but it really could be anything. You could use it to have Ansible send an email and put in your variables into that email, and because your configuration file is static, the Jinja2 Engine can change that static file into a dynamic one because your variables are going to be changing, so your static files, your static configuration files, are actually going to be changing based on the execution and the targets, or rather your playbook variables are going to be changing as they're inserted into that file. Let's take a look at a simplified example. Here I'm using the template module to take a source file called httpd. j2 in the templates directory. Ansible already knows the variables assigned in it during its execution, so as it reads the file, it does substitution from the variable names to the respective values where they're supposed to be put inside that file. It then copies that file to the remote destination with that rename. So what does a Jinja2 file look like? Well I'm glad you asked. Here's what it looks like. It's merely just a text file of static data, but where you want the dynamic values to be used, you just add the variable name surrounded by double braces. The Jinja2 engine will replace all of those variable declarations with those respective values.
Demo: Advanced Playbook Usage
Okay, we're moving over to our final demo of this module. Here, we're going to build on what we just did in the previous demo. We're going to be adding install decisions based on our OS, we're going to create a template for that Apache configuration that we had just installed, we're going to deploy that configuration, and then if that configuration does change, we want it to restart the service. So let's move over to our demo environment and see what this looks like. Okay, we've moved over to our demo environment right now, and we're in our working directory. If I look at our directory right now, we have all the files we've been working on, but there's a new folder in here called templates. Now we'll go over the templates file here in a second, for right now we want to start building on our YAML file to start including some of those things that we just learned about. So let's start editing our YAML file, and this is what we just finished up with the last time. So all of this is the same, but if we wanted to run this, what if we accidentally run this on a Debian system? Well the Debian system, they have different packages. It's the same software, but the packages are named differently. So we want to make sure that as we're installing this stuff, we want to make sure that Debian is not going to get the yum modules, because it's going to fail. So, for these tasks that are doing yum, what we want to do is we want to put in a when command. So we're going to say when, so the name for ensuring Apache is installed using yum, we only want this task to run when the fact variable ansible_os_family ==RedHat. So, when Ansible runs and it's going to be running this playbook, we didn't turn off gather facts, so gather facts is still going to work, and when it gathers all those facts, one of those fact variables that it's going to be storing is ansible_os_family. So we want to make sure that it's a part of the Red Hat family to use this yum install. We're going to do the same thing down here for this yum install. We're going to say when: ansible_os_family ==, not Debian, RedHat. I currently have Debian on mine. Okay, so only those yum tasks are going to run for Red Hat. Now, for us, db1 and web1 are both Red Hat systems, but now we can see what it looks like when we want to use the when clause. Now, what's the next thing that we want to do? Well, we want to deploy a configuration file to Apache. We installed Apache, we started Apache, but chances are we probably have our own configurations that we want to put into Apache. So let's go back up to our Apache tasks, or this play that we're doing to install Apache, and what we're going to do is we're going to deploy a template to Apache. So, we're going to create a new task, two spaces, dash, and a space, the name is going to say Deploy configuration file. Now you may be asking yourself, well why am I deploying the configuration file after we start the services? Well, I could have done it before the services, but I want to do this so that we can add the handler at the end. So this is really more for your show than actual efficient play operations. So, the template is going to be, the source of the template file is going to be in the folder that we have in here called template. Now you can see, I don't know why I'm having such a hard time typing templates, you can see that I'm actually not putting a forward slash in here, that's because we're talking about it relative to the execution of this playbook file. So it's actually in the same directory as this playbook file, so we're only going to say templates/. Now the name of this template is going to be http. j2. Now, j2 is a simpler way, you can actually call it whatever you want, but j2 does seem to be the standard for a Jinja2 template. Now, that's the source of the file, but we want to put it somewhere, don't we? So the destination is actually going to be where that configuration file resides on the remote host, which is going to be in etc/httpd/conf, and the name is going to be httpd. conf. So it's taken that file, it's going to make changes to it, and then deploy it. Now, once this happens, we need to tell that handler, remember we talked about the handlers and how it can only restart services when needed, so we have to notify that handler somehow. And the best way to do that is we're going to say notify. Now under notify I'm going to go here and I'm going to add two more dashes here, because we can actually notify more than one handler in this task, but we're only going to do the one handler and we're going to call it Restart Apache. Now, what I'm going to do here is I'm going to stop all these changes and I need to make sure that I put in those handlers now, because otherwise I'm probably going to forget. So, let's go ahead and go to our handlers. Now, again, handlers are done just like tasks are. So just like a task, we have a name of a task and we have the module that that task is going to run. So this name of the task, make sure I have my, I'm having a hard time seeing my indentation here, I want to make sure I get it right. Now inside this handlers we're going to have a name, so the name is, what did I call it, I called it Restart Apache. Case sensitive because we are dealing with Linux here. And now the module is going to be called service because we want to restart the service. So the name is going to be httpd, and the state is going to say restarted. So, what's going to happen is we're going to take this template and make whatever changes we need to based on the variables, and then we're going to push this file over to the remote host. Now, the template module knows, due to idempotence, on whether this changed or not. If this task does have a changed status, it's going to notify Restart Apache, and now there's going to be a little flag here inside of Restart Apache saying I'm going to remember to restart at the very end. So once all these tasks are done, this handler should work, provided that that task actually has a change. Okay, so now that we've deployed our configuration and I've moved on to our handlers, let's go back to my tasks so I don't forget, and we're going to create another task. Now, we've already deployed our configuration to Apache, what if we wanted to deploy our site now, let's say our configuration says hey, we're going to create a new site, it's going to be over here, blah, blah, blah, blah, well now what if we want to deploy some files to that site? So, let's go ahead and start deploying some files. We're going to have a new name, and we're going to say Copy Site Files. Now, in my case it's just a single, very simple index. html file, but we'll pretend like it's an entire site and looks really pretty and hugely impressive. Alright, so from the name now we're going to do a template because I've actually got some variables in this site file as well just to make it really cool and fun, so we're going to do src=, we're back in the template again. Now this template file is going to be called index. j2 because I'm going to rename it to be index. html. Now my destination is going to be, we're going to include a variable here. We'll say the doc_root of a variable, and then we'll call it index. html. Now you may be asking yourself, well where did that variable come from? Ah ha, I haven't done it yet. So, let's go ahead and create those variables. So, under hosts and sudo, we're now going to create our vars here. Now again we need to be very mindful of our whitespace. Now, let's go ahead and create some vars. Now inside my Jinja2 file for the index on the website, I have an, well no, this is for the configuration, for the configuration I have an http_port, and I'm going to have a document directory, and that's going to be ansible, and I'm going to have a doc_root that's going to be in var/www/html/ansible. And my max_clients, just for fun, even though nobody would really ever hit it, is going to be 5. Okay, now that I've got my variables here, and you can see here that I'm actually pushing my destination to doc_root/index. html. That's where the file is going to go. Where's my doc_root? Well, it's going to go into var/www/html/ansible, and then it's going to go into index. html. Now inside this index. html, I've also got another parameter for a name. So we want to prompt somebody for the name. So we're going to go down here and we're going to say vars_prompt, and in this we're going to have the name, this is going to be the name of the variable, that will call it username, and now we want the actual message, the actual prompt message. And we will say What is your name? What is your Quest? What is your fav color? If you guys get that, you are awesome. So, now that we have this playbook saved, did I save it, let's make sure. Yes, I did save. Now that we have this playbook saved, let's go ahead and run it. Now I haven't actually done a reset yet from the other modules, so a lot of the installs are going to go really fast because this software has already done the install. So, we're going to say ansible-playbook web_db. yaml. Well lookie there. So there's your syntax error that I was talking about earlier, apparently I was not as careful as I thought I was. So when I'm doing my Copy Site Files, do you see that, oh that is so hard to see. I am one space away, this is one space. Do you see how annoying this is, this is super annoying. It is what it is, you just can't change it, but it is one space away from being, and oh it looks like that might be one space away as well. So then if I didn't fix that, okay, everything else seems to be in line. Alright, very good. Kind of nice to see that, isn't it? Alright, you're not going to be the only ones, even people that have been doing it a while still have this. Let's go ahead and run it. Okay, there's our prompt. What's your name, what's your Quest, what's your fav color? I will say my name is Aaron, you can see that it's not echoing the text that I'm typing it in, search for the grail, blue. Okay. Now it's running the play, gathering the facts, tasks. Look at that. Installing the Apache services didn't change because we're running it from the previous module, but deploying the site files and deploying the configuration did change. And so because that did change, we can see here that NOTIFIED: Restart Apache actually worked, and that changed for web1. Now, let's double check to see if that worked. Let's bring over our handy dandy little browser, and we are going to go to 192. 168. 33. 20/ansible, because that is what it should be going to. Look at that. Nice job Aaron search for the holy grail Blue!! You have successfully ran your first playbook, outstanding! So now that we have that, let's go ahead and take a look at those Jinja2 templates so you can actually see what actually was deployed. Okay, we're back in to our directory now, let's go into our templates directory, and let's take a look at my index. html because it's a lot easier and doesn't have as much data, so let's take a look at index. j2. So you can see here I've just got some HTML code here, but it says Nice job, braces, braces, username. Well, that's exactly the same variable that I declared in my playbook. So it's really easy to be able to just insert variables into anything that has a template in it. Okay, well that's easy enough, let's look at something a little bit more complex. We're going to look at the configuration file. So that's going to be httpd. j2. Now this isn't really that much more complex, just more data. So you can see here that I am putting in my max number of count servers, where's that at, right here. So for MaxClients I'm inserting the max_clients variable here. And then for the Alias I'm putting in my doc_dir right here for where that data is, and then the doc_root for where it resides on the file system. And then listening, well, I have it on port 80 so my http_port does say 80. I can have this running on 8080 or 4080, whatever. If I put that http_port in there, it's going to run it on that port.
Roles
Introduction to Roles
Roles allow you to refactor your playbooks into manageable and more identifiable categories. If you really think about your environment, you may not see a MySQL, Apache or LDAP server, you may see an application server, Active Directory, code building servers or maybe some business repositories. If you sit down and take a minute to think about it, you can probably start looking at your servers as participating in functions of a process. For example, instead of server1 being a MySQL server, you may see it as a dbserver, a datacenter server, and a managed server. You can think of roles as abstracted functions that a system is going to provide, tags if you will. Once you do that, you've identified the roles that that system participates in. In this module, I'm going to be showing you how you can create and use roles in your playbooks. It does take some work ahead of time, but you'll soon see that it's worth it in the long run. The more effort and time that you put into building your Ansible process, the more successful your automations are going to be.
Role Basics
From our last training module, we showed you that you can abstract and refactor your playbooks into more manageable chunks by using the include module. Now, if you break down your playbooks into functions and categories, rather than just steps, you'll start to think in larger terms in reviewing the overall concept of modeling your processes. Well this is what roles are, a combination of thinking and reviewing on how you can model your datacenter process for automation. Now, it's really just more automation, but rather than automating your task, a role is automating your includes using predefined directory structures and search paths. Not only does this make understanding your blocks of plays easier, but it also makes the sharing of these processes easier as well. Now let's start our thinking process by listing out some example roles that you may already have running in your environment or in your existing playbooks. If you have a developer team, then you're likely going to have servers responsible for the build process. Now those systems will have dependencies on compilers and libraries. Well you can create a role called builder, and in that role define all your required packages and configurations. Any hosts that are assigned this role of builder is going to receive all of those tasks, templates, and variables. Another common role would be, well, maybe a server-common role. This is the standard state that you'd want all of your servers in, things like SNMP, NTP, and maybe a SYSLOG declaration. If you have any third-party management agents, or maybe have a playbook that creates a record in your asset management system, you can also include those here as well. Maybe you have a code repository. Well you can add this role and maintain all of your configurations, files, and code on those systems. When thinking about what should be and shouldn't be included in a role, consider how you would apply that role. For example, let's take our previous examples of build and repo. If you've never had a requirement for a code repository that's going to be outside of the build role, well then abstracting the repo role into its own independent function may not make a lot of sense. You could be spending too much time on the abstraction, and start to lose focus at the task at hand. Now too many times I've actually fallen into that "abstraction pit" where I'm spending too much time abstracting every little detail, and I fail to see that larger picture. And when I'm done, now I have way too many roles to manage, and not only am I back to being overwhelmed on how to apply these roles, but now I've wasted a lot of time building them unnecessarily. Okay, back to the task at hand. Let's first talk about how to build roles, and then maybe we can circle back around and see if this changes our thinking. Roles take on a predefined directory structure. Rather than describing our playbooks using directives like tasks, handlers, and variables, we're going to use directories to describe them. Not only does this clean up our playbook, but it also gives us a single location for us to easily find them. Now in the tree image we have defined three roles, a builders role, a server-common role, and a webservers role. A role is really just a subdirectory under a parent roles directory. If you look at this role server-common, you'll see that we have some predefined directories. Now you don't need all of these directories if you don't need them. For example, if you don't use any handlers, well then you don't really need the handlers directory, but what is uber-cool about these search paths that I'd talked about built into roles is that you no longer need to maintain the paths. In other words, you don't have to remember what folders they're in. If you have any file copy operations, well, you just put your files in the files directory, and Ansible will automatically look them up, you don't actually have to specify the files directory. And the same thing goes for tasks, even templates can be directly referenced without actually having to say files folder and templates folder. As you build out your role directory design, there's a couple files that you're going to need. Now the first file is the main. yml file. This file is what you place in your directories to define your respective data so that Ansible can learn about it, directories like handlers, tasks, and vars. So, if you're defining your tasks, you would edit the main. yml in your tasks folder. Need to add variables? Well you add them in the main. yml file in the vars directory. And, if you don't want to put all your definitions in one basket, and you'd rather separate it out for better maintenance, especially for your tasks area, well you can do that, you just as easily just create those include statements that we talked about in our last module, and bring in those separate files. However, try and not do that with your vars directory, because the vars directory is a little bit special, it's expecting a key value pair, so it's not expecting any kind of special include statement so you still have to put in all your variables in that one file, but feel free to do that for your tasks and your handlers so that you can include remote files to keep it separated for better maintenance. Now just as the main. yml file is the go-to file for Ansible to load up all of your necessary definitions, the site. yml file is meant to hold your entire infrastructure, or at the very least the part of the infrastructure that you are defining. And just like the main. yml files, you can add includes into it, helping to split out the long written playbooks. Here, we're expecting the webservers. yml and the dbservers. yml to be put into the same top-level directory as our site. yml file is. By using a single playbook file called site. yml, you greatly simplify the executing of your playbooks. Now, you don't have to run specific playbooks, just execute the site. yml command and then you have access to all of those playbooks that the site. yml file is aware of. But, what if you don't want to execute all of your playbooks? If just execute that site. yml file you're going to execute everything, you may not want that. Well, you can use tags to categorically define your playbooks and tasks, and then use those tags to execute. For example, I can take my include statements and add tags to them like this. Now, here in my include statements I'm basically saying that if I execute the site. yml file with the tag webserver, then only the playbooks that are tagged with that tag will be executed. Well that's pretty handy. But it doesn't have to be in the playbook, you can actually define your tags in your tasks as well. Here, I have two tasks executing debug. The first one is tag debug. Now, you're not limited to running a single tag, you can use multiple tags as well. Now, when you're ready to assign your roles, you simply add the roles to your playbook in the global declarations, like so. You can see that I'm applying two roles in this playbook, the server-common, which includes tasks like SNMP, NPT, and SYSLOG, and other common server variations and configurations, and the builders, which is everything that a development server needs to have. If that's all you want to push, then you're done. Now sometimes it's useful to add tasks that will execute before your roles or after, or perhaps even both. Ansible gives you directives in your global play declaration to define tasks that you want to execute. Two examples would be to remove webservers from the load balancer while you're updating them, and then maybe re-add them afterwards, or you could silence your alarms due to a maintenance window, and then rearm the monitoring servers when you're done. Let's see how we can use these tasks. You can see that we are in our global play declarations, and you can see that because I'm actually declaring my hosts file and I have my gathered facts in there, and under the pre_tasks declaration, you would add your tasks that you'd want to execute. And the same things goes for your post_tasks. Now executing the playbook for roles isn't any different than executing any other playbook. If you use the site. yml file to build your roles, you'd execute it here using the ansible-playbook executable. But what if you didn't want to execute everything? Well, you can tell Ansible which tags you'd want to focus on. If you have any tasks or includes in your site. yml file that you've added a tag clause to it, then Ansible will execute only those tasks or playbooks. Of course, you would add all of this in a single line of declaration, I've only split it here between multiple lines for better display. Now, what if running all tasks on the web is still too much and you want to focus only on a group? Well, you can use the limit option of the executable to define portions of your inventory file. Is that still too much? You can use your host target patterns here with the limit option that we talked about earlier to be as specific as you need. Maybe you'll only want to execute playbooks with the tags web, but only for the atlanta group.
Demo: Creating Roles
In this demo we're going to start building some roles. Now in our previous demo we've already built a webserver and a dbserver playbook, what we're going to do is we're going to take those playbooks and make them into roles. So we're going to define a webserver role, we're going to define a dbserver role, and then we're going to define a common-server role and apply those to all the servers. So let's move over to our demo environment. Okay, we're in our directory here. If I take a directory listing, it's the same Ansible configuration and inventory that we've been working on over the last few modules. If I were to take a quick look at the inventory, it's going to be the same inventory that we've been using the past few modules, as we can see here. Okay, so now we have this directory, but it's fairly empty, we don't really have anything going on here. If we want to build some roles we need to have a directory called roles. So let's go ahead and create a directory called roles, and let's change directory into it. Now we need to start creating some roles that we want to use. Well the first role that we're going to create is webserver, so let's go ahead and create that directory, webserver, and we'll change directory into it. Alright, now we're in our webserver role, what exactly do we need to create? Well we know we have some variables, so we need to have a variables directory, so we're going to create a directory called vars. Well we also know we have tasks, so let's create a directory called tasks. We have some handlers, because if we update the configuration file it says hey, restart, so we definitely want to have a handlers directory, and we definitely had some templates too, right, because we actually had to deploy a configuration file for Apache, and we also had our index. html file to display that really cool page. So we had some templates, so let's go ahead and create our templates directory. Okay, now we have our directory here. If I go up a directory and look at our tree, we have our webserver and we have our four directories. Now let's just take a quick look at our playbook that we've had before, so I'm going to say show_playbook. Alright, so here's our playbook that we've been using, or at least we built in the last module. So we have our variables, those are the variables that we're going to want to be able to put into our vars directory, at least in the main. yml file of vars. Now vars_prompt, we don't really have any kind of prompting available to us from roles, because remember, roles are meant to be non-interactive, it's supposed to fully automated, so we're not going to be able to be prompted for a role, it's actually done on the back end. You can still send variables to it using the -e parameter of ansible-playbook, but we don't have any ability to interactively prompt, so we're not going to be able to have that prompt. We are going to have to send the name username in with it somehow. Now we actually have our tasks here, so we're going to have to put those tasks into that tasks directory, and we have our handlers at the bottom. So those are what we're going to want to try and create as we move over to this role. Alright, so let's go ahead and quit out of this, we're going to go back in to our webserver role. The first thing that we need to do is let's go ahead and start creating our tasks, those are usually the easiest because that's what we've been doing this whole time. So we're going to go into tasks, and now I'm going to create a main. yml file. Now again, this is just a standard YAML file. So I'm going to start off with --- and go down. Now, here's the thing is that these are tasks, this isn't a playbook. The playbook is actually going to be ran up a level, or at least up a directory, so this isn't actually a playbook. Normally you would go ahead and start saying hosts, but we're not actually defining a playbook, we're just defining tasks. So the very first thing we're doing here is we're going to start off with our tasks as if it's implied that tasks is already up above and it's already been named. So we're going to go ahead and start off with our name, and the very first name that we have is we wanted to make sure that Apache is installed. So we're going to say the name is Ensure Apache is installed. And that is going to be a yum update, so the name is going to be http, the state is going to be present, I keep missing that equal sign. Okay, so what's the next task that we have? Well, after that we wanted to make sure that it was started, so we're going to go ahead and say name again and we're going to say Start Apache. And that was going to be a service module, the name was going to be httpd, and the state was going to be started. But we also wanted to make sure it was enabled. So let's make sure it's enabled. Enabled=yes. Alright, so that was our second task. The next task was we were going to deploy a configuration file, that was our template file. So, we're going to say name. Okay, now we're going to define our template. Now remember, I'm going to say src, but I don't have to actually say templates right here, it's already implied that we have templates, so I'm going to take templates out and I'm just going to put in the name of the template, which is httpd. j2. Now my destination is going to be in etc/httpd/conf. Okay, there's our template. Now, once that's done we also have to notify our handler too. So we're going to say notify, and under here we're going to say we want to notify Restart Apache. Now we haven't actually created that handler yet, we're going to do that after this. Now, the last thing that we want to do is we want to copy some site files over, that's the index. html that I had. So, we're going to go back down and we're going to say name: Copy Site Files. Now this is also going to be a template, and this is going to go into our doc_root variable, remember I was defining that last time, index. html. Alright. Now there's all of our tasks that we have defined for the webservers. Remember, we're not actually defining a playbook, these are just tasks, so we start off straight off with the tasks. Let's go ahead and save that, that's our main. yml file. Now let's go ahead and start building our variables. We're going to go up a directory and we're going to change directory into our vars. Here's where we're going to create another main. yml, only this time this is going to be just nothing but key value pairs, these are our variables. So we have had a variable called http_port, and that was port 80, we had another variable called doc_dir, and that was going to be in ansible, another one for doc_root that was in var/www/html/ansible, and finally we had just a fun max_clients, even though we would never hit it, that's going to be 5. But remember, we also had a vars_prompt, right, that's where we prompted for that username. So we're actually just going to type it in here, username equals My name is Aaron. Okay, so now we have our variables populated. So we have our tasks and we have our variables, let's go ahead and work on those handlers. So we're going to go change directory out of here and we're going to go into our handlers directory. Again, main. yml because that is the file that Ansible is planning on loading. So, because it is a YAML file, ---, and here we're going to start off with our handlers. Again, no playbook, no host declaration, we're going to go straight off into the name of the task. So this handler is going to be name: Restart Apache, make sure you do spell it correctly and with case sensitivity, just like you're being called, and the service name was httpd, and we want to make sure that the state was restarted. And as far as I know, that was our only handler. Okay, fantastic, now we have our handlers. Now, rather than trying to rebuild all the templates over at once, I have this handy dandy script here that's going to go ahead and populate it for us. If I were to look at what's in templates now, nothing's in templates. So, let's go ahead and say load_data, oh that's because I already created the templates, okay. So my load_data didn't work very well. Let's go ahead and remove that directory and let's reload our data. And server-common works, that's because I'm doing a load_data for something else and we'll get to that when it's done. So, let's go ahead and take a look at templates again. Here we go, now we have our http and our index templates. That load_data, all I did was just take our templates that we used in the last module and just copied it over. So, if I were to look at the entire tree now for webserver, oh, you're actually seeing my server-common there, let's go ahead and go into webserver and do tree here, there we go. So we have our handlers, main. yml, we have our tasks, main. yml, we have the two templates that we created, and our var is main. yml. The last thing that we really don't have is, well, we don't have a playbook to actually call these roles. So let's go ahead and create just a simple playbook just to call these roles. (Typing) Alright, so we're just creating a simple playbook called webserver. yml. Again, this is the playbook now, so we need to do the ---, and now we need to define what hosts do we want, and this is going to be for the webservers. Now it is going to be sudo is yes because we are installing some software here. We're going to go ahead and say gather_facts no, because it's going to be a lot faster if we don't, and we're not using any of them, so let's go ahead and say no. Now we need to say the roles. Well we only have one role that we want to apply to it, so we're going to say roles, and under roles we're going to say webserver. So all we're doing here is the very simple template, we're targeting the webservers, we're saying sudo, we don't want to gather_facts, and we say hey, we want to apply the webserver role. So let's go ahead and save this. Now, even though there's really nothing in that playbook, that role is going to take care of everything. So if we say ansible-playbook, and we say the webserver. yml file for the playbook, let's see what happens. Okay, it seems like I had a bit of a directory problem on the webserver. The directory was created prior to doing my playbooks, but now it's not, so that's why, what we should have done is in the playbook I should have had a check if the directory exists and then create one in the playbook. I didn't have to do that in the playbooks from the previous modules because that directory was already there. Now I'm dealing with the system that that directory wasn't there, so it's going to fail. So we can go ahead and just run this real quick again just to make sure everything is good, and it copied the site files over, changed, everything was fine. Okay. So you can see that just by defining that one role, if I were to take a look at it real quick, webserver. yml, just by saying roles webserver it goes ahead and takes a look at all of those directories I've created, builds those tasks and the variables, and creates its own playbook based on those directories. So that's actually really easy to do. Now let's go ahead and create our db directory. The db was a lot easier because we're not really doing anything with it yet. So let's go ahead and make sure we're at our top directory, let's go under roles, and let's make our directory for dbserver and change directory into it. Okay, now let's take a look at what did we do with the dbservers. Well, we weren't really using any usernames or we weren't using any kind of variables, so that was pretty simple, we don't need a variables directory. I think the only thing that we really were doing was just doing a couple of tasks. We just made sure it was installed, and we made sure it was started. So that was pretty easy. So we'll just create a tasks folder. So we'll just say mkdir tasks, and in tasks we're going to create a file called main. yml, and in this file, again just three dashes, and we're going to go straight into naming our tasks. That name was going to be Ensure MySQL installed, if I can spell right, apparently I'm not doing well spelling, okay, service. Now the service name was mysql-server, and the state was going to be present, and then the next one was going to start the service, so the name is going to be let's Start MySQL. Of course I'm thinking ahead, I'm already typing in service. If we're going to make sure it's installed, we need to say yum, so kudos to those who have already figured that out while I was typing. Okay, service name is going to be mysqld, and the state is going to be started. Well that was pretty easy. I mean, that's really the only thing we've got to do for the db role was just by typing in that one task of installing and starting. So all we'd really have to do is just, if we wanted to apply this, we would say roles and we would say dbservers. Let's go ahead and do that. So now we're going to go back up, go back up again, and now let's go ahead and, oh, we're still in our roles, we need our top directory. So we have a playbook called webserver, let's go ahead and create a playbook called dbserver. Okay, and again we're going to say our hosts is going to be dbservers, we want to make sure sudo is yes because we are installing and dealing with services, and now we're going to say roles, but in this role we're going to say dbservers, and let's say gather_facts no because, well, that's just going to take more time than we really need because we're not using any of those variables. Okay, now we have those roles. So let's go ahead and run dbserver and see if everything runs fine there, playbook dbserver. yml. Okay, did I mislabel that? My role is called dbserver, and woops, I want to edit it, did I say, no I did not. See, you computers, you need to learn fuzzy logic. Know what I mean, not what I say. I typed in dbservers, and my role was dbserver. Okay, let's go ahead and save this file and let's run it again. And I seem to have a problem with SSH, this is probably due to me working on systems and getting them ready for the demo, so let's go ahead and just do a very quick check, let's SSH into it. Okay, well SSH seems to be working okay. Is it a problem with my Ansible config? Now you're actually seeing me troubleshoot. This is really handy. So let's take a look at our inventory here. Make sure db1, ah, that's why. I don't actually have a username or password for db1, that's actually, that used to be down here in datacenter:vars because dbservers was actually in the datacenter group, but because it's in the datacenter group I don't have any variables defined here, and I also don't have any variables defined here at the host level. So, let's go ahead, and this is my fault, I used the wrong inventory file, my apologies. So let's go ahead and just create those real quick. Ansible, apparently I didn't type that in right, ansible_ssh_user=vagrant, ansible_ssh_pass=vagrant. Okay. Boy you're getting some troubleshooting skills in now, aren't you? Okay, so let's go ahead and run this again. That's better, everything is okay, now we've got MySQL installed, the service was started. Okay. So far, and a bonus of troubleshooting, we've actually gone through two roles, we've created a webserver role and a dbserver role. Now I did go ahead and create this other role, let's take a look at it, and it was the server-common role. So let's take a look at what the server-common role entails. Alright, so if we were to take a look at our tasks, let's go into tasks, and let's take a look at that main. You can see here I've got actually three YAML files. Well that's kind of odd, aren't we just supposed to have the main YAML file? Well this is what I was talking about with splitting up your playbook files, or your task files. If your tasks file starts getting really, really large, you can actually start breaking them up into what you're trying to do. So I'm trying to install and set up SNMP, and I'm also trying to set up and install SYSLOG. Now these files are not very big, but I wanted to show you that you can split them up. So let's take a look at what our main. yml looks like. And you can see, all it is, it's just two includes, we're including the SNMP tasks and the SYSLOG tasks. Well that was pretty easy to do. So let's take a look at our SNMP tasks. So I've got four tasks, installing SNMP and then installing the utilities for troubleshooting, then I'm pushing an SNMP configuration, and then I'm making sure that everything is started. But I also am notify: SNMP Restart because I am pushing a configuration file. Well if I'm doing that then I must have a handlers directory, and I do, there's my handlers directory, and just like before that you've seen the webservers, if I look at my main. yml file, it's just a simple task saying well, what's the restarts. So, now that we have that, we have three roles, and since the server-common role should be applied to all the servers, let's go ahead and create a site. yml file that's going to encompass everything. So we have our dbserver playbook, we have our webserver playbook, but we want to create a site. yml file that's going to encompass all playbooks, so that if we just run that one file for everything. So let's go ahead and create the site. yml file. Okay, so now that we're here, let's go head and start our YAML file, and the very first thing that we want to do is we want to start identifying hosts for all. We're going to do the server-common role first. Well, you know what, this is going to be a really good exercise. You need to probably question whether hosts: all should be used, because hosts: all is going to be every single item inside of your playbook, but not all hosts may be servers. You may be having hosts that could be load balancers, it could be switches, it could be firewalls, it could be all kinds of things that may not actually require the server-common role. So instead of saying hosts: all, we're actually going to define more servers. Now what I would suggest is maybe tagging or building a group called servers so that you can separate the difference between a server and a, let's say a network device. But what we're going to do here is we're actually just going to do the target pattern that we were talking about earlier. So we're going to say webservers and dbservers. So this is going to enclose everything. So sudo needs to be yes because we are installing some software, we're not going to gather_facts because we're not using any of those variables, we don't need to be slowed down, and the roles are going to be server-common. That's the role that we want to be applied to both systems. Now, this is the site. yml, so we need to also encompass the other playbooks that we've created there too, which is going to be really easy. So now we can actually just say include, and we can say webservers. yml, and we can also include our dbservers. yml. Now I need to make sure that I am doing plural-wide, those are Ss on each one and do we have Ss, no we don't. So I'm putting Ss on them again. I've got apparently a really bad habit to do that. So let's take the Ss off of each one so that it will actually work. Now I have my site. yml file. So now I can actually just say ansible-playbooks site. yml, and this is actually going to run and execute all of those playbooks, if I were to type in books. I am just not thinking on my commands today, am I, ansible-playbook. So now it's installing the server-common, it's installing net-snmp. Okay that was a lot of tasks. Let's scroll up here. What I really like about the whole idea of roles is that in task name, it's actually telling you the role name first, and then it's going into the task name, which really is handy as you're trying to identify where in the execution that it's either failing at or where you're at if it's a really long-running playbook and it's taking 30, 45 minutes, at least now you can find out where it's at in the entire execution. So this is really handy, but you can see it's actually using all the web1s, it's going through server-common first because that was the very first task that I created, and then after that it goes down and it does play the webserver roles and the dbserver roles, because that's what I included. So you can see that you can not only just create everything into a site. yml file, but break out your playbooks and include them into the site. yml, so it makes it a lot easier to try and break up and use.
Ansible Galaxy Introduction
As we finish up on our module for roles, I wanted to touch base on how to get roles. Now, there's two ways. You can always just create your own. This way is usually what happens when you're just learning about roles, and obviously the best way to learn it is to actually do it, but really the other reason is is that if you have some kind of proprietary application or workflow, in which case, well yeah, that would make sense, but if it's a fairly common role or workflow that other people probably are using too, well then chances are somebody's already created a role for you to use, so why not try and use it? Now, how would you get it, I mean, where do you go to search for roles? Mr. Google may have some answers, but Ansible has really come up with a better way, and they call it Ansible Galaxy. Now Ansible Galaxy has been built as a system that encompassed three ideas into their app. You can search and download roles, you can share your own roles, and you can review other people's roles. Now, as you search for roles you're going to find that a lot of roles all have the same name, or at least do all the same things, so how do you know which one to choose? Well that's where the review idea comes from. You should pick the ones with the highest rating. If other people have liked it and there aren't any bugs in the code or have any problems, it's going to have a higher review. Use that, people have liked it. Now installing roles is extremely easy using the ansible-galaxy binary. Your command would be ansible-galaxy install, and then the name of the role that you're getting from Ansible Galaxy. Now each role is named by the person's username and their role name. So if I were to create an uber-cool role called ultimate, well you would install it by saying apaxson. ultimate, and that's the name of the role that you would use inside of your playbooks as well. So let's take a quick look around Ansible Galaxy and let's see how to view and install their roles.
Demo: Using Ansible Galaxy
In this final demo, we're going to be browsing Ansible Galaxy. We're going to open up a web page and look at Ansible Galaxy, we're going to search for some roles, we're going to find a role and install it, and finally, we're going to open up a playbook and actually use that role. Okay, I've got my browser opened. I'm going to go to the address bar and type in galaxy. ansible. com, and now I'm on the Galaxy site. You could go ahead and just say Explore, and here's where you can see kind of like the top roles and the newest roles that are out there, you can scroll down and start taking a look at who the top reviewers are and who's the top users on actually updating and sharing these roles. But we want to do is we want to browse roles, so we're going to click on Browse Roles. Now, as you're searching for roles, you actually have a few options to choose from. Let's say that you only want to see Ubuntu. Well you could type in the keyword Ubuntu here, but a far better option is if you drop this down and choose the platform. Now you can actually say the platform you want. Okay, so now we have this filter here for platform Ubutnu, but what if now we wanted to see, okay, well we wanted to look at Git. Well if I type in Git here again, it's going to load up another platform. Oh no, I forgot, we need to change that. So we're going to close out of this filtering tag. And you can see that as we start getting deeper and deeper with our tags, we can start building filters into here. So we're looking for Ubuntu packages, or roles, with the keyword of Git, and let's just choose let's say networking over here in the Popular Tags. Well now we can start drilling down even more, now we're looking at a tag, a keyword, and a platform. Now that's all well and good, but sometimes you have to remember to kind of clear these tags if you need to reset your search. So we're going to say clear tags here, and we're just going to look for Git, because I want to install Git, that's just a really cool thing to have. Well we don't want gitlabs, so let's just keep going down here. Okay, there's a few options for Git. You can see here that we have this git option, we have this git option, boy, there's a lot of options here for Git, how do I know which ones to install? Well, let's go ahead and filter by platform, and I'm going to be looking for el for Enterprise Linux. Okay, well that brought it down a little bit, but still not a lot, I'm still seeing a lot. So we're going to say Sort by Relevance to Score. I want to see what the highest level reviews out there are. Well here's this one, geerlingguy over here, he's got a score of 5. 0. So that means that a lot of people have actually liked this package, that's actually going to take precedence over another Git package, maybe down here, let's see if we can find one. Alright, I'm not seeing one here, let's maybe go to the next page. But there's going to be, here's a git, but without a score. Well I'm going to choose a score of 5. 0 before I'm going to choose a score of not available. So, let's go ahead and go back to our page 1 here with a score of 5. 0, this is the Git that I want. Alright, let's open this up. Okay, 2 people have reviewed it and gave it a 5. 0 score. The details are, well, the details aren't very explanatory. You can see here that you have a Minimum Ansible Version, and what the installation command is. Okay, that's great, but this README file is super important. Click on this README file to look for any kind of requirements that you need to do, or any kind of variables that you need to override. This is where you can see any kind of gotchas or things that are required from your point of view. You can click on Reviews to see what other people are saying about it as well. But this is the package I think we're going to get, so we're going to go ahead and run this command on our acs server to install this role and let's use it. So let's move over to our demo, or to our acs system. Okay, I'm on acs. So we want to install that Ansible Galaxy role. Now I am going to have to say sudo here because when you're installing a Galaxy role, it is going to install it in the /etc folder for Ansible, and you do need root level permissions for that. So I'm going to say sudo, and then we're going to say ansible-galaxy install. Now remember what we said earlier, in order to install it we need to have the username., and then the repo name, or the role name, sorry. So we're going to say install, here's the username, and then we want the git role. So let's install this. Okay, it downloaded the role, extracted it, and installed it. And you can see here that it installed to this path. So let's go ahead and take a look at that path and see what it looks like, because it's just always fun to see other people's work. So, let's go ahead, I won't cd, I'll just do a listing here for etc/ansible/roles/geerlingguy. git, that is a fun name to type, and you can see here that it does have the same directories that we've already created for our roles. But there is one thing that we haven't talked about yet, and that is that defaults directory. So let's see what's in that defaults directory. Well there's a main. yml file there as well. If we were to open that up, here's all the default variables that are actually defined inside of this role. You can always take these variables, let's say we want to, instead of installing it from a package, we want to install Git from source, and this needs to be true. So we can always override this variable inside of our playbook. So I think what I'll do is I will go ahead and copy this just in case we want to use it, but we're going to leave it here at false just for now. Okay, so now let's get out of here. Let's go ahead and create our playbook and actually use this role. So, we're going to create a role called git, and we will say. yml. Alright, now starting up our role, we're going to say our hosts are going to be our webservers because I want to, the reason why I'm installing Git is so that I can have my Git repository always pull down the latest version of say my index. html files, or any other static web pages that I want Apache to serve. So, we're going to say host our webservers. Now we're going to go ahead and say gather_facts. Now I don't actually have to say this, gather_facts is yes. I usually would say no, but a lot of roles when you're installing it from Ansible Galaxy are going to be actually using a lot of decision making based on the fact variables, especially like the distribution. If you get a role that can do both Debian, Ubuntu, Enterprise Linux, whatever distributions you have out there, a lot of times it's going to use a gather_facts variable, or a fact variable to make that decision. Now, so I would usually say no. Now I could say yes here, but if you remember back in previous modules, gather_facts is already set to yes, I don't have to say it. So I'm going to go ahead and remove this line. Now, we do want sudo because it is going to install Git and it's probably going to use our package manager, so I'm going to go ahead and say sudo is yes, and now let's define the role. The role is, and we're going to go down here, and we're going to say the role is going to be geerlingguy. git. There's our role, that's all we have to do. Now if we wanted to override that one variable, it's by default going to be using the package manager, but if we wanted to override that variable, we can always just do vars here, and then underneath vars, put in this var and say yes, install from source. This is now going to override the defaults of that role, and it's going to install from source. But we don't want that, so let's go ahead and remove it. Actually, let's go ahead and remove the vars as well, and let's save it. Now let's go ahead and run it and see what it's going to do on this git repo. So we're going to say ansible-playbook git. yml and run this bad boy. Role is not a legal parameter because, if you guys caught that as I was typing it, good for you. I'm doing things a little bit fast, it needs to be roles, not role. Alright, let's try this bad boy again, shall we? Okay, so it took me a second to take a look at this, this error message is actually an error message from a previous repository that was installed on this system as I was testing things out. So it went ahead and removed this repository from the yum system, from the yum package manager, so I removed that repository, so now let's go ahead and re-run this again because that repository was causing some problems with yum. So, we're going to go ahead and run this again. So let's go ahead and scroll up here and let's see everything that it's done. So, it's playing the webservers, gathering the facts, and here's where it's installing or checking to see if Git is installed. Well Git wasn't installed, so that's why it went ahead and changed. Now, everybody does their roles differently. You can see here that this person is actually checking to see am I Debian or not, so it's actually going ahead and skipping this task because I am not Debian. It's also skipping this task because it's ensuring, it doesn't need to check if the dependencies are installed, so it skipped it. And we're obviously not doing any kind of source compiling either, so we're actually installing from a yum repository. So it's downloading it, all this stuff was skipped, so all we basically did was just make sure that if it was actually installed, and it did change. So if we look down here at the PLAY RECAP, changed=1. So we actually installed this role and we put it into our playbook and ran the playbook. Now I have Git installed and I can start using it as I need to.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment