Introduction to Ansible

Hello There,

This is the first article of the series on configuration management with Ansible. I hope this will give you hands on real practice time , working knowledge with Ansible

Using Ansible

Now that we have our default inventory and our infrastructure defined in the mother article, let’s see what we can do with Ansible. Let’s make sure both nodes can be pinged manually on their hostonly IP. We can actually do the pinging using ansible:

Let’s explained that just happened.

Ubuntu the OS I am running on requires a gentlemen handshake between itself and any other guy (nodes) I am trying to do any ssh business with. So I have been prompted to accept the other guy’s Key. So my Ubuntu adds the other guy’s key to it knowns_hosts file so it knows that node 172.24.14.100 is a familiar face so we can do business together. You might be wondering why it didn’t do the same for node 105… well it’s because I made some mistakes so I had to verify few things for the 105 before running the show 😛 so I have already added the key for 105… basically.

On the command front below is what we have passed as arguments.

  • all: this targets all nodes in the default inventory file /etc/ansible/hosts
  • -m: this is a module switch telling Ansible that we are using ping module
  • -u: this is the username switch telling Ansible to use the passed username to connect to the node
  • -s: this is the sudo mode switch
  • -k: this is the password switch to make Ansible prompt for password of the target nodes. Ansible prefers ssh key based authentication though. So we will get to that

So with our ping, our nodes happily responded with pong through Ansible. This was easy but there is nothing to be excited about. Let’s do a little more interesting job. Let’s use 2 modules : apt and shell.

Using apt module

using Ansible apt module we will be install unzip on both machines with one command. So assuming you have 1000 nodes , this is still done in one command.

This will take some time to execute since it will be doing apt-get update (with the argument update_cache=true) on each of the nodes. Let’s see below values we have passed. We will skip those explained before unless it’s necessary.

  • -m apt: we are using apt module
  • -a ‘pkg=unzip udpate_cache=true state=installed’: The switch a is for passing the used module’s arguments. pkg is the package we are pointing out for Ansible to action on, update_cache=true is to trigger apt-get update on the node and state is to describe what we want Ansible to do with the pointed package.
Using shell module

Say we need to create same file inside every node. We can use the shell module and type our commands as if we are in some terminal.

Playing in the playbook Arena

We have see how helpful Ansible has been. It’s not too far from what we do form terminal. How about executing multiple tasks or even more complex functionalities. To do that, we will need playbooks.

It's important to mention here that playbooks and other instruction files of Ansible are mere yaml files.

Enough with command lines stuff which can still make us miss some steps when setting up complex requirements. Let’s give ourselves some challenge. Let’s use Ansible to download and install wordpress on the webserver and its database on the db server. This will mean we will use the playbook and play the following major tasks:

  • Install Apache2 on web
  • Install php on web
  • Install mysql on db
  • Download and setup wordpress on web

The above contains obviously some sub tasks and steps that might be missed when doing it manually. Let’s see how with the help of ansible we can make these tasks deterministic.

Install Apache 2 on web

Let’s start defining our playbook using apt module for the apache. Let’s create our wordpress_on_apache.yml file in ~/ansible/playbooks folder that we created for the purpose.

Please notice that the line 6 above has been highlighted for a reason. The sudo mode is activated so there is no need to pass the switch -s when calling the playbook.

Install php on web

Let’s edit our playbook to add the installation of php on the webserver. Let’s make sure you have the same playbook as shown below:

On the line 7 , we have used a templating engine syntax. Ok I will tell you what it is… Ansible is built on python so the templating engine that Ansible uses is not other than jinja2. So with the double brackets, we have defined a place holder using the looping syntax of nsible to loop through packages defined under with_items as a list. Let’s run it a see

Install mysql on db

With retrospect, we will edit our playbook to install mysql-server on the db server and we will install mysql-client on both web and db nodes rather. let’s have our playbook look like below:

We have defined a second host in our hosts list in the yaml file so whatever we instructed there gets applied to the pointed hosts. I could have used the loop syntax for the db node as well but chose not to in order to have a simpler dbservers section of the playbook. Running this new playbook should look like something below:

Download and Setup wordpress on web
Download worpress on web

In order to download wordpress , we will need to leverage on Ansible’s get_url module. Let’s check first where we can get the download url of the currently available version of wordpress. By checking on https://wordpress.org/download/, I get the latest zip url : https://wordpress.org/latest.zip. Let’s then adjust our playbook for this new task to take place:

This should logically produce the a screen similar to the one below unless some monkeyfication took place along the line 😀

Setup wordpress on web

You might have noticed that I separated downloading and setting up of the web application. The reason is simple. We will use a simple web deployment process which is just copy and paste but we will need to take care of the MySQL user that wordpress itself will use to connect to the database server. We need to create that database as well as prepare Ansible to contact our MySQL server. According to documentation , Ansible uses the python-mysqldb driver.

The MySQL server installed by Ansible has a little special case. It installs with default root user with no password. It’s done so by design so we will need to use the mysql_user module to create the right root user. That mysql_user module uses login_user and login_password parameters for authentication. Now it uses the default root with empty passwords or read from the my.cnf file.

Both login_password and login_user are required when you are passing credentials. If none are present, the module will attempt to read the credentials from ~/.my.cnf, and finally fall back to using the MySQL default login of ‘root’ with no password.

MySQL server installs with default login_user of ‘root’ and no password. To secure this user as part of an idempotent playbook, you must create at least two tasks: the first must change the root user’s password, without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from the file.

Since I am not really comfortable putting username and password in the my.cnf file, I will have to find another solution. It’s a simple one though. I will use the mysql_user module to delete the mysql empty user and then check whether that failed or not. I have not provided login_user nor login_password so it will assume the root password has not changed.

If this tasks of deleting empty user has run successfully then it means the task that sets our root with password has not been run so task for setting password for root user should then run.

If the deleting of the empty user task fails then our root with password is set and different from empty string so Ansible won’t run that tasks again.

We have also already downloaded the wordpress zip when we run the playbook again, it won’t try to download it again as far as the destination file exists. But if you are some paranoia guy like K and still want to check by yourself, you might want to use stat module for that. This is just to show how we can handle some of our concerns through Ansible as if we are manually typing commands the old way in a terminal. With the stat module we test if the path exists. If it does exist then our downloading task will be skipped. Below is our new playbook. When run, it will prompt for the new root password of the MySQL

Upon first run we should be having some output similar to below:

As per output, we have skipped the task on line 23 for wordpress download, and then our hosts for root have been installed on lines 37-39. Let’s run it again and see what will happen:

According to the experiment we still have your skipped task on line 23, we have also ignored our mysql user task on 36 and skipped the hosts from line 39 to 41. Let’s now create our wordpress user: wp_user and our wordpress database wp_db.

In normal circumstances we would have logged into MySQL and create the wp_user with its privileges like shown below but don’t do it though, we will have Ansible handle that:

The equivalent in Ansible of the statement above is shown below but with Ansible, we handle the login to MySQL database as well that’s why you can still see loging_user and login_password:

The full playbook as to this current state of our road to our wordpress installation should look like the following:

Below shows how to run the above playbook as a new way of passing variables to the playbook has been introduced. Kindly notice that mysql_wp_password variable’s value has been passed through the extra argument switch -e.

Let’s finally uncompress/unzip the wordpress package that we downloaded earlier. We will need to use creates argument of the unarchive module to prevent the overriding of the uncompressed files for subsequent run of the playbook. It’s also important to indicate here that unarchive depends on tar, unzip package an assumes they already exist on node. If you follow this post from its mother article then it will run as we have already used unzip package in one of our example. If that it’s not case then you might want to add unzip package before the unarchive package

So far, we have not actually gone to check whether whatever we have been doing are not just lies to ourselves. At this point some verifications must happen. In the browser let’s go to the IP we have defined ourselves 172.24.14.100 this should show the usual apache 2 default page. Let’s then check on the wordpress folder : 172.24.14.100/wordpress . It should show you a wordpress welcome page with option for language. When clicked on continue button, it shows information about what you need to know for the installation to be completed successfully. That’s how we know on the web front we are safe :D. Let’s also follow the wordpress installation wizard to check whether we are safe on the db front too.

Inserting known values
Inserting known values

After clicking on submit I have seen a very nice failure message smiling at me. I have crossed checked all the parameters and they where all correct.

We were bounced
We were bounced

After pondering on this for a while , I checked pinging of 172.24.14.105 from 172.24.14.100 and it replied. I then tried to connect to mysql server on 172.24.14.105 from 172.24.14.100 and it bounced. So I went to check on the network settings of mysql server installed and just saw it doesn’t allow connection from anything else but loopback address 127.0.0.1 which is also localhost.

So we have identified 2 problems, first our wp_user is not allowed to connect from any host different from the server host, meanwhile the wordpress app which will use that user is on a different host: 172.24.14.100. The second issue is even if we have set that, we wouldn’t even be able to connect to the server because it doesn’t access any external connection.

let’s fix that. We will change the bind-address part and will add new host for the user wp_user with the following snippet:

In the snippet above the host variable of the mysql_user module has changed to a placholder for a list item. We have also used the replace module to look for a particular beginning of a line of a file and replace that line with what is convenient for us. Then we have triggered and action with the keyword notify. That is a handler. Handlers are exactly like tasks, but where they differ is that they don’t run on their own as tasks would, instead they are called by other task(s). After changing our mysql network policy , we need to let mysql server itself know about it so it has to restart. That’s what our handler did for us. Below is how the full playbook should look like:

Let’s resume our journey of installing wordpress, after these modifications, we should be fine. After retrying it we should be seeing a page similar to the one below:

DB test passed
DB test passed

After 1 or 2 steps, we have wordpress installed

we are done
we are done

Voila folks, this has shown how powerful ansible is, we have taken some steps to simulate our flow,debugged it just like in real usage just like big boys. By the way, please don’t go brag anywhere that you know how to use ansible, we have barely scratched the surface of what ansible can do 😀 . In the next tutorial , we will try more advance concept of ansible thus of configuration management in general showing how to use roles, group_vars/host_vars. I hope you had fun and thanks to the Ansible IRC chat room folks, they have been of a great help 😉

Leave a Reply

Your email address will not be published. Required fields are marked *

captcha * Time limit is exhausted. Please reload the CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to top