How To Deploy Multiple PHP Applications using Ansible on Ubuntu 14.04

Introduction

This tutorial is the third in a series about deploying PHP applications using Ansible on Ubuntu 14.04. The first tutorial covers the basic steps for deploying an application; the second tutorial covers more advanced topics such as databases, queue daemons, and task schedulers (crons).

In this tutorial, we will build on what we learned in the previous tutorials by transforming our single-application Ansible playbook into a playbook that supports deploying multiple PHP applications on one or multiple servers. This is the final piece of the puzzle when it comes to using Ansible to deploy your applications with minimal effort.

We will be using a couple of simple Lumen applications as part of our examples. However, these instructions can be easily modified to support other frameworks and applications if you already have your own. It is recommended that you use the example applications until you are comfortable making changes to the playbook.

Prerequisites

To follow this tutorial, you will need:

Two Droplets set up by following the first and second tutorials in this series.

A new (third) Ubuntu 14.04 Droplet set up like the original PHP Droplet in the first tutorial, with a sudo non-root user and SSH keys. This Droplet which will be used to show how to deploy multiple applications to multiple servers using one Ansible playbook. We'll refer to the IP addresses of the original PHP Droplet and this new PHP Droplet as your_first_server_ip and your_second_server_ip respectively.

An updated /etc/hosts file on your local computer with the following lines added. You can learn more about this file in step 6 of this tutorial.

The example websites we'll use in this tutorial are laravel.example.com, one.example.com, and two.example.com. If you want to use your own domain, you'll need to update your active DNS records instead.

Step 1 — Setting Playbook Variables

In this step, we will set up playbook variables to define our new applications.

In the previous tutorials, we hard-coded all of the configuration specifics, which is normal for many playbooks that perform specific tasks for a specific application. However, when you wish to support multiple applications or broaden the scope of your playbook, it no longer makes sense to hard code everything.

As we have seen before, Ansible provides variables which you can use in both your task definitions and file templates. What we haven't seen yet is how to manually set variables. In the top of your playbook, alongside the hosts and tasks parameters, you can define a vars parameter, and set your variables there.

If you haven't done so already, change directories into ansible-php from the previous tutorials.

cd ~/ansible-php/

Open up our existing playbook for editing.

nano php.yml

The top of the file should look like this:

Top of original php.yml

---
- hosts: php
sudo: yes
tasks:
. . .

To define variables, we can just add in a vars section in, alongside hosts, sudo, and tasks. To keep things simple, we will start with a very basic variable for the www-data user name, like so:

Updated vars in php.yml

---
- hosts: php
sudo: yes
vars:wwwuser: www-data
tasks:
. . .

Next, go through and update all occurrences of the www-data user with the new variable {{ wwwuser }}. This format should be familiar, as we have used it within looks and for lookups.

To find and replace using nano, press CTRL+\. You'll see a prompt which says Search (to replace):. Type www-data , then press ENTER. The prompt will change to Replace with:. Here, type {{ wwwuser }} and press ENTER again. Nano will take you through each instance of www-data and ask Replace this instanace?. You can press y to replace each one by one, or a to replace all.

Note: Make sure the variable declaration that we just added at the top isn't changed too. There should be 11 instances of www-data that need to be replaced.

Before we go any further, there is something we need to be careful of when it comes to variables. Normally we can just add them in like this, when they are within a longer line:

In your playbook, this needs to happen any time you have sudo_user: {{ wwwuser }}. You can use a global find and replace the same way, replacing sudo_user: {{ wwwuser }} with sudo_user: "{{ wwwuser }}". There should be four lines that need this change.

Once you have changed all occurrences, save and run the playbook:

ansible-playbook php.yml --ask-sudo-pass

There should be no changed tasks, which means that our wwwuser variable is working correctly.

Step 2 — Defining Nested Variables for Complex Configuration

In this section, we will look at nesting variables for complex configuration options.

In the previous step, we set up a basic variable. However, it is also possible to nest variables and define lists of variables. This provides the functionality we need to define the list of sites we wish to set up on our server.

First, let us consider the existing git repository that we have set up in our playbook:

We can extract the following useful pieces of information: name (directory), repository, branch, and domain. Because we are setting up multiple applications, we will also need a domain name for it to respond to. Here, we'll use laravel.example.com, but if you have your own domain, you can substitute it.

This results in the following four variables that we can define for this application:

If you run your playbook now (using ansible-playbook php.yml --ask-sudo-pass), nothing will change because we haven't yet set up our tasks to use our new applications variable yet. However, if you go to http://laravel.example.com/ in your browser, it should show our original application.

Step 3 — Looping Variables in Tasks

In this section we will learn how to loop through variable lists in tasks.

As mentioned previously, variable lists need looped over in each task that we wish to use them in. As we saw with the install packages task, we need to define a loop of items, and then apply the task for each item in the list.

Open up your playbook for editing:

nano php.yml

We will start with some easy tasks first. Around the middle of your playbook, you should find these two env tasks:

You will notice that they are currently hard-coded with the laravel directory. We want to update it to use the name property for each application. To do this we add in the with_items option to loop over our applications list. Within the task itself, we will swap out the laravel reference for the variable {{ item.name }}, which should be familiar from the formats we've used before.

Next, move down to the two Laravel artisan cron tasks. They can be updated exactly the same as we just did with the env tasks. We will also add in the item.name into the name parameter for the cron entries, as Ansible uses this field to uniquely identify each cron entry. If we left them as-is, we would not be able to have multiple sites on the same server as they would overwrite each over constantly and only the last one would be saved.

If you save and run the playbook now (using ansible-playbook php.yml --ask-sudo-pass), you should only see the two updated cron tasks as updated. This is due to the change in the name parameter. Apart from that, there have been no changes, and this means that our applications list is working as expected, and we have not yet made any changes to our server as a result of refactoring our playbook.

Step 4 — Applying Looped Variables in Templates

In this section we will cover how to use looped variables in templates.

Looping variables in templates is very easy. They can be used in exactly the same way that they are used in tasks, like all other variables. The complexity comes in when you consider file paths as well as variables, as in some uses we need to factor in the file name and even run other commands because of the new file.

In the case of Nginx, we need to create a new configuration file for each application, and tell Nginx that it should be enabled. We also want to remove our original /etc/nginx/sites-available/default configuration file in the process.

First, open up your playbook for editing:

nano php.yml

Find the Configure Nginx task (near the middle of the playbook), and update it as we have done with the other tasks:

While we are here, we will also add in two more tasks that were mentioned above. First, we will tell Nginx about our new site configuration file. This is done with a symlink between the sites-available and sites-enabled directories in /var/nginx/.

However, we haven't finished yet. Notice the default_server at the top? We want to only include that for the laravel application, to make it the default. To do this we can use a basic IF statement to check if item.name is equal to laravel, and if so, display default_server.

You should notice the Nginx tasks have been marked as changed. When it finishes running, refresh the site in your browser and it should be displaying the same as it did at the end of the last tutorial:

http://laravel.example.com/

Queue: YES
Cron: YES

Step 5 — Looping Multiple Variables Together

In this step we will loop multiple variables together in tasks.

Now it is time to tackle a more complex loop example, specifically registered variables. In order to support different states and prevent tasks from running needlessly, you will remember that we used register: cloned in our Clone git repository task to register the variable cloned with the state of the task. We then used when: cloned|changed in the following tasks to trigger tasks conditionally. Now we need to update these references to support the applications loop.

Now we need to update it to loop through bothapplications and cloned. This is done using the with_together option, and passing in both applications and cloned. As with_together loops through two variables, accessing items is done with item.#, where # is the index of the variable as it is defined. So for example:

with_together:
- list_one
- list_two

item.0 will refer to list_one, and item.1 will refer to list_two.

Which means that for applications we can access the properties via: item.0.name. For cloned we need to pass in the results from the tasks, which can be accessed via cloned.results, and then we can check if it was changed via item.1.changed.

There should be no changes from this run. However, we now have a registered variable working nicely within a loop.

Step 6 — Complex Registered Variables and Loops

In this section we will learn about more complicated registered variables and loops.

The most complicated part of the conversion is handling the registered variable we are using for password generation for our MySQL database. That said, there isn't much more that we have to do in this step that we haven't covered, we just need to update a number of tasks at once.

Open your playbook for editing:

nano php.yml

Find the MySQL tasks, and in our initial pass we will just add in the basic variables like we have done in previous tasks:

Next we will add in with_together so we can use our database password. For our password generation, we need to loop over dbpwd.results, and will be able to access the password from item.1.stdout, since applications will be accessed via item.0.

Despite all of the changes we've made to our playbook, there should be no changes to the database tasks. With the changes in this step, we should have finished our conversion from a single application playbook to a multiple application playbook.

Step 7 — Adding More Applications

In this step we will configure two more applications in our playbook.

Now that we have refactored our playbook to use variables to define the applications, the process for adding new applications to our server is very easy. Simply add them to the applications variable list. This is where the power of Ansible variables will really shine.

This step may take a while as composer sets up the new applications. When it has finished, you will notice a number of tasks are changed, and if you look carefully you'll notice that each of the looped items will be listed. The first, our original application should say ok or skipped, while the new two applications should say changed.

More importantly, if you visit all three of the domains for your configured sites in your web browser you should notice three different websites.

The first one should look familiar. The other two should display:

http://one.example.com/

This is example app one!

and

http://two.example.com/

This is example app two!

With that, we have just deployed two new web applications by simply updating our applications list.

Step 8 — Using Host Variables

In this step we will extract our variables to host variables.

Taking a step back, playbook variables are good, but what if we want to deploy different applications onto different servers using the same playbook? We could do conditional checks on each task to work out which server is running the task, or we can use host variables. Host variables are just what they sound like: variables that apply to a specific host, rather than all hosts across a playbook.

Host variables can be defined inline, within the hosts file, like we've done with the ansible_ssh_user variable, or they can be defined in dedicated file for each host within the host_vars directory.

Even though we have moved our variables from our playbook to our host file, the output should look exactly the same, and there should be no changes reported by Ansible. As you can see, host_vars work in the exact same way that vars in playbooks do; they are just specific to the host.

Variables defined in host_vars files will also be accessible across all playbooks that manage the server, which is useful for common options and settings. However, be careful not to use a common name that might mean different things across different playbooks.

Step 9 — Deploying Applications on Another Server

In this step we will utilize our new host files and deploy applications on a second server.

First, we need to update our hosts file with our new host. Open it for editing:

Ansible will take a while to run because it is setting everything up on your second server. When it has finished, open up your chosen applications in your browser (in the example, we used laravel.example2.comtwo.example2.com)and to confirm they have been set up correctly. You should see the specific applications that you picked for your host file, and your original server should have no changes.

Conclusion

This tutorial took a fully functioning single-application playbook and converted it to support multiple applications across multiple servers. Combined with the topics covered in the previous tutorials, you should have everything you need to write a full playbook for deploying your applications. As per the previous tutorials, we still have not logged directly into the servers using SSH.

You will have noticed how simple it was to add in more applications and another server, once we had the structure of the playbook worked out. This is the power of Ansible, and is what makes it so flexible and easy to use.

This series will show you how to set up an Ansible playbook that will automate your entire PHP application deployment process on Ubuntu 14.04.

April 13, 2015

This tutorial covers the process of provisioning a basic PHP application using Ansible. The goal at the end of this tutorial is to have your new web server serving a basic PHP application without a single SSH connection or manual command run on the target Droplet.

June 2, 2015

In this tutorial, we will cover setting up SSH keys to support code deployment/publishing tools, configuring the system firewall, provisioning and configuring the database (including the password), and setting up task schedulers (crons) and queue daemons. The goal at the end of this tutorial is for you to have a fully working PHP application server with the aforementioned advanced configuration.

June 28, 2015

This tutorial is the third in a series about deploying PHP applications using Ansible on Ubuntu 14.04. In this tutorial, we will build on what we learned in the previous tutorials by transforming our single-application Ansible playbook into a playbook that supports deploying multiple PHP applications on one or multiple servers.