In this entry, I will talk about the steps needed to get PHP + MySQL + Wordpress running on Azure. I will be using Wordpress here as a sample PHP application but once you know this, you will be able to deploy any PHP application on Windows Azure.
Pre-requisites:
1. PHP Binaries for IIS (http://windows.php.net/)
2. MySQL Binaries (Without Installer) (http://dev.mysql.com/downloads/mysql/5.1.html )
2. MySQL PHP Solution Accelerator (http://code.msdn.microsoft.com/winazuremysqlphp )
3. VS2008 SP1/VS2010 Beta 2 w/ Windows Azure SDK
4. Wordpress (Or any PHP Application)
5. “The Lazy Man Hello Cloud PHP” ( http://innovativesingapore.com/post/The-Lazy-Mane28099s-e2809cPHP-Hello-Cloude2809d.aspx ) [Optional but Reccomended]
Phase 1 – Setup
1. Install the MySQL PHP Solution Accelerator
2. Extract the PHP binaries for IIS to the “php” folder in the PhpMyAdminWebRole directory. E.g. “C:\Samples\AzureMySQLPHP_x86\PhpMyAdminWebRole\php”
3. Extract the Wordpress files to the “wordpress” folder in the PhpMyAdminWebRole directory. E.g. “C:\Samples\AzureMySQLPHP_x86\PhpMyAdminWebRole\wordpress”
4. Extract the MySQL Binaries to the “mysql” folder in the MySQL_WorkerRole directory. E.g. “C:\Samples\AzureMySQLPHP_x86\MySQL_WorkerRole”
Phase 2 – Windows Azure Cloud Service
5. Create a new “Windows Azure Cloud Service” solution in Visual Studio.
6. Add an existing project, “PhpMyAdminWebRole” (From the Solution Accelerator), to the solution you created.
7. Add another existing project, “MySQL_WorkerRole” (Also from the Solution Accelerator,), to the solution.
8. Right click on “Roles” (Found in your “Windows Azure Cloud Service” project) and select “Add-> Web Role Project in solution” and select the project you added in your solution previously.
9. Repeat step 8, but this time, choose “Add -> Worker Role Project in solution” and select the “MySQL_WorkerRole” you added previously.
Phase 3 – Configuring MySQL_WorkerRole
*For learning purposes we shall not touch on the advance features of the MySQL_WorkerRole in the Solution Accelerator. If you do want to do more with it, please watch this video: http://microsoftpdc.com/Sessions/SVC51 .
For now, we are going to configure the worker role to have only 1 instance.
10. Open up the settings for “MySQL_WorkerRole” (Found in the Roles folder at your “Windows Azure Cloud Service” project) and change the following configurations:
Configuration Section
Instance Count: 1
Settings Section [Name, Type, Value]
TableStorageEndpoint, String, http://table.core.windows.net
BlobStorageEndpoint, String, http://blob.core.windows.net
DataConnectionString, ConnectionString, UseDevelopmentStorage=true
DiagnosticsConnectionString, ConnectionString, UseDevelopmentStorage=true
ContainerName, String, mysqlphp11
FullBackupHour, String, 06:00
IncrementalBackupDuration, String, 10
RecycleThreadSleepTime, String, 300
EnableWindowsAzureDrive, String, False
EnableBackup, String, False
*Note, we will be using the Dev Fabric Storage in this walkthrough, feel free to use your cloud storage if any.
Endpoints (Name, Type, Protocol)
PeerEndpointIn, Internal, tcp
MasterElectionService, Internal, tcp
MySQL, Internal, tcp
InstanceManagerEndpontIn, Internal, tcp
Local Storage
MySQLStorage, 200MB
MySQLDatastore, 1024MB
BlobBackup, 500MB
Next up, we want to edit the behavior of the worker role such that when the worker role starts, it will update the PHP ini (configuration) file so that our PHP web role will know where is the MySQL database located in the cloud.
11. Open up MySQLAccess.cs in “MySQL_WorkerRole” project and make the following changes:
In the internal class “MySQLClient” add a string constant – relative path to your php ini file.
Code:
private const string REL_PATH_TO_PHP_INI = "./../../PhpMyAdminWebRole/approot/php/php.ini";
Next find the line “string masterHost = instance.IPEndpoint.Address.ToString();
“ and add the following after the line:
Code:
//Set php ini so PHP knows where is the host and port of the MySQL endpoint
FileStream fs = File.OpenRead(REL_PATH_TO_PHP_INI);
StreamReader reader = new StreamReader(fs);
//Read all text in INI File
string php_ini = reader.ReadToEnd();
//Closing resources
reader.Close();
fs.Close();
//Add in details (Host IP Address and Port)
php_ini = php_ini.Replace("mysql.default_port =", "mysql.default_port = " + port);
php_ini = php_ini.Replace("mysql.default_host =", "mysql.default_host = " + masterHost);
File.WriteAllText(REL_PATH_TO_PHP_INI, php_ini);
string iniFile = Path.Combine(baseDir, "my.ini");
File.Copy("my.ini", iniFile, true);
[NEW UPDATE!]
I have changed the logic of the codes to use Environment Variables instead of updating the PHP INI file on runtime. Please make sure your function "public bool Start(int id)" looks something like the one below. (: The code is now easier to understand and shorter.
public bool Start(int id)
{
try
{
string baseDir = RoleEnvironment.GetLocalResource("MySQLStorage").RootPath.Replace('\\', '/');
string dataDir = RoleEnvironment.GetLocalResource("MySQLDatastore").RootPath.Replace('\\', '/');
string blobDir = RoleEnvironment.GetLocalResource("BlobBackup").RootPath.Replace('\\', '/');
LogError("MySql Base directory: {0}", baseDir);
LogError("MySql Data directory: {0}", dataDir);
string command = Path.Combine(baseDir, @"bin\mysqld.exe");
SetupBlobBackup(blobDir);
if (!File.Exists(command))
{
SetupAzureMySql(baseDir);
SetupAzureMySqlDataDir(dataDir);
}
MySqlConnection rootConn = GetConnection(_endpointName);
if (IsRunning(_endpointName))
{
int currentId = GetMySqlServerId();
if (currentId == id)
{
return true;
}
command = Path.Combine(baseDir, @"bin\mysqladmin.exe");
Process.Start(command, "-u root shutdown");
}
RoleInstanceEndpoint instance;
//Get the "MySQL" instance
instance = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["MySQL"];
//Get the port of the instance
string port = instance.IPEndpoint.Port.ToString();
Environment.SetEnvironmentVariable("MasterPort", port, EnvironmentVariableTarget.Process);
//Get the IP address of the instance
string masterHost = instance.IPEndpoint.Address.ToString();
Environment.SetEnvironmentVariable("MasterHost", masterHost, EnvironmentVariableTarget.Process);
string iniFile = Path.Combine(baseDir, "my.ini");
//update the my.ini file with mysql server details
UpdateMyIni(iniFile, baseDir, dataDir, port, id.ToString());
ProcessStartInfo startInfo = new ProcessStartInfo(command);
startInfo.RedirectStandardOutput = true;
startInfo.WorkingDirectory = baseDir;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = false;
startInfo.Arguments = "--console";
Process driver = new Process();
driver.StartInfo = startInfo;
driver.Start();
StreamReader sr = driver.StandardOutput;
string output = sr.ReadToEnd();
while (!IsRunning(_endpointName))
{
Thread.Sleep(TimeSpan.FromSeconds(10));
}
}
catch (Exception ex)
{
LogError("Error in MySqlAccess start(): {0}", ex.Message + ex.StackTrace);
return false;
}
return true;
}
Phase 4 – Configuring Wordpress
12. Copy “wp-config-sample.php” in the “wordpress” folder to “wp-config.php” then open up “wp-config.php”
13. You may want to look at http://codex.wordpress.org/Editing_wp-config.php to know more about what each setting does.
Under the MySQL hostname section, set it to be “define('DB_HOST', ini_get("mysql.default_host").':'.ini_get("mysql.default_port"));” , without the quotes of course ;)
[NEW UPDATE!]
Under the MySQL hostname section, set it to be “define('DB_HOST', getenv("MasterHost").':'.getenv("MasterPort"));” , without the quotes of course ;)
For the rest of the settings, configure it as per normal (refer to: http://codex.wordpress.org/Editing_wp-config.php)
Phase 5 – Deploying
14. We will use the Dev Fabric for now. Hit F5 and wait for all your instances to run.
Important : For those who want to deploy on the cloud,
Phase 6 – Database Setup + Testing
15. Navigate to your PhpMyAdmin site. (If you didn’t change any settings, it should be at http://localhost:81/PhpMyAdmin/index.php )
16. Login as root with no password.
17. Create a new database (the name for the database you defined when setting up wp-config.php, lets say “wordpressdb”) called “wordpressdb”.
18. Create new user with the credentials you set in “wp-config.php” previously and give the necessary rights. Remove the root users’ privileges after you created the new user (security concerns here.)
19. You can now proceed to your WordPress app to test! (Default: http://localhost:81/wordpress/index.php)
WordPress will detect whether the app have been set up or not and prompt you accordingly. Enjoy your new PHP Application using MySQL on Windows Azure Cloud!
If you have any queries, feel free to email me at v-japoh@microsoft.com or MSN me @ Jason_scorpio@hotmail.com . ;)
You just need to change the storage settings to point to your Windows Azure storage and it will work. Publish the project as per normal and deploy as per normal.
---
Settings for MySQL:
DataConnectionString, ConnectionString, UseDevelopmentStorage=true
DiagnosticsConnectionString, ConnectionString, UseDevelopmentStorage=true
Change the above to use Windows Azure Storage.
Common Problems Encountered & Solutions
Here I have included some of the problems others faced for the benefit of those who might meet the same problems (:
(1) phpmyadmin folder is empty
- Could not find file 'C:\Samples\AzureMySQLPHP_x86\PhpMyAdminWebRole\PHPMyAdmin\browse_foreigners.php';
- Could not find file 'C:\Samples\AzureMySQLPHP_x86\PhpMyAdminWebRole\PHPMyAdmin\bs_change_mime_type.php';
- Could not find file 'C:\Samples\AzureMySQLPHP_x86\PhpMyAdminWebRole\PHPMyAdmin\bs_disp_as_mime_type.php'.
Solution: I downloaded the phpmyadmin and paste the folder into the phpmyadmin folder in Accelerator.
(2) Cannot load mysql extension.
Solution: In php.ini, uncomment extension=php_mysql.dll, extension=php_mbstring.dll.
(3) HTTP Error 500.0 - Internal Server Error
Solution: In php.ini, locate the extension_dir setting. Change the value to “./ext” and locate the cgi.force_redirect setting and remove the comment and set the value to 0. Set cgi.fix_pathinfo and fastcgi.impersonate to 1.
(4)Missing Dlls
Solution: If the dlls are not found inside the windows php distribution binaries, the dlls belongs to optional add-ons. You can simply delete the missing dll references from the project.