Creating a HipHop Virtual Machine (HHVM) Cartridge for Stratos 2.0

  • By Lakmal Warusawithana
  • 17 Jul, 2013

Table of contents

  1. Introduction
  2. What is a cartridge?
  3. Selecting the IaaS
  4. Setting up HHVM
  5. Setting up Stratos Cartridge Components
  6. Creating AMI for HHVM Cartridge
  7. Register with Cloud Controller
  8. Prepare Wordpress code for run in HHVM
  9. Deploying wordpress over HHVM Cartridge
  10. References

Introduction

HipHop Virtual Machine (HHVM) is a new open source virtual machine designed for executing programs written in PHP. HHVM’s JIT compiler allows it to execute PHP faster than Zend PHP, in most cases, unmodified. Facebook now uses HHVM as a faster replacement for hphpi, with plans to eventually use HHVM for all PHP execution. [1]

With the Stratos 2.0 release we have shipped PHP, MySQL, TOMCAT, and CARBON cartridges.  Here, we explain how to create a HHVM cartridge, registering with Cloud Controller, and we finally show how to deploy wordpress into a HHVM cartridge.

What is a cartridge?

A Cartridge is the core functional component of Stratos 2.0, which is pluggable. A Cartridge component can make use of core services provided by WSO2 Stratos 2.0 (e.g. auto-scaling, load-balancing, health monitoring, metering, billing, tenant provisioning, code deployment, identity management, and entitlement) to build a platform in which tenants can deploy their applications. In summary, the Stratos 2.0 Cartridge is a Cloud-aware platform environment, which extends legacy technologies into the Cloud and deliver Cloud benefits.

Selecting the IaaS

Stratos runs on top of an IaaS. With the Stratos 2.0 release, we have pre-configured Stratos AMIs that can simply run on top of AWS EC2. Hence, we explain this cartridge creation using EC2.

To create a cartridge, we need to have a Cloud-based images with respect to IaaS; for EC2 it's AMI. Here you can use the ami-23d9a94a Ubuntu 12.04 Cloud image, which is available in the US EAST (North Virginia) EC2 zone. You have to create at least an M1 Small instance because HHVM cannot run inside the T1 Micro Instance with 613MB RAM [2]

Setting up HHVM

First SSH to the created Instance.

ssh -i Downloads/lakmal-s2.pem ubuntu@ec2-50-19-56-52.compute-1.amazonaws.com

Let's use pre-built packages for setting up HHVM. [3] Add the hiphop repository to your /etc/apt/sources.list

deb http://dl.hiphop-php.com/ubuntu precise main

Also enable the universe component

deb http://archive.ubuntu.com/ubuntu precise main universe

Then update your package index

$ sudo apt-get update

Finally, you can install (or update) your package with the normal apt-get install process

$ sudo apt-get install hiphop-php

Then create a config file /etc/hhvm.hdf

Server {
 Port = 80
 SourceRoot = /var/www/
}

Eval {
 Jit = true
}
Log {
 Level = Error
 UseLogFile = true
 File = /var/log/hhvm/error.log
 Access {
   * {
     File = /var/log/hhvm/access.log
     Format = %h %l %u %t \"%r\" %>s %b
   }
 }
}

VirtualHost {
 * {
   Pattern = .*
   RewriteRules {
     dirindex {
       pattern = ^/(.*)/$
       to = $1/index.php
       qsa = true
     }
   }
 }
}

StaticFile {
 FilesMatch {
   * {
     pattern = .*\.(dll|exe)
     headers {
       * = Content-Disposition: attachment
     }
   }
 }
 Extensions {
   css = text/css
   gif = image/gif
   html = text/html
   jpe = image/jpeg
   jpeg = image/jpeg
   jpg = image/jpeg
   png = image/png
   tif = image/tiff
   tiff = image/tiff
   txt = text/plain
 }
}

Make sure you have created /var/www and /var/log/hhvm directories. Then, create a system user to run hiphop PHP server. For example, we can run the server as a web user (you do not need to provide a password because we are not going to login as this user).

adduser web

Thereafter, all installed software need to run HHVM. Now, we will continue with the Stratos configuration and convert this to a cartridge.

Setting up Stratos Cartridge Components

For the Stratos component, we need to install some system packages. git need to artifacts synchronization, libboost-all-dev need for compile thrift, which need to cartridge logging agent. xml-twig-tools need to extract git credentials from the response xml. We will explain these in detail in the relevant section.

sudo apt-get install git libboost-all-dev ruby xml-twig-tools make unzip

Here, we will create a bash script (wso2-cartridge-init.sh) with Stratos Configuration. The sub section below explain content of the wso2-cartridge-init.sh script.

Initialize variables

First, we initialize some variables, which we need in the later part of the script.

#!/bin/bash

# ----------------------------------------------------------------------------
#  Copyright 2005-2013 WSO2, Inc. http://www.wso2.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

# ----------------------------------------------------------------------------
export LOG=/var/log/wso2-cartridge.log
instance_path=/opt
PUBLIC_IP=""
SLEEP_DURATION=3

Extracting payload

In the Stratos, we would be passing some values through the payload. Therefore, we need to have some mechanism to get these payload values to our script. Note that you can add some comments, and these will be useful in the case of debug.

Here, we can get payload by using EC2 meta data services (http://169.254.169.254/latest/user-data. ) In Stratos, payload is passed as a zip file. In the following script, extract all payload variables and export as the environment variables.

if [ -d ${instance_path}/payload ]; then
   rm -fr ${instance_path}/payload
fi
echo "creating payload dir ... " >> $LOG
mkdir ${instance_path}/payload
echo "creating payload dir ... " >> $LOG
echo "payload dir created ... " >> $LOG
wget http://169.254.169.254/latest/user-data -O ${instance_path}/payload/payload.zip
echo "payload copied  ... "  >> $LOG
unzip -d ${instance_path}/payload ${instance_path}/payload/payload.zip
echo "unzipped..." >> $LOG
for i in `/usr/bin/ruby /opt/get-launch-params.rb`
do
   echo "exporting to bashrc $i ... " >> $LOG
   echo "export" ${i} >> /home/ubuntu/.bashrc
done
source /home/ubuntu/.bashrc

You can create /opt/get-launch-params.rb with following content. And make sure it's executable.

#! /usr/bin/ruby
### get-launch-params.rb
# The following script obtains the launch parameters from 
# the file /tmp/payload/launch-params, then parses out the 
# parameters for this instance by using the launch index
# of this particular EC2 instance.
#
# Pass the command the -e flag to output the instance 
# parameters as exports of shell variables. Any other 
# arguments are ignored.

def get_launch_params(launch_params_file)
 IO.readlines launch_params_file
end

export_stmt = ""

launch_params = get_launch_params(
 "/opt/payload/launch-params")

if launch_params.length > 0
 instance_params_str = launch_params[0]

 instance_params = instance_params_str.split(',')

 export_stmt = "export " if ARGV.length > 0 && ARGV.include?("-e")

 instance_params.each { |param|
   puts export_stmt + param
 }

end

Getting public IP

The following code will be assigned the public IP of the instance and exported as the environment variable.

echo "getting public ip from metadata service" >> $LOG

wget http://169.254.169.254/latest/meta-data/public-ipv4
files="`cat public-ipv4`"
if [[ -z ${files} ]]; then
   echo "getting public ip" >> $LOG
   for i in {1..30}
   do
     rm -f ./public-ipv4
     wget http://169.254.169.254/latest/meta-data/public-ipv4
     files="`cat public-ipv4`"
     if [ -z $files ]; then
         echo "Public ip is not yet assigned. Wait and continue for $i the time ..." >> $LOG
         sleep $SLEEP_DURATION
     else
         echo "Public ip assigned" >> $LOG
         break
     fi
   done
   if [ -z $files ]; then
     echo "Public ip is not yet assigned. So exit" >> $LOG
     exit 0
   fi
   for x in $files
   do
       PUBLIC_IP="$x"
   done
else
  PUBLIC_IP="$files"
fi
for i in `/usr/bin/ruby /opt/get-launch-params.rb`
do
   export ${i}
done
rm -f ./public-ipv4

Starting up HipHop server

To start up HipHop server add following to script

echo "Starting HipHop Server" >> $LOG
/usr/bin/hhvm --mode daemon --user web --config /etc/hhvm.hdf

Clustering with ELB through the agent

Now, we add a piece of coding to handle clustering with the ELB. To keep the cartridge light weight, we have the Stratos Agent running outside. We can join the ELB through the Stratos Agent. First, create request.xml. Note that many ${} variables comes from the environment variable, which we export from the payload.

mkdir -p  /etc/agent/conf

echo "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:agen=\"http://service.agent.cartridge.carbon.wso2.org\">
 <soapenv:Header/>
 <soapenv:Body>
    <agen:register>
       <registrant> 
          <alarmingLowerRate>${ALARMING_LOWER_RATE}</alarmingLowerRate>
          <alarmingUpperRate>${ALARMING_UPPER_RATE}</alarmingUpperRate>
          <hostName>${HOST_NAME}</hostName>
          <key>${KEY}</key>
         <maxInstanceCount>${MAX}</maxInstanceCount>
         <maxRequestsPerSecond>${MAX_REQUESTS_PER_SEC}</maxRequestsPerSecond>
         <minInstanceCount>${MIN}</minInstanceCount> " > /etc/agent/conf/request.xml
IFS='|' read -ra PT <<< "${PORTS}"
for i in "${PT[@]}"; do
IFS=':' read -ra PP <<< "$i"
echo "          <portMappings>
                       <primaryPort>${PP[1]}</primaryPort>
                       <proxyPort>${PP[2]}</proxyPort>
                       <type>${PP[0]}</type>
               </portMappings>">> /etc/agent/conf/request.xml
done
echo "          <remoteHost>${PUBLIC_IP}</remoteHost>
          <service>${SERVICE}</service>
          <remoteHost>${PUBLIC_IP}</remoteHost>
          <roundsToAverage>${ROUNDS_TO_AVERAGE}</roundsToAverage>
          <scaleDownFactor>${SCALE_DOWN_FACTOR}</scaleDownFactor>
          <tenantRange>${TENANT_RANGE}</tenantRange>
       </registrant>
    </agen:register>
 </soapenv:Body>
</soapenv:Envelope>
" >> /etc/agent/conf/request.xml

We can simply call CURL request to Stratos Agent to connect to ELB.

echo "Sending register request to Cartridge agent service" >> $LOG
curl -X POST -H "Content-Type: text/xml" -H "SOAPAction: urn:register" -d @/etc/agent/conf/request.xml -k --silent --output /dev/null "${CARTRIDGE_AGENT_EPR}"

Handle artifacts synchronization

Let's create repoinforequest.xml, which we will use to obtain credentials for git cloning and pulling.

echo "Creating repoinfo request  " >> $LOG
echo "TENANT_ID and SERVICE ${TENANT_ID} and ${SERVICE} " >> $LOG
set -- "${HOST_NAME}" 
IFS="."; declare -a Array=($*)
ALIAS="${Array[0]}"
echo "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://org.apache.axis2/xsd\">
  <soapenv:Header/>
  <soapenv:Body>
     <xsd:getRepositoryCredentials>
        <xsd:tenantId>${TENANT_ID}</xsd:tenantId>
        <xsd:cartridgeType>${SERVICE}</xsd:cartridgeType>
        <xsd:alias>${ALIAS}</xsd:alias>
     </xsd:getRepositoryCredentials>
  </soapenv:Body>
</soapenv:Envelope>" > /opt/repoinforequest.xml

Now, we add the code to handle git clone/pull.

echo "Git repo sync" >> $LOG

# If repo is available do a git pull, else clone
echo "#!/bin/bash
if [ -d \"${APP_PATH}/.git\" ]; then
   cd ${APP_PATH}
   
   curl -X POST -H \"Content-Type: text/xml\" -H \"SOAPAction: urn:getRepositoryCredentials\" -d @/opt/repoinforequest.xml --silent  \"${REPO_INFO_EPR}\" --insecure > /tmp/git.xml
  sed '1,5d' /tmp/git.xml > /tmp/git1.xml
  sed '2d' /tmp/git1.xml > /tmp/git.xml
  username=\`xml_grep 'ax29:userName' /tmp/git.xml --text_only\`
  password=\`xml_grep 'ax29:password' /tmp/git.xml --text_only\`
  repo=\`xml_grep 'ax29:url' /tmp/git.xml --text_only\`
  rm /tmp/git1.xml
  rm /tmp/git.xml
  url=\`echo \$repo |sed 's/http.*\/\///g' |sed 's/\:.*//g' |sed 's/\/.*//g'\`
  echo \"machine \${url} login \${username} password \${password}\" > ~/.netrc
  sudo echo \"machine \${url} login \${username} password \${password}\" > /root/.netrc
  chmod 600 ~/.netrc
  sudo chmod 600 /root/.netrc
  git config --global --bool --add http.sslVerify false
  sudo git pull
  rm ~/.netrc
  sudo rm /root/.netrc

  sudo chown -R web:web ${APP_PATH}
else
   sudo rm -f ${APP_PATH}/index.html
  curl -X POST -H \"Content-Type: text/xml\" -H \"SOAPAction: urn:getRepositoryCredentials\" -d @/opt/repoinforequest.xml --silent  \"${REPO_INFO_EPR}\" --insecure > /tmp/git.xml
  sed '1,5d' /tmp/git.xml > /tmp/git1.xml
  sed '2d' /tmp/git1.xml > /tmp/git.xml
  username=\`xml_grep 'ax29:userName' /tmp/git.xml --text_only\`
  password=\`xml_grep 'ax29:password' /tmp/git.xml --text_only\`
  repo=\`xml_grep 'ax29:url' /tmp/git.xml --text_only\`
  rm /tmp/git1.xml
  rm /tmp/git.xml
  url=\`echo \$repo |sed 's/http.*\/\///g' |sed 's/\:.*//g' |sed 's/\/.*//g'\`
  echo \"machine \${url} login \${username} password \${password}\" > ~/.netrc
  sudo echo \"machine \${url} login \${username} password \${password}\" > /root/.netrc
  chmod 600 ~/.netrc
  sudo chmod 600 /root/.netrc
  git config --global --bool --add http.sslVerify false
  sudo git clone \${repo} ${APP_PATH}
  rm ~/.netrc
  sudo rm /root/.netrc
  sudo chown -R web:web ${APP_PATH}   
fi" > /opt/git.sh
echo "File created.." >> $LOG
chmod 755 /opt/git.sh
echo "git cloning.." >> $LOG
/opt/git.sh

Configure log publisher

Before we configure the log publisher, we have to prepare our cartridge instance to work thrift. You can download the tested version from [4].

tar zxvf thrift-0.8.0.tar.gz
cd thrift-0.8.0
./configure
make
make install

Let's setup logging agent. It can be downloaded from [5]

cd /opt/
wget https://svn.wso2.org/repos/wso2/carbon/platform/branches/4.1.0/build/stratos2/setup/tools/cartridge_create/init_scripts\
/php/cartridge_data_publisher_1.0.2.zip
unzip cartridge_data_publisher_1.0.2.zip
cd cartridge_data_publisher_1.0.2
make

We have setup all the necessary software for log publisher to BAM. To add to startup script, note that ${} variables come from payload. Moreover, make sure that you have included the correct access and error log location.

echo "setting up logging conf" >> $LOG

echo "host:     ${BAM_IP}
thriftPort:     ${BAM_PORT}

#cartridge configs
cartridgeAlias:  ${CARTRIDGE_ALIAS}
tenantName:      ${HOST_NAME}
tenantId:        ${TENANT_ID}
localIP:         ${PUBLIC_IP}" > /opt/cartridge_data_publisher_1.0.2/conf/data_publisher.conf
echo "started loggin ........."
cd /opt/cartridge_data_publisher_1.0.2/dist/Debug/GNU-Linux-x86/
nohup ./cartridge_data_publisher_1.0.2 /var/log/hhvm/access.log /var/log/hhvm/error.log >> /var/log/wso2-data-publisher.log  &

Complete Startup Script

We have completed our startup script. If we put everything together, it will look like as shown below.

#!/bin/bash

# ----------------------------------------------------------------------------
#  Copyright 2005-2013 WSO2, Inc. http://www.wso2.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

# ----------------------------------------------------------------------------
export LOG=/var/log/wso2-cartridge.log
instance_path=/opt
PUBLIC_IP=""
SLEEP_DURATION=3
if [ -d ${instance_path}/payload ]; then
   rm -fr ${instance_path}/payload
fi
echo "creating payload dir ... " >> $LOG
mkdir ${instance_path}/payload
echo "creating payload dir ... " >> $LOG
echo "payload dir created ... " >> $LOG
wget http://169.254.169.254/latest/user-data -O ${instance_path}/payload/payload.zip
echo "payload copied  ... "  >> $LOG
unzip -d ${instance_path}/payload ${instance_path}/payload/payload.zip
echo "unzipped..." >> $LOG
for i in `/usr/bin/ruby /opt/get-launch-params.rb`
do
   echo "exporting to bashrc $i ... " >> $LOG
   echo "export" ${i} >> /home/ubuntu/.bashrc
done
source /home/ubuntu/.bashrc

echo "getting public ip from metadata service" >> $LOG

wget http://169.254.169.254/latest/meta-data/public-ipv4
files="`cat public-ipv4`"
if [[ -z ${files} ]]; then
   echo "getting public ip" >> $LOG
   for i in {1..30}
   do
     rm -f ./public-ipv4
     wget http://169.254.169.254/latest/meta-data/public-ipv4
     files="`cat public-ipv4`"
     if [ -z $files ]; then
         echo "Public ip is not yet assigned. Wait and continue for $i the time ..." >> $LOG
         sleep $SLEEP_DURATION
     else
         echo "Public ip assigned" >> $LOG
         break
     fi
   done
   if [ -z $files ]; then
     echo "Public ip is not yet assigned. So exit" >> $LOG
     exit 0
   fi
   for x in $files
   do
       PUBLIC_IP="$x"
   done
else
  PUBLIC_IP="$files"
fi
for i in `/usr/bin/ruby /opt/get-launch-params.rb`
do
   export ${i}
done
rm -f ./public-ipv4

echo "Starting HipHop Server" >> $LOG
/usr/bin/hhvm --mode daemon --user web --config /etc/hhvm.hdf

mkdir -p  /etc/agent/conf

echo "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:agen=\"http://service.agent.cartridge.carbon.wso2.org\">
 <soapenv:Header/>
 <soapenv:Body>
    <agen:register>
       <registrant> 
          <alarmingLowerRate>${ALARMING_LOWER_RATE}</alarmingLowerRate>
          <alarmingUpperRate>${ALARMING_UPPER_RATE}</alarmingUpperRate>
          <hostName>${HOST_NAME}</hostName>
          <key>${KEY}</key>
         <maxInstanceCount>${MAX}</maxInstanceCount>
         <maxRequestsPerSecond>${MAX_REQUESTS_PER_SEC}</maxRequestsPerSecond>
         <minInstanceCount>${MIN}</minInstanceCount> " > /etc/agent/conf/request.xml
IFS='|' read -ra PT <<< "${PORTS}"
for i in "${PT[@]}"; do
IFS=':' read -ra PP <<< "$i"
echo "          <portMappings>
                       <primaryPort>${PP[1]}</primaryPort>
                       <proxyPort>${PP[2]}</proxyPort>
                       <type>${PP[0]}</type>
               </portMappings>">> /etc/agent/conf/request.xml
done
echo "          <remoteHost>${PUBLIC_IP}</remoteHost>
          <service>${SERVICE}</service>
          <remoteHost>${PUBLIC_IP}</remoteHost>
          <roundsToAverage>${ROUNDS_TO_AVERAGE}</roundsToAverage>
          <scaleDownFactor>${SCALE_DOWN_FACTOR}</scaleDownFactor>
          <tenantRange>${TENANT_RANGE}</tenantRange>
       </registrant>
    </agen:register>
 </soapenv:Body>
</soapenv:Envelope>
" >> /etc/agent/conf/request.xml

echo "Sending register request to Cartridge agent service" >> $LOG
curl -X POST -H "Content-Type: text/xml" -H "SOAPAction: urn:register" -d @/etc/agent/conf/request.xml -k --silent --output /dev/null 
"${CARTRIDGE_AGENT_EPR}"

echo "Creating repoinfo request  " >> $LOG
echo "TENANT_ID and SERVICE ${TENANT_ID} and ${SERVICE} " >> $LOG
set -- "${HOST_NAME}" 
IFS="."; declare -a Array=($*)
ALIAS="${Array[0]}"
echo "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://org.apache.axis2/xsd\">
  <soapenv:Header/>
  <soapenv:Body>
     <xsd:getRepositoryCredentials>
        <xsd:tenantId>${TENANT_ID}</xsd:tenantId>
        <xsd:cartridgeType>${SERVICE}</xsd:cartridgeType>
        <xsd:alias>${ALIAS}</xsd:alias>
     </xsd:getRepositoryCredentials>
  </soapenv:Body>
</soapenv:Envelope>" > /opt/repoinforequest.xml

echo "Git repo sync" >> $LOG

# If repo is available do a git pull, else clone
echo "#!/bin/bash
if [ -d \"${APP_PATH}/.git\" ]; then
   cd ${APP_PATH}
   
   curl -X POST -H \"Content-Type: text/xml\" -H \"SOAPAction: urn:getRepositoryCredentials\" -d @/opt/repoinforequest.xml --silent
  \"${REPO_INFO_EPR}\" --insecure > /tmp/git.xml
  sed '1,5d' /tmp/git.xml > /tmp/git1.xml
  sed '2d' /tmp/git1.xml > /tmp/git.xml
  username=\`xml_grep 'ax29:userName' /tmp/git.xml --text_only\`
  password=\`xml_grep 'ax29:password' /tmp/git.xml --text_only\`
  repo=\`xml_grep 'ax29:url' /tmp/git.xml --text_only\`
  rm /tmp/git1.xml
  rm /tmp/git.xml
  url=\`echo \$repo |sed 's/http.*\/\///g' |sed 's/\:.*//g' |sed 's/\/.*//g'\`
  echo \"machine \${url} login \${username} password \${password}\" > ~/.netrc
  sudo echo \"machine \${url} login \${username} password \${password}\" > /root/.netrc
  chmod 600 ~/.netrc
  sudo chmod 600 /root/.netrc
  git config --global --bool --add http.sslVerify false
  sudo git pull
  rm ~/.netrc
  sudo rm /root/.netrc

  sudo chown -R web:web ${APP_PATH}
else
   sudo rm -f ${APP_PATH}/index.html
  curl -X POST -H \"Content-Type: text/xml\" -H \"SOAPAction: urn:getRepositoryCredentials\" -d @/opt/repoinforequest.xml --silent  
\"${REPO_INFO_EPR}\" --insecure > /tmp/git.xml
  sed '1,5d' /tmp/git.xml > /tmp/git1.xml
  sed '2d' /tmp/git1.xml > /tmp/git.xml
  username=\`xml_grep 'ax29:userName' /tmp/git.xml --text_only\`
  password=\`xml_grep 'ax29:password' /tmp/git.xml --text_only\`
  repo=\`xml_grep 'ax29:url' /tmp/git.xml --text_only\`
  rm /tmp/git1.xml
  rm /tmp/git.xml
  url=\`echo \$repo |sed 's/http.*\/\///g' |sed 's/\:.*//g' |sed 's/\/.*//g'\`
  echo \"machine \${url} login \${username} password \${password}\" > ~/.netrc
  sudo echo \"machine \${url} login \${username} password \${password}\" > /root/.netrc
  chmod 600 ~/.netrc
  sudo chmod 600 /root/.netrc
  git config --global --bool --add http.sslVerify false
  sudo git clone \${repo} ${APP_PATH}
  rm ~/.netrc
  sudo rm /root/.netrc

  sudo chown -R web:web ${APP_PATH}
   
fi" > /opt/git.sh
echo "File created.." >> $LOG
chmod 755 /opt/git.sh
echo "git cloning.." >> $LOG
/opt/git.sh

echo "setting up logging conf" >> $LOG

echo "host:     ${BAM_IP}
thriftPort:     ${BAM_PORT}

#cartridge configs
cartridgeAlias:  ${CARTRIDGE_ALIAS}
tenantName:      ${HOST_NAME}
tenantId:        ${TENANT_ID}
localIP:         ${PUBLIC_IP}" > /opt/cartridge_data_publisher_1.0.2/conf/data_publisher.conf
echo "started loggin ........."
cd /opt/cartridge_data_publisher_1.0.2/dist/Debug/GNU-Linux-x86/
nohup ./cartridge_data_publisher_1.0.2 /var/log/hhvm/access.log /var/log/hhvm/error.log >> /var/log/wso2-data-publisher.log  &

Make the script (wso2-cartridge-init.sh) executable Add entry to /etc/rc.local to run it in the startup. Then it should look like below

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

chmod 755 /opt/wso2-cartridge-init.sh
/opt/wso2-cartridge-init.sh > /var/log/wso2-openstack-init.log

exit 0

We have now completed the cartridge setup. Now, make an AMI out of this instance.

Creating AMI for HHVM cartridge

Go to your AWS EC2 management console and select the created instance, click on action, and then select Create AMI.

If you wish, you can make it a public image, so others can use it as well. Here, my HHVM cartridge ami id is ami-a4c2b0cd

Register with Cloud Controller

First, you need to follow [6] and setup Stratos on EC2. Now, we can add our new HHVM cartridge. We have to create cartridge xml. (please note that we have configured our Stratos domain as stratos2-demo.com). Make sure you have changed the highlighted values according to your environments. AMI id is the HHVM cartridge ID that you have created above.

<!-- Use below section to specify properties that are needed in order to start Cartridges.  -->
   <cartridges>

       <!-- You can have 1..n cartridge elements. -->
       <cartridge type="hhvm" host="hhvm.stratos2-demo.com" provider="hhvm" version="5.5" multiTenant="false">
           <!-- cartridge element can have 0..n properties, and they'll be overwritten by the properties
                specified under iaasProvider child elements of cartridge element. -->

           <displayName>HHVM</displayName>
           <description>HHVM Cartridge</description>
           <!-- A cartridge element should add a reference to an existing IaaS provider (specified
                in the above &lt;iaasProviders&gt; section) or it can create a completely new IaaS
                Provider (which should have a unique "type" attribute. -->
           <iaasProvider type="ec2" >
               <imageId>us-east-1/ami-a4c2b0cd</imageId>
               <maxInstanceLimit>250</maxInstanceLimit>
               <property name="instanceType" value="m1.small"/>
           </iaasProvider>
           <deployment baseDir="/var/www">
               <dir>www=copy#app#files#here</dir>
           </deployment>
          <portMapping>
              <http port="80" proxyPort="8280"/>
          </portMapping>
       </cartridge>
   </cartridges>

Create hhvm.xml with above content and copy to the following folder

/opt/wso2cc-1.0.1/repository/deployment/server/cartridges/

Now HHVM cartridge are ready to used.

Prepare Wordpress code for run in HHVM

There are some small code changes that have to be made to wordpress to run on HHVM. You can find more details on [7]. Here, we have a git repository [8], which has applied the above changes and you can directly used this.  

Deploying wordpress over HHVM Cartridge

First, you have to create a tenant on Stratos. Please follow [9] to created a tenant. Then log-in using created tenant and go to Main->Available Cartridge->Single-Tenant Cartridge. Now, you can find the newly registered HHVM cartridge.

Let's subscribe to the HHVM Cartridge.

Set up your /etc/hosts file to access the subscribed cartridge. Our sample looks like as shown below. 

54.227.40.254   website.hhvm.stratos2-demo.com

After subscription, the status become ACTIVE - try accessing the url (make sure to append index.php)

http://website.hhvm.stratos2-demo.com:8280/index.php

It should load the wordpress installation page. For wordpress, you need to configure MySQL database. You can subscribe to the MySQL cartridge to setup the database.

Add relevant host entries. In my sample, it look like below 54.227.40.254 webdb.mysql.stratos2-demo.com You can get MySQL credentials by clicking on the subscribe MySQL alias

Log in to Access URL (in my sample http://webdb.mysql.stratos2-demo.com:8280 ) and create a database for wordpress.

Then, continue with the wordpress installation.

Finally, you will have wordpress running inside the HHVM cartridge. Likewise, you can deploy any HipHop compatible PHP application on top of this HHVM cartridge.

References

  1. https://www.facebook.com/note.php?note_id=10150415177928920
  2. http://www.swageroo.com/wordpress/hiphop-vm-on-ec2-could-not-allocate-1210089471-bytes-for-translation-cache/
  3. https://github.com/facebook/hiphop-php/wiki
  4. https://svn.wso2.org/repos/wso2/carbon/platform/branches/4.1.0/build/stratos2/setup/tools/cartridge_create/init_scripts/php/thrift-0.8.0.tar.gz
  5. https://svn.wso2.org/repos/wso2/carbon/platform/branches/4.1.0/build/stratos2/setup/tools/cartridge_create/init_scripts/php/cartridge_data_publisher_1.0.2.zip
  6. http://docs.wso2.org/wiki/display/Stratos200/Quick+Start+Guide#QuickStartGuide-Stratos2setupwithEC2astheIaaS
  7. http://www.hiphop-php.com/wp/?p=113
  8. https://github.com/lakwarus/wordpresshhvm
  9. http://docs.wso2.org/wiki/display/Stratos200/GUI+User+Guide

Author

Lakmal Warusawithana, Software Architect, WSO2 Inc.

About Author

  • Lakmal Warusawithana
  • Senior Director - Cloud Architecture
  • wso2 inc