Deploying a LAMP Stack Application with Cloudsoft AMP

The most common application stack for the web is the LAMP stack (Linux, Apache, MySQL and PHP).  Cloudsoft AMP makes deploying such applications using cloud providers such as AWS, IBM Bluebox, or Microsoft Azure easy.

In this article, we show how to write a simple application that is deployed to the cloud using Cloudsoft AMP.  Future blogs will cover in-life management such as automation of scaling and restarting.

Prerequisites

Blueprint

The LAMP blueprint describes what software should be installed and run on a VM. So we start by defining the set of services. Since we want to run Apache HTTP Server and MySQL on the same server, we choose the SameServerEntity type.

services:
- type: org.apache.brooklyn.entity.software.base.SameServerEntity

Its two children are a custom entity with the definition of the Apache HTTP Server and the pre-existing entity type MySqlNode, which is provided by Apache Brooklyn.

 Brooklyn.children:
  - type: org.apache.brooklyn.entity.database.mysql.MySqlNode
    id: mysql
  - type: org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess
    name: Apache httpd

To write the implementation of the custom entity we need to define a few lifecycle methods.  The first, install.command is used to install all of the packages we need for the httpd server and php. We also configure SELinux to allow connections to the database using the 127.0.0.1 loopback from PHP. (One of the benefits of this approach is that we can add this kind of esoteric command to our blueprint without any trouble.)

install.command: |
      sudo yum -y install httpd
      sudo yum -y install php
      sudo yum -y install php-mysql
      sudo yum -y install git
      sudo setsebool httpd_can_network_connect_db=1

In the customize.command method, we clone the git repo that contains our application, replace the environment variables, and copy these into the folder that the web server serves its content from.

customize.command: |
      git clone https://github.com/cloudsoft/demo-php-mysql-app.git
      cd demo-php-mysql-app
      sed -i "s|\${MYSQL_USERNAME}|${MYSQL_USERNAME}|" items.php
      sed -i "s|\${MYSQL_PASSWORD}|${MYSQL_PASSWORD}|" items.php
      sudo cp ./* /var/www/html/

The launch.command is the command that is used to run the web server, and is triggered after the installation is finished.

launch.command: |
      sudo systemctl start httpd.service

Since AMP manages the runtime state of the software, we populate the checkRunning.command with the command to check the status of the web server.

checkRunning.command: |
      sudo systemctl status httpd.service

Finally, we inject the environment with values from the MySQL entity, so that these values could be used by the customize.command above.

shell.env:
      MYSQL_USERNAME: $brooklyn:entity("mysql").attributeWhenReady("mysql.user")
      MYSQL_PASSWORD: $brooklyn:entity("mysql").attributeWhenReady("mysql.password") 

Web Application

To test out this blueprint we created a simple web app using JQuery to make frontend requests and PHP on the backend to query the database.

Html

The HTML of the application comprises a simple form and a div which will be populated by JQuery.

<body>
    <div class="container">
      <div class="row">
        <form style="text-align: center">
          <h2>List</h2>
          <input type="text" name="name" placeholder="item name">
          <input type="text" name= "description" placeholder="description">
          <button class="btn btn-primary">Add Item</button>
        </form>
      </div>
      <div class="row">
        <h2>Items</h2>
        <div id="result" class="list-group"></div>
      </div>
    </div> 
</body>

Javascript

The javascript we use is minimalist - using JQuery to make AJAX requests to the php backend. When the page loads a GET request is made to get the items in the database and append each item to the result div.

$(document).ready(function(){
  $.get("items.php", function(response){
    var items = JSON.parse(response)
    items.forEach(function(item){
      $("#result").append(makeTemplate(item));
    });
  });
});

When the button is clicked to add the item, a POST request is made to the php backend to add the item to the database, and the view is updated by appending the new item to the result div.

$(document).ready(function(){
  $("button").click(function(e){
    e.preventDefault();
    var values = $('form input').serialize();
    $.post("items.php", values, function (response) {
      var item = JSON.parse(response);
      $("#result").append(makeTemplate(item));
    });
  });
});

PHP Backend

On the back end, we handle the database queries. First establishing a connection, with the placeholder values for credentials replaced by AMP:

$servername = "127.0.0.1";
$username = "${MYSQL_USERNAME}";
$password = "${MYSQL_PASSWORD}";

$conn = new mysqli($servername, $username, $password);

if ($conn->connect_error) {
  die("Connection failed: " . $conn->connect_error);
}

And the test database selected, with the items table created if it doesn’t already exist:

if (!$conn->select_db('test')) {
  die("Couldn't select database test");
}

$createTable = "CREATE TABLE IF NOT EXISTS `items` (
    `id` int(11) unsigned NOT NULL auto_increment,
    `name` varchar(255) default '',
    `description` varchar(255) default '',
    PRIMARY KEY  (`ID`)
)";

if(!$conn->query($createTable)){
    die("Table creation failed: (" . $conn->errno . ") " . $conn->error);
}

We then handle the request returning json for the view to be rendered on the client-side:

$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
  case 'POST':
    echo json_encode(handlePost($conn));
    break;
  case 'GET':
    echo json_encode(handleGet($conn));
    break;
  default:
    break;
}

For the GET request we simply make a SELECT query to the database, building up an array to be passed back, where it will be serialized as json.

function handleGet($conn) {
  if ($result = $conn->query("SELECT name, description FROM items")) {
    if($result) {
      while ($row = $result->fetch_object()){
        $items_arr[] = $row;
      }
    }
    $result->close();
    return $items_arr;
  }
  return array();
}

Meanwhile the POST request sanitizes the user input, before adding to the database using an INSERT query.  Then the added values are returned, before again being serialized as json.

function handlePost($conn) {
  $name = $conn->real_escape_string($_POST['name']);
  $description = $conn->real_escape_string($_POST['description']);
  $conn->query("INSERT into items (name, description) VALUES ('$name', '$description')");
  if (!empty($conn->error)) {
    echo $conn->error;
  }
  return array('name' => $name, 'description' => $description);
}

Deploy

With the blueprint and application ready to go, we can use Cloudsoft AMP to deploy the blueprint to a location of our choice. (For more information on setting up locations, see http://docs.cloudsoft.io/locations/index.html).

Once the deployed application is running we can select the Application and look for the sensor host.address to visit our running webapp.

Conclusion

We have built a minimal web application using the LAMP stack and deployed to the cloud using Cloudsoft AMP.  The basic Blueprint comprised two elements: an Apache Brooklyn Entity for MySQL and a custom entity which installs the Apache HTTP server.  The web application itself was also simple (but a more complicated application would be deployed in the same way).  It displays items stored in on the MySQL server and has a simple form for adding new items, using JQuery for client-side rendering, and PHP to connect to the database on the server-side.