Customizing a Windows Azure PHP Worker Role

I was recently asked if it is possible to run PHP 5.4 in a Windows Azure Worker Role. (The default scaffold for a PHP worker role currently installs a version of PHP 5.3.) This got me to wondering how you might use any custom PHP installation in a worker role. The short answer is, yes, you can use ay custom PHP installation in a worker role – in this post I’ll walk you through the steps for doing so. However, a disclaimer first: my investigations here raised a few questions I haven’t answered yet, so depending on what you want to do, this post may/or may not be helpful.

Note: The Windows Azure team is looking at ways to enhance the tooling so that you can choose a specific version of PHP and add/enable extensions all from the command line prior to deployment. This post is simply meant to show you what you need to do before that tooling becomes available. Of course, we’d like to hear feedback about what you would like to see in the tooling.

Anyway, here’s what I did to get my local installation of PHP 5.4 running in a worker role…

1. I used the Windows Azure Powershell cmdlets to create a new Windows Azure Service, add a PHP worker role, and enable remote desktop access:

Screen Shot 2012-08-10 at 3.33.48 PM

2. I added my local PHP 5.4 installation to the WorkerRole1 directory. I also edited the php.ini file to make sure all paths referenced in the file were relative, not absolute (e.g. include_path=".\ext\" instead of "C:\php54\ext\").

Note: Here is where some questions arise. Some PHP extensions may require dependencies that aren’t installed by default. For example, the sqlsrv extension requires SQL Server Native Client. In this case, I could install the dependency using the Web Platform Installer, but other dependencies may require other methods.

3. I deleted all other files from the WorkerRole1 directory except for the setup_worker.cmd file. This script is called when the role is started, and I changed it so that it will add my PHP installation to the Path environment variable.

 @echo off
  
 echo Granting permissions for Network Service to the deployment directory...
 icacls . /grant "Users":(OI)(CI)F
 if %ERRORLEVEL% neq 0 goto error
 echo OK
  
 echo SUCCESS
  
 setx Path "%PATH%;%~dp0php54" /M
  
 exit /b 0
  
 :error
  
 echo FAILED
 exit /b -1

4. I added two files to the WorkerRole1 folder: php_entry.cmd and entry.php. The first file (php_entry.cmd) will be the program entry point for the role (I’ll configure this in a later step). The only thing that this file does is call the entry.php script. It’s contents are simply this:

 php entry.php

The entry.php script is just an example of some long-running script that might actually do something interesting in a “real” scenario. In my case this is just a proof of concept, so I just open and write to a file once every minute:

 <?php
  
 $h = fopen("output.txt", "a+");
  
 while(true){
     fwrite($h, "Hello world!");
     sleep(60);
 }
 ?>

My WorkerRole1 directory now looks like this:

Screen Shot 2012-08-10 at 4.44.50 PM

5. Finally, publish the project with the Powershell cmdlets: Publish-AzureServiceProject

It will take a few minutes to publish the project, but when it is ready, you should be able to login to it via remote desktop…

Screen Shot 2012-08-10 at 4.58.04 PM

…and find your way to the approot directory to see that your long-running php script is writing to the file as expected. (Of course, your project will do something interesting.)

One last thing to consider: By default, port 80 is open on a PHP Worker Role. Here’s the entry in the ServiceDefinition.csdef file:

 <Endpoints>
     <InputEndpoint name="HttpIn" protocol="tcp" port="80" />
 </Endpoints>

Depending on what your worker role does, you may want to close this endpoint. To allow other Azure services to communicate with your worker role, you may want to open other endpoints. For more information, see How to: Enable Role Communication.

The approach to using a custom PHP installation for a web role is similar to that for a worker role. The steps are basically…

1. Use the Powershell tools to create a new project and PHP Web Role.

2. Add your PHP installation to the WebRole/bin directory.

3. Modify the startup script (setup_web.cmd) to configure IIS (this configures IIS to run PHP from my php54 directory):

 SET PHP_FULL_PATH=%~dp0php54\php-cgi.exe 
 SET NEW_PATH=%PATH%;%RoleRoot%\base\x86 
 %WINDIR%\system32\inetsrv\appcmd.exe set config -section:system.webServer/fastCgi /+"[fullPath='%PHP_FULL_PATH%',maxInstances='12',idleTimeout='60000',activityTimeout='3600',requestTimeout='60000',instanceMaxRequests='10000',protocol='NamedPipe',flushNamedPipe='False']" /commit:apphost 
 %WINDIR%\system32\inetsrv\appcmd.exe set config -section:system.webServer/fastCgi /+"[fullPath='%PHP_FULL_PATH%'].environmentVariables.[name='PATH',value='%NEW_PATH%']" /commit:apphost 
 %WINDIR%\system32\inetsrv\appcmd.exe set config -section:system.webServer/fastCgi /+"[fullPath='%PHP_FULL_PATH%'].environmentVariables.[name='PHP_FCGI_MAX_REQUESTS',value='10000']" /commit:apphost 
 %WINDIR%\system32\inetsrv\appcmd.exe set config -section:system.webServer/handlers /+"[name='PHP',path='*.php',verb='GET,HEAD,POST',modules='FastCgiModule',scriptProcessor='%PHP_FULL_PATH%',resourceType='Either',requireAccess='Script']" /commit:apphost 
 %WINDIR%\system32\inetsrv\appcmd.exe set config -section:system.webServer/fastCgi /"[fullPath='%PHP_FULL_PATH%'].queueLength:50000"

In this case, you will likely want to leave port 80 open.

That’s it. As usual, feedback in the comments is appreciated.

Thanks.

-Brian