Ansible on Red Hat on Microsoft Azure: Deploy Apache Web App with Mysql back-end

Introduction

After the Microsoft - Red Hat alliance, engineering teams from both sides are working on the integration aspects. There is a lot to integrate - OpenShift, CloudForms, Ansible, etc. Speaking of Ansible, I recently had to set it up on an Azure Red Hat VM for a demo. Like any developer-at-heart would, I was determined to make Ansible work from source (as opposed to a downloaded binary package). To my surprise, the packages needed for the Ansible master to run on an Azure Red Hat VM were mostly missing! I went through the usual package-hunting process and finally got it going. Thought I would put a blog out to help others in my shoes.

 

My take on Ansible

Ansible feels very simple for the power it gives you. I have used Chef and Puppet - in fact Azure Linux VM-s can be provisioned with Chef and Puppet Extensions enabled. However, what I find a little messy about Chef and Puppet is their dependency on Ruby. Ruby, in any shape and form, is painful to install and keep up to date. Ansible, on the other hand, has nothing to do with Ruby. I was blown away at the power of Ansible's inventory files. Such simplicity! The concept of supporting groups, variables, roles, wild cards, even regular expressions is great. It is simple and intuitive. Ansible is also completely agent-less - you do not install anything on the managed nodes. That makes it extremely lightweight. It relies on inbuilt SSH security - which is well-understood and reliable. I like Ansible a lot!

Of course, I should also point out the potential cons. Ansible is for geeks, because it needs scripting, writing playbooks - so for DevOps personnel who would normally shy away from a little bit of coding, it may demand some learning curve. One good question I faced from an Azure customer was: As Ansible does not install an agent on the managed nodes, does that mean Ansible cannot detect drift of state on the nodes automatically and fix it like Chef does?

That is a good question because it betrays a not-so-uncommon lack of understanding of how Chef works. Chef gives you the illusion of instantly detecting the slightest drift in state because the agent is running every so many seconds or minutes (whatever you have configured it to be) on the managed node. With Ansible, there is nothing stopping you from running your playbooks every few seconds or minutes either (say, by using a cronjob on the master). It will have the same effect. However, with Ansible, you need to be careful that you do not do anything stupid in the playbooks if you use it that way - for example, if you have an online web server serving traffic, and the playbook runs every few seconds - you need to be careful to not step on the live operation. It is definitely doable, but needs conscious deliberation. For example, if you specify the existence of the package "apache" as the desired state, Ansible might keep on resetting your log files and you may lose log files for later processing :)

 

Get set, ready, go

Let us set up a web server and a database server on Azure Red Hat using Ansible. We will need to spin up three Azure Red Hat VM-s: one will be controller (aka master), and the other two will be the managed nodes - one of them the web server running apache, and the other a database server running mysql (mariadb).

I will share how I did this.

 

Step 1. Spin your Linux Infrastructure up

I spun up three RHEL 7.2 instances ("ansiblecontroller", "apacheweb" and "mysqldb"). I was careful to use SSH Keys for them - not just passwords. Ansible is a lot easier if you use SSH Keys. I used the same user id and SSH Key Pair for all three nodes - you should do that while trying Ansible for the first time, it will help keep the focus on functionality rather than configuration. Best way to spin the VM-s up is using the Azure portal, as it automatically adds 1 Public IP Address (for ease of access) and 1 Network Security Group aka NSG (for selectively opening ports) to each VM - we will need these constructs to demo the results of our web deployment by accessing the web server from a browser.

I went ahead and added an inbound security rule to the NSG associated with "apacheweb" that let me access port 80 from the browser.

Finally, after the VM-s are spun up, and you are testing them out one by one SSH-ing into them, quickly do this: copy your private key into the controller VM. Place it in ~/.ssh directory. Name it id_rsa. I know, we can make it work even if we call something else. I just wanted to stick to norms because I was playing with Ansible for the first time on Azure, and I did not want to run against edge cases!

 

Step 2. Install and run Ansible on the controller

When I ran the following commands in that order, it worked (this is the result of some research, trial and error, so I am sure that this is not the shortest nor the most optimized sequence of commands, but they work):

  1.  sudo yum install git
    
  2.  git clone git://github.com/ansible/ansible.git --recursive
    
  3.  cd ./ansible
    
  4.  source ./hacking/env-setup
    
  5.  sudo easy_install pip
    
  6.  sudo yum install python-devel
    
  7.  sudo yum groupinstall "Development Tools"
    
  8.  sudo yum install libffi-devel
    
  9.  sudo yum install openssl-devel
    
  10.  .sudo pip install paramiko PyYAML Jinja2 httplib2 six
    
  11.  sudo pip install pycrypto
    

 

After the above commands complete successfully, ansible will run: ansible all -m ping

It will of course not find any managed host to ping, as we have not added or configured any.

Even if ansible runs fine after the above commands, it does need the command #4 above ("source ./hacking/env-setup") to be run every time you log in. Therefore, go ahead and append the following lines to ~/.bashrc file:

  pushd ansible
 source ./hacking/env-setup
 popd

Step 3. Create your own custom inventory file and see it work

Now, we need to tell Ansible about the managed nodes. We do that via the inventory file called "hosts". We can put the hosts file in several places, there exists an order in which Ansible looks for it. I chose to place it in /etc/ansible directory (just my preference - in fact, /etc/ansible happens to be the last place Ansible looks for it, so for example, if you put another hosts file in the same directory as the playbook, that will take precedence over /etc/ansible/hosts).

So I created the directory /etc/ansible: sudo mkdir -p /etc/ansible

I then created a file called "hosts" in there, with the following contents (change the private IP addresses of the webserver and dbserver to whatever they are in your case, look them up from the Azure portal):

 [webservers]
 apacheweb ansible_host=10.6.0.5
 [dbservers]
 mysqldb ansible_host=10.6.0.6

 

I will not go into the details of all possible ways to create entries in an inventory file, but it is very powerful and simple. In this example, we have used the keyword "ansible_host" and the concept of groups by having two groups: [webservers] and [dbservers].

After this, I configured SSH for ease:

ssh-agent bash; ssh-add ~/.ssh/id_rsa

And now, a ping should show both the managed nodes: ansible all -m ping

(The first two times you try to ping, RHEL will ask you whether to trust the connection (once per managed node), so the output of [ansible all -m ping] will not be as expected. Go ahead and say yes to the prompts, and it should work fine after that)

 

Step 4. Deploy Web Server and Database Server and see it all work

At this stage, you have succeeded in running Ansible on the controller. The other two VM-s are running, but they are doing nothing yet. The controller can see them. Hence, you can run  a playbook that deploys some software on them. I wanted to demo this part quickly, so I started looking for the right playbook on the internet. I came across this excellent collection:

https://github.com/ansible/ansible-examples

Clone it and cd into lamp_simple_rhel7. We will use this playbook.

For me, using this "simple" playbook was not so simple, as it turned out. I had to make certain changes to the downloaded (cloned) files for them to work on Azure. Here is a list of changes I did:

  1. First of all, the playbook "lamp_simple_rhel7" came with its own hosts file in the lamp_simple_rhel7 directory. I renamed it to something else, as I was trying to use /etc/ansible/hosts
  2. I commented out the 3 lines, each saying "remote_user=root" from site.yml, the playbook that I was trying to run. I used a leading '#' to comment them out
  3. For each *.yml file in lamp_simple_rhel7 directory and all subdirectories (except site.yml), I added "sudo: yes" to every task and include.
    To do this, first cd into lamp_simple_rhel7 directory and run a find command: find . -name "*.yml"
    I found 7 files except site.yml. Ignore site.yml. In each of these 7 files, I added a line "sudo: yes" after proper indentation to each task/ include. As an example, I will show the final form of /roles/web/tasks/copy_code.yml file:
 - name: Copy the code from repository
  git: repo={{ repository }} dest=/var/www/html/
  sudo: yes
 - name: Creates the index.php file
  template: src=index.php.j2 dest=/var/www/html/index.php
  sudo: yes

 

Doing the above changes to the downloaded code was not enough though. I soon realized that I had to make certain changes to the managed nodes as well. Looks like the yum repo rh-cloud tries to reach out Red Hat servers over SSL, and this gets blocked, may be because firewalls on these Red Hat servers might be blocking SSL traffic from Azure. Because of this, I got this error when I tried to run the playbook:

 Failure talking to yum: Could not contact any CDS load balancers

 

To fix this, we have to turn off communication over SSL for this repo. This is not recommended for production servers, but I am sure this is one of the things Microsoft and RedHat engineers are jointly trying to resolve so that they can make Ansible more Azure-friendly.

I SSH-ed into each of the managed nodes (apacheweb and mysqldb) and did the following to each, one by one:
I edited /etc/yum.repos.d/rh-cloud.repo, changed all instances of "sslverify=1" to "sslverify=0", and saved (If you use vi, a simple search and replace command is all this takes)

 

After I did all the above course-corrects, I was finally ready to run the playbook without errors:

ansible-playbook site.yml

It should succeed!

 

Step 5. Verify installation

Go to a browser and type in https://<>/index.php, where replace the <> with the public IP address of the web managed node. See the output, which should contain the list of databases in the MariaDB instance! Thus, we have deployed a two-tier application on Azure using Ansible!

 

Conclusion

I hope you found this useful. As the Microsoft - Red Hat integration proceeds further, this blog may get out-of-date, as many of the workarounds may not be needed any more! However, knowing what it took at the early stages sometimes can help! Please leave a comment or feedback to let me know if you tried, and what you found! Thanks!