Author: Xavier C Proofreader(s): Jilliam S, and Jason M
IntroductionWhat I aim to teachGuide referenceSSH key generation and user managementSSH key generationSSH user managementUsing the root user"Chapter" Reference (aka: How Xav remembers things)Inventory and You (The Ansible "inventory" system)A very basic ansible inventoryA spicy ansible inventory (A medium level ansible inventory)Specifying a groupYour first Ansible playbook!The first little stepsWhat do I run? (Actual tasks and basic module usage)The YAML spacingSo how do I actually use this YAML file? (Running the playbook)Variables in AnsibleGathering the facts (Variables Part 1)Customized variables in a playbook (Variables Part 2)Modules in AnsibleThe apt moduleThe template moduleThe copy moduleThe sqluser and sqldb module(s)The shell moduleTroubleshooting AnsibleHow to correctly google the error (Part 1 of Troubleshooting)Seeing how someone else did it (Part 2 of Troubleshooting)How to ask for help (Part 3 of Troubleshooting)Conclusion
This is just a brief summarization on how to at least get a foot in the door for learning ansible. This "guide" doesn't cover any particular lab, but if you're going off of Areeb's labs, it will help for any ansible lab after the 4th one. This is more of a reference rather than a complete guide aimed to be at least slightly fun, or visually appealing to read.
This guide should familiarize you with the general usage of Ansible. Such as:
In this guide. I will be using a Mint 18.2 host and a Debian 10 target. Both of the machines are isolated in a VirtualBox NAT with its own addressing scheme. This environment is all based on what was setup on my previous guide. Some things may change from target to target, or even from host to host. But you should still be able to scrounge up the pieces to get Ansible working in your environment! Debian 10 Mint 18.2
You may be thinking why SSH is so important for Ansible. If you wanted to configure machines, you'd just SSH into them manually and type all the commands out no problem. But scale this a couple hundred more times, and you'll have a problem on your hands. SSH is the gear that gets ansible moving. Ansible relies on SSH to actually do things on the machine.
Generating an SSH key makes connecting to the target machine from the host easier. It also prevents the need to install additional programs like sshpass
. First we need to generate an ssh key on our Ansible host machine:
11ssh-keygen
MAKE SURE TO NOT RUN THIS COMMAND IN SUDO!!
After typing in the command, you just need to spam enter until you see some super cool art on the screen. What this does is generate a public and private SSH key for our host Ansible machine to use.
Now the only information we need to know is what user we want to use on the target machine, and what the IP of the target machine is. The command looks like this:
11ssh-copy-id (user here)@(ip address here)
MAKE SURE TO NOT RUN THIS COMMAND IN SUDO!!
Let's analyze this command a bit more. When I specified (user here), this will be the account you want to use for the Ansible target. So you must ensure that this account has sudo privileges, or is a root user. If not, the only thing useful the user can do is use the ping module. The other part after the @ sign specifies the IP address of the target machine. If you have multiple or different users on the target machine, you may run this command again to have keys for all the users you need.
Sometimes, you want to use the root user so you don't have to specify the become option in your playbook. This requires some changes on the target machine. Sometimes you need to edit your sshd_config file (located usually in /etc/ssh/sshd_config
) and add in PermitRootLogin yes
. Other times, your distro may just not set a root password. So you would need to specify a new one with sudo passwd root
. You can find an example of doing permit root login in my first guide. Or a use case of setting a root password on Michael's guide.
Target: A target refers to the machine you intend to manage and modify with ansible. Host: A host refers to the machine you installed ansible on, and will be using to manage other machines Become: A term used to specify that you want to run something in root. "Become" root. Or sudo Module: A module is used in a playbook to reference a command or action you want to use. Playbook: A set of instructions used by ansible to specify what to run and what target to run it on
So what exactly is an "inventory"? And why do I keep putting it in quotes? To wrap it neatly with a bow, it's really just a text file that contains the IP address of the machine you want to manage. We call that machine you want to manage a target machine. Of course there are different ways you can go about doing this. Some more specified than others, so here's all of the ones I know how to do:
For the next couple of examples, we will be viewing a file on the host Ansible machine. This would be the mint 18.2 VM. We will be viewing a file located in /etc/ansible/hosts
. Keep in mind that anytime you edit a file located inside /etc/
, you will need to open it in sudo
.
Here is an example of a very basic ansible inventory:
41# The very bottom of this file. The top would contain more examples of a hosts file.
2
3
4192.168.0.20
As you can see, this is plainly an IP address at the end of a text file. If we wanted to reference this when doing an ansible ping test, we would type in a command like this:
11ansible -m ping -u (user) 192.168.0.20
Let's analyze the structure of this command. The -m
part of the command specifies what module we want to execute on the target machine. In this case, it is the ping module. It requires no special permissions to execute as this is just testing our connection to the target machine. The -u
part of the command specifies what user to ssh in with. This is important because with this super basic inventory, we don't reference a user or anything! We can do a little bit better than that.
41# The same very bottom of this file. The top would some other cool things
2
3
4Puter ansible_user=(user) ansible_host=192.168.0.20
In this one, I specified 2 things. The IP address of the target machine, and the user of that target machine we're going to use. This line puts both of those statements into an alias. So when running a ping test, I only need to do type this:
11ansible -m ping Puter
In most cases, you won't be just applying changes to 1 machine. After all, this automation comes in handy when you're working with 2 or more computers. So instead of specifying just 1 host, we can shove IPs under a group like this:
101# You know honestly, I can just vent out my problems here, this is just the same bottom.
2
3
4[BeegGroup]
5192.168.0.20
6192.168.0.30
7hostname.tldhere
8
9[BeegGroup:vars]
10ansible_user=(user)
In this example, I put some IPs and a hostname under some brackets. I also defined the user to SSH with for the entire group at the bottom. There is another way of adding this if you want to slightly segment your configuration. You know, leave the targets with targets, and variables with variables. We can actually specify a file for variables. Let's say my group is called "BeegGroup". I would need to edit a file in /etc/ansible/group_vars/BeegGroup
. And in the file, I would add this line:
11ansible_user=(user)
The gist of that is you need to create a file in /etc/ansible/group_vars/
with your group's name to specify variables for that specific group.
Testing the group is the same as testing the "spicy inventory". We just specify the group when running the ansible
command:
11ansible -m ping BeegGroup
Now that we've setup your inventory, we can now finally begin making changes to the target machine! There are a couple rules when it comes to creating these. But it all boils down to "can you write a yaml file".
Here is an example of the first part of a playbook:
41---
2name Tested
3 hosts you
4 becomeyes
The 3 dashes in the beginning specify the beginning of a YAML file. I put them in the start of all my YAML files, so you will also put them in the start of all your YAML files. The second line begins with a dash. What this tells me is it's the name of a specific task we want to execute. But instead of a task, we're just specifying a few things to get the playbook working. The hosts line specifies which target group or computer this playbook will run on. If you just have a plain IP address in there, you're gonna need to also specify a user when running the ansible-playbook
command like this:
11ansible-playbook myplaybook.yml -u (my user here)
The become section at the bottom tells the target computer that we want to run everything in sudo. This is necessary for when you're running modules like apt or when copying files to special directories.
Now I'm going to modify the above playbook to include more information:
101---
2name Tested
3 hosts you
4 becomeyes
5 tasks
6
7name apache install
8 apt
9 update_cacheyes
10 name apache2
In this example, I added the apt module and specified a couple things to it. First of all, I have a name for the task "apache install". These can pretty much knock out the need to comment your playbook unless you name the task "Install stupid app" or something not very descriptive. I also tell ansible that this task needs sudo privileges with the "become" keyword. Then I tell the task that I want to run the "apt" module. Under the apt module, I tell it to update my cache, and install the package labeled "apache2".
When messing around with modules, sometimes you don't have to specify what you think you need to specify. For example, I can modify that playbook to just be this:
91---
2name Tested
3 hosts you
4 becomeyes
5 tasks
6
7name apache install
8 apt
9 name apache2
It's much shorter and still technically specifies what I want it to do. But I am just missing certain things that might not make the task succeed. If you're familiar with the apt package manager, I will compare what these 2 playbooks accomplish on the command line level:
The first playbook:
21sudo apt update # This tells apt to update the package cache and check for newer versions
2sudo apt install apache2 # This tells apt to actually install this package
The second playbook:
11sudo apt install apache2 # This just installs the package without updating the cache
This is the bane of my existence being an average tab enjoyer. Now I haven't tested this, but I'm sure due to how YAML works, you need to use spaces instead of tab. If you have an editor like VIM or some other advanced text editor, you can convert tab to become 4 spaces instead. I'll show the above playbook again, but I will comment the amount of spaces I used:
181---
2name Tested
3#^ only a single space between the dash and the name
4 hosts you
5#^ 2 spaces
6 becomeyes
7#^ 2 spaces
8 tasks
9#^ 2 spaces
10
11name apache install
12#^ 4 spaces
13 apt
14#^ 6 spaces
15 update_cacheyes
16#^ 8 spaces
17 name apache2
18#^ 8 spaces
As you can see, the spacing keeps incrementing by 2 depending on how deep it goes.
Now that we've put in our blood sweat and tears into a YAML file, we can finally tell ansible to execute it on the target vm. For this example, I assume you have a "Spicy Ansible Inventory" or a group that specifies an ansible_user
variable already. If you're running this with an ansible user that isn't root. The command will look like this:
11ansible-playbook myplaybook.yml --ask-become-pass
Which will prompt you to enter your sudo password. However, if you feel like you're too good to just be a normal user and want to run everything in root, you can remove the --ask-become-pass
part of the command because you would already be root:
11ansible-playbook myplaybook.yml
Essentially, variables are placeholders for passwords, names, or anything really. The neat thing about ansible playbooks is we can define a wack load of variables, and it will just dynamically change the script depending on what you set and how you set it.
What exactly are these facts? They're just bits of information about the target collected by ansible. If you want to see a list of all available default variables to use, just run this command:
11ansible -m setup (host or group here)
It will show something like this:
191192.168.0.20 | SUCCESS => {
2 "ansible_facts": {
3 "ansible_all_ipv4_addresses": [
4 "192.168.0.20"
5 ],
6 "ansible_all_ipv6_addresses": [
7 "fe80::a00:27ff:fe61:8bc7"
8 ],
9 "ansible_default_ipv4": {
10 "address": "192.168.0.20",
11 "alias": "enp0s3",
12 "broadcast": "192.168.0.255",
13 "gateway": "192.168.0.1",
14 "interface": "enp0s3",
15 "macaddress": "08:00:27:61:8b:c7",
16 "mtu": 1500,
17 "netmask": "255.255.255.0",
18 "network": "192.168.0.0",
19 "type": "ether"
These are default variables generated with the setup module. So if you wanted to reference a certain aspect about a target, lets say their IP address. You would do it like this in a playbook:
101---
2name Tested
3 hosts you
4 becomeyes
5 tasks
6
7name Installing for "{{ ansible_default_ipv4['address']}}"
8 apt
9 update_cacheyes
10 name apache2
This example kinda sucks. But the naming would change to "Installing for (ip address of target)". Say you're installing a webserver, and need to display or reference the target's ip address. You can use these default ansible variables to specify the ip address, especially if it changes all the time.
Sometimes we want to specify something more than just the target's properties. Say we use a password for a database, or want to name a database a specific thing. We can solve this using custom variables defined inside of your playbook. Here is an example:
121---
2name Tested
3 hosts you
4 vars
5 stupidpassword areallylongstring538zerozero
6 becomeyes
7 tasks
8
9name Installing apache2 and updating apt
10 apt
11 update_cacheyes
12 name apache2
In this example, we define a custom variable for a password. This can be called by using "{{ stupidpassword }}"
. This may not be useful for just plainly defining it in a playbook. But it becomes useful when using it in the template module. Actually, speaking of which..
Modules are the building blocks that make ansible go! They're little small tools that build up to something bigger like a LAMP stack, or even a full wordpress site! There are a multitude of modules to use in ansible, you can find the full list and documentation here. But I will just list and give examples of modules that I find the most useful. It may be daunting at first, but scrounging up little pieces of examples and googling errors is exactly how I learned to use ansible:
The apt module is useful for installing packages on Debian based distributions. Whether it be on Debian, Ubuntu, Mint, Elementary, basically any non-specialty distro that uses apt
as it's package manager. Here is an example of how this module would look like in a playbook:
61# Hosts and Other things defined above
2name Installing apache2 and updating apt
3 become yes # This module needs to run in sudo
4 apt
5 update_cacheyes
6 name apache2
What this is telling ansible is, "update my package repository. then install a package named apache2". Or in command form:
21sudo apt update
2sudo apt install apache2
We can also tell the apt module to install many packages by changing up the structure a little bit:
91# More things and such defined above
2name Install apache2 and friends
3 becomeyes
4 apt
5 update_cacheyes
6 pkg
7 apache2
8 mariadb-server
9 php
Which is equivalent to:
21sudo apt update
2sudo apt install apache2 mariadb-server php
This module allows us to do magical things with the variables we defined at the top of the playbook. You know, like place them into files. Usually we would create a playbook yaml file in a folder, and also create a folder within that one called "files". This is where we'd store our template files (files that end in the .j2 extension) and other things we'd like to copy to the target machine. Lets say we want to create a file named foo.txt
inside the /tmp
folder on the target machine. In this file, we want to place a password, and the machine's hostname. First we create foo.txt.j2 inside the files folder. Then we modify it to look like this:
21The special sauce password is: {{ mycoolpassword }}
2And this computer's name is: {{ ansible_nodename }}
And the playbook would look like this:
121---
2name What a cool playbook
3 hosts (host here)
4 vars
5 mycoolpassword Pa$$w0rd
6 becomeyes
7 tasks
8
9name This really tems my plate
10 template
11 src"files/foo.txt.j2"
12 dest"/tmp/foo.txt"
Upon running this playbook and inspecting the file we created with the module on the target machine, it would look like this:
21The special sauce password is: Pa$$w0rd
2And this computer's name is: (pretend I put a witty hostname here)
Essentially, the copy module is the exact same thing as the template module, but it is solely used for copying, not shoving variables into template files. Again, this example references a folder called "files" which would be in the same folder the playbook yaml file is in:
41name This really tems my plate
2 copy
3 src"files/doIlooklikeIknow.what"
4 dest"/tmp/a.jpeg.is"
!!HEADS UP!!
FOR DEBIAN 10, YOU WILL NEED TO INSTALL THE python3-pymysql
AND python-mysqldb
PACKAGES ON THE TARGET MACHINE
Protip: Use the apt module to install these packages on the target machine
The mysql_user and mysql_db modules deal with the mariadb backend. This includes creating sql users, managing databases, and setting the sql root password. Here is an example of setting the root sql password using the mysql_user module:
61name Maria password
2 mysql_user
3 name root
4 password"{{ supersecurepass }}"
5 login_unix_socket /var/run/mysqld/mysqld.sock
6 # Idk, this module dies if I don't specify the thing above
And here's an example of the mysql_db module. We will be using the module to create a database using that root account:
81name Creates database for WordPress
2 mysql_db
3 name"{{ database }}"
4 state present
5 login_user root
6 login_password"{{ supersecurepass }}"
7 login_unix_socket /var/run/mysqld/mysqld.sock
8 # Idk, this module commits unalive when I don't specify login_unix_socket
The shell module is what I like to call the "catch all" module. Why do I call it that? Because the purpose of this module is to literally run a shell command. Now imagine if I didn't know how to use the get_url module in my playbook. But I do know that the target machine has wget. It'd be safe to do this:
41name Ansible is my bash script
2 shell wget https //cooldownloadhere.com
3 args
4 chdir /tmp/
What this does is downloads whatever is contained in that url, and stores it in /tmp
on the target machine.
When running commands in this module, sometimes ansible will tell you what module to actually use. You can choose to try and change your script to match ansible compliance, or you can be glad that your playbook just works.
A lot of the times, your playbook won't work. Or something out of the ordinary will happen to you. Fortunately, there's always a way to solve these issues. Whether its pressing, or just really annoying.
When running the playbook and receiving an error, ansible will show text in red. Sometimes it will be just fine and you can ignore it. (especially when its the last task in the playbook and its something like deleting some temp files) But other times, it will prevent you from progressing any further.
When googling the error, you want to make sure to strip any information relating to you or your machine. Take this error for example:
11fatal: [192.168.0.20]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true}
Now you can't just google this entire string. Google will be so confused about what you're asking. In this case, you can trim it down to this:
11"msg": "Failed to connect to the host via ssh.", "unreachable": true
The way this text is formatted looks like its an ansible specific error. Or you can strip it down even further to this:
11Failed to connect to the host via ssh ansible
Furthermore, you can enable debugging details to further analyze the error and get a better idea of what to do by appending -vvv
to the end of an ansible
or ansible-playbook
command.
Hey, sometimes you really just don't know how to accomplish a certain thing in ansible. But chances are, somebody already went through the same thing, or already has a full playbook. Lets say I wanted to deploy a LEMP stack to a target, but I don't know how to go about doing that. Fortunately, google lead me to a premade LEMP stack playbook. Now from here, I can choose to just copy everything this person did. Or I peak through the files and see how they accomplished the specific thing I couldn't do.
When worse comes to worse. You're just genuinely confused on how to go about fixing a specific error you have. But don't fret! You can ask somebody you think knows the answer like another peer or the teacher! You just need to make sure you provide all the relevant details.
When asking your question, include the following:
Trust me, its easier to respond when you include those details and ask a question like: "I tried to use the mysql_user module to create a user, but the step keeps failing on this error message". It's more helpful than if you just posted a screenshot with the message "idk what's wrong". It's good to be descriptive, but don't be overly descriptive. Example: "Hey so I ran into this error with the error code 0x38134. I ran the program at 1643771299 UNIX time (UTC) on a computer supporting an intel core i5 processor running....".
Ansible is a whole lot of fun when you get to understanding it. So whether its your first time using ansible, or you're just skimming this to complete the lab. I hope you actually understand and have fun with it!
-Xav :)