Programming

Windows Azure: Custom PHP installation

I write this article because of an odyssey of bug tracing concerning my PHP application hosted in the Windows Azure cloud.

Before the Windows Azure SDK 1.6 the configuration of PHP with Fast CGI was done in the Web.config. Here you could define the exact path of your custom PHP.

Now with the SDK 1.6 and the different folder structure defined by the scaffolder, the integration of PHP has changed. The new way is to install PHP automatically by the predefined startup scripts. So you don’t need to put the whole PHP stuff into your package. You just add your php.ini modifications and maybe your special PHP extension DLLs.

Now, when you deploy your package the startup scripts will add some environment variables,  install PHP53, SQLDriverPHP53IIS and PHPManager.

That’s all quite cool, but last week my live application crashed (WORST CASE!) without any modifications by my side. I could not find the problem. A few days ago I recognized that on the compute instances runs a different PHP version than on my local dev environment. I could not figure out how this could happen. But since I know that the startup script uses webpicmdline (Web Plattform Installer for Commandline) to install the PHP stuff (s.o.) it’s clear to me. The WebPI always takes the newest PHP version for installation, and it’s not possible to set up a specific version number. In my case in need PHP 5.3.8 but WebPI installs PHP 5.3.9 which crashes my application with an ugly PHP53_via_FastCGI Error Code 0xc0000005.

Ok, how can I tell Azure to take my PHP version. The only way is to again put the whole PHP folder into the deployment package as explained in this tutorial.

I for my use case I needed to do it a little bit different:

  • I put the PHP folder into MyProject\WebRole\bin\PHP\v5.3\php-cgi.exe
  • The extensions are in MyProject\WebRole\bin\PHP\v5.3\ext\
  • In configureIIS.cmd I changed SET PHP_FULL_PATH=%~dp0php\php-cgi.exe to SET PHP_FULL_PATH=%~dp0PHP\v5.3\php-cgi.exe
  • To add the environment variables I leave add-environment-variables.cmd as it was
  • To set user permission I leave monitor-environment.cmd as it was

These changes bring some small losings (with which I can live):

  • SQLDriverPHP53IIS needs to be installed manually (ext\php_sqlsrv_53_nts_vc9.dll [2]).
  • No IIS PHPManager (Maybe it’s possible to install it by modifing the install-php.cmd).

The startup script part in my ServiceDefinition.csdef now looks like:

    <Startup>
      <Task commandLine="add-environment-variables.cmd" executionContext="elevated" taskType="simple" />
      <Task commandLine="installCustomPHP.cmd" executionContext="elevated" taskType="simple">
        <Environment>
          <Variable name="EMULATED">
            <RoleInstanceValue xpath="/RoleEnvironment/Deployment/@emulated" />
          </Variable>
        </Environment>
      </Task>
      <Task commandLine="monitor-environment.cmd" executionContext="elevated" taskType="background" />
    </Startup>

I renamed the configureIIS.cmd from the tutorial to installCustomPHP.cmd.

One thing: It very strange to make the experience as Azure PHP developer that there are sometime changes within the Azure Plattform which are crashing my live application. And I have no chance to get warned/informed early enough to fix the problems before my customers get a blank page.

Corrections, tips and hints are welcome :)

Links:


Windows Azure SDK 1.6 Update & Windows Azure for PHP

Windows Azure came up with a new SDK update “Windows Azure SDK 1.6″.

In our case it brought a lot of hair tearing chances with it. Because so far we developed our PHP application with eclispe and the out-dated  windowsazue4e Plugin.

Eclipse presetting for the folder structure was like this:

C:\WindowsAzurePHPApp
|_ ServiceDefinition.csdef
|_ ServiceConfiguraion.cscfg
|_ ServiceDefinition.csx (Development approot folder)
|_ WindowsAzurePHPApp.cspkg (Package file)

C:\WindowsAzurePHPApp_WebRole
|_ php
|_ Web.config
|_ Web.roleconfig
|_ index.php

Whereas the new scaffolding advices the following structure:

C:\WindowsAzurePHPApp
|_ ServiceDefinition.csdef
|_ ServiceConfiguraion.cscfg
|_ WebRole
|_ build
|    |_ [Local dev. package files]
|_ pack [Package files]
|    |_ WindowsAzurePHPApp.cspkg
|    |_ ServiceConfiguraion.cscfg
|_ php
|    |_ [The whole PHP libs]
|_ bin
|    |_ [Start up scripts]
|_ index.php

Command to run the development build:

package create -in="C:\temp\WindowsAzurePHPApp"
   -out="C:\temp\WindowsAzurePHPApp\build" -dev=true

Command to generate the cloud package:

package create -in="C:\temp\WindowsAzurePHPApp"
   -out="C:\temp\WindowsAzurePHPApp\pack" -dev=false

 

Preparing everything:

  1. Get the Windows Azure SDK
    The best way is it to use the Windows Web Platform Installer. Follow step by step the instructions here.
  2. Get the new WindowsAzure for PHP SDK.
    Set it up step by step using the instructions here.
    If you use classes from this SDK within your project you should also put a copy into your project itself.

What to change after the SDK update:

  1. Generate a sample project:
    To have a clean sample project for copying the needed parts to the our project we generate it by using the new scaffoldig functionality of the WindowsAzure for PHP SDK:
    Open the command line and run: 

    scaffolder run -out="C:\temp\WindowsAzurePHPApp"

    Now we have clean and simple sample project.
    Test everything by following these steps.

  2. Move the sources into the new folder structure:
    Rebuild the sample structure for our project and move your files into it.  

    • Replace the php-folder with the php folder in c:\program files\PHP\v5.3 (installed with the Web Platform Installer) and edit the php.ini with your settings.
      Set the xtension directory to: 

      extension_dir="ext"
    • Replace the old bin-folder with the bin folder of the sample project.
    • Edit the PHP-paths within cmd-scripts in the bin-folder.

  3. ServiceDefinition.csdef configuration:
    • Add the Sites-Element.
    • Add the Startup-Element for the PHP installation.
    <?xml version="1.0" encoding="UTF-8"?>
    <ServiceDefinition name="WindowsAzurePHPApp" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
      <WebRole enableNativeCodeExecution="true" name="WebRole" vmsize="ExtraSmall">
        <Sites>
          <Site name="Web" physicalDirectory="./WebRole">
            <Bindings>
              <Binding name="HttpIn" endpointName="HttpIn" />
            </Bindings>
          </Site>
        </Sites>
        <Startup>
          <Task commandLine="add-environment-variables.cmd" executionContext="elevated" taskType="simple" />
          <Task commandLine="install-php.cmd" executionContext="elevated" taskType="simple">
            <Environment>
              <Variable name="EMULATED">
                <RoleInstanceValue xpath="/RoleEnvironment/Deployment/@emulated" />
              </Variable>
            </Environment>
          </Task>
          <Task commandLine="monitor-environment.cmd" executionContext="elevated" taskType="background" />
        </Startup>
        <InputEndpoints>
          <InputEndpoint name="HttpIn" port="80" protocol="http"/>
        </InputEndpoints>
        <ConfigurationSettings>
          ...
        </ConfigurationSettings>
      </WebRole>
    </ServiceDefinition>
  4. ServiceConfiguraion.cscfg configuration:
    Add the osFamily and osVersion attributes: 

    <ServiceConfiguration serviceName="Stats" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="2" osVersion="*">
    ...
    </ServiceConfiguration>
  5. Web.config configuration:
    • Remove the Handlers-Element for PHP via FastCGI (By now on this will be handled by the startup scripts)

 

Important changes with the new SDK:

  • $_SERVER['RoleRoot'] doesn’t exist anymore.
  • $_SERVER['INSTANCE_ID'] doesn’t contain a local Instance number like “1″ or “2″ anymore. It seams that it’s now a global number like “16325987″.
    Alternativly you can use [RoleInstanceID] => WebRole_IN_0.
  • $_SERVER['TEMP'] is not the same drive like the accessable local storage any more, so you can’t use it to locate the current letter of the accessable this drive. I don’t know if it’s always “c:\“.
  • The PHP method azure_getconfig(‘StorageAccountName’) does not exist anymore. This method could be used to get the setting values of the ServiceConfiguration.cscnf.
    Even if I copy the php_azure.dll of the old project into the new one and enabling the extension (extension=php_azure.dll) in th ephp.ini, it does not work.
  • The file Web.roleconfig is deprecated.

 

Links:


Symfony 1.4: Doctrine: There is no open connection

When you have this problem just go into your controler e.g. index.php and add the following line:


$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'test', true);
new sfDatabaseManager($configuration);


Symfony: Instruct browser to refresh CSS and JS files

In Symfony the stylesheets and javascript files are defined in the view.yml. Sometimes the problem appears that the browsers cache these files, even if there where some changes in the last release without refreshing the new contents.

There is a common technique to instruct the browser to take the new file version: Browsers store different file versions for different GET paramters. So if you include a CSS file like this:

<script type="text/javascript" src="css/styles.css?v=21"></script>

and in the next version with a higher version number (v=22), the browser will use  (and cache) the newer version.

In our case we save the current version number (SVN) into the app.yml and we use this number as increasing GET-Parameter to force a cache refresh:

default:
  stylesheets: [styles.css?v=<?php echo sfConfig::get('app_revision'); ?>]

Symfony cache folder on Windows Azure

I had the problem, that symfony had no write permissons to create the cache files in the Windows Azure approot folder.

After some research I found out, that it is possible to allocate local storage where the role user has write permissions. The disadvantage though is, that the local storage is not persistent, that means, it will be reset after role upgrades, instance reimage or reboots. But for caching I don’t need a persistent storage.

To get it running you need the folowing settings:

ServiceDefinition.csdef

Define a local resource, in this case with the folder name “FileCacheStorage” and the size 1000MB:

<?xml version="1.0" encoding="UTF-8"?>
<ServiceDefinition name="MyWebRole" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole enableNativeCodeExecution="true" name="WebRole" vmsize="ExtraSmall">
    ...
    <LocalResources>
      <LocalStorage name="FileCacheStorage" sizeInMB="1000" />
    </LocalResources>
 </WebRole>
</ServiceDefinition>

ProjectConfiguration.class.php

Change the default symfony cache path within the “setup()” method:

    if(strpos($_SERVER['HTTP_HOST'], "127.0.0.1")===false)
    {
    	$azureFileCachePath = substr($_SERVER['TEMP'], 0, 3).'Resources\Directory\\'.$_SERVER['RoleDeploymentID'].'.WebRole.FileCacheStorage\\cache';
    	$azureFileLogPath   = substr($_SERVER['TEMP'], 0, 3).'Resources\Directory\\'.$_SERVER['RoleDeploymentID'].'.WebRole.FileLogStorage\\log';

		  // Replace default path with local azure storage path, defined in ServiceDefinition ("LocalStorage" = "FileCacheStorage"):
		  $this->setCacheDir($azureFileCachePath);
    }

Notice: When you are testing in your local dev environment the local storage path is completely different (more information here), so I decided to change the path only in the cloud env, because locally I have no write problems. To get the recent drive letter I take it from $_SERVER['TEMP'].

(For the symonfy logging I did it the same way.)

Special thanks to Taylor for his hints.

I hope this helps somebody ;)

Links

 


Copyright © 2007-2012 iTopia. All rights reserved.
Jarrah theme by Templates Next | Powered by WordPress