Thursday, January 10, 2013

Learning Cloud Foundry PHP staging and deployment

Background

VMWare Cloud Foudry is an open source Platform for PaaS (platform as a service). I see cloud foundry as a tool to simplify multiple application deployment in multiple servers. This post will describe things I found by reading source code of vcap (Vmware Cloud Application Platform) at github here and here.
My point of interest is php deployment capability that exists on vcap, which are contributed by phpfog developers.
My previous exploration of Cloud Foundry resulted in this picture below, which describe  my current understanding of the Cloud Foundry platform.


Starting point

The starting point of php support is given an interesting commit I had seen before, where phpfog team implements php functionality. At first it took me about 10 minutes browsing the vcap network graph (https://github.com/cloudfoundry/vcap/network), then I just realized there is a distinct phpfog branch in the vcap git.. 
The interesting commit is titled 'Support for deploying PHP applications through a standard Apache configuration with built-in support for APC, memcache, mongo and redis', authored by 'cardmagic' about 2 years ago (see the commit in github here). 

Staging

PHP applications are deployed using the vmc client. For now I just ignore the client part. The client communicates with the cloud controller, which in turn will command the DEA (Droplet Execution Agent) to deploy applications. DEA will execute the start_droplet function, which will invoke the correct staging plugin associated with the application's runtime.
[maybe I would research further on the relation between start_droplet and plugins]

The PHP plugin (ref) prepares the application by executing this ruby fragment  :
 def stage_application
    Dir.chdir(destination_directory) do
      create_app_directories
      Apache.prepare(destination_directory)
      copy_source_files
      create_startup_script
    end
  end

I am no Ruby programmer, so I sure hope I read this correctly..
At first, the plugin changes the current directory into droplet instance directory. In there, it calls the method create_app_directories (which I guess would create some required directories in there). Then it calls prepare method of the Apache class. Reading apache.rb, we know what the Apache::prepare does is that it copies apache.zip from the plugin directory and extracts it into the droplet instance directory. The apache.zip consists of configuration directories of an apache httpd server, with some modification so it honors several environment variable that would be injected in apache/envvars below. Generate_apache_conf script is also being copied from the plugin resource directory.
I guess the copy_source_files method would copy the application source codes into the droplet instance directory.
After that, startup script will be created using startup_script method :
 def startup_script
    vars = environment_hash
    generate_startup_script(vars) do
      <<PHPEOF
env > env.log
ruby resources/generate_apache_conf $VCAP_APP_PORT $HOME $VCAP_SERVICES
PHPEOF
    end
  end
  Which conveniently executes generate_apache_conf script, which in turn will create some apache configuration files and a shell script based on application parameters. The files are :
  1. apache/sites-available/default, which defines DocumentRoot, ErrorLog file, log format, and VCAP_SERVICES environment variable
  2. apache/envvars, which defines apache user, group, pid file, base directory
  3. apache/ports.conf, which define the port where apache listens
  4. apache/start.sh, which is the script that would start the apache server in the droplet directory with the created configuration files

 Running the App

 Two methods in php plugin tells us how the platform starts the application :
  # The Apache start script runs from the root of the staged application.
  def change_directory_for_start
    "cd apache"
  end
  def start_command
    "bash ./start.sh"
  end
So it starts the application by running apache/start.sh that is created by the previous generate_apache_conf script.

The Most Current Version 

I try to look for the latest lib/vcap/staging/plugin/php/plugin.rb file, at first  I found none, because it is already migrated from vcap repository to vcap-staging repository. Refer here to the newer version.
I notice an improvement which would allow us to define application memory allocated to the PHP application and also a stop command which invoke kill -9 :

  def stop_command
    cmds = []
    cmds << "CHILDPIDS=$(pgrep -P ${1} -d ' ')"
    cmds << "kill -9 ${1}"
    cmds << "for CPID in ${CHILDPIDS};do"
    cmds << " kill -9 ${CPID}"
    cmds << "done"
    cmds.join("\n")
  end
  private
  def startup_script
    generate_startup_script do
      <<- span="span">PHPEOF
env > env.log
ruby resources/generate_apache_conf $VCAP_APP_PORT $HOME $VCAP_SERVICES #{application_memory}m
PHPEOF
    end
  end

The kill -9 thing really handy because in numerous ocassions I am forced to do such command manually to stop a stuck/hung php process. The generate_apache_conf script is enhanced to create an additional php configuration file (apache/php/memory.ini) which impose a memory limit:
output_path = 'apache/php/memory.ini'
template = <<- span="span">ERB
memory_limit = <%= php_ram %>
ERB

That tells us that memory limitiation is for a single apache/PHP process. Collective application memory usage can be determined by n * (php_ram + x) where n is the amount of apache process running and x is the memory used by apache on its own. That makes me wonder about max client in apache's configuration (in apache.zip), here is the latest version :

    StartServers          5
    MinSpareServers       5
    MaxSpareServers      10
    MaxClients          150
    MaxRequestsPerChild   0

The configuration fragment above essentially says that running apache process could be between 5 to 150 child processes, and typically 10 during idle.
 
There is also an additional line in stage_application method to copy php configuration files too :
system "cp -a #{File.join(resource_dir, "conf.d", "*")} apache/php"
This environment variable export in generate_apache_conf script enables the apache/php directory to contain php configuration files :
export PHP_INI_SCAN_DIR=$APACHE_BASEDIR/php

 Finishing remarks

I hope by reading this will allow us to customize Cloud Foundry's PHP support as needed. I might need to add additional php extensions, that must be inserted into php's conf.d directory (shown above copied from the resource directory). And also it might be interesting to implement a method to change  MaxClients from the cloud API

No comments: