Deploying Your Play! Framework App to Azure as a PaaS

Aims

We wanted to deploy a Play! framework application to the cloud but we didn’t want to take care of all security updates on a virtual machine. Heroku is very expensive and Amazon has had no suitable solutions for our needs. We decided to go with Windows Azure.

The previous process
(& its problems)

Without automation our process was:

With this concept, automation was really hard, but this was a stable starting ground.

deploy-play-framework-app-button

Changes

First of all, I took the core part of the WindowsAzureToolkitForEclipseWithJava and edited it to look like as if it was generated by the Azure Tool itself.

The .startup.cmd describes how the environment should be set up to start your cloud service. We need to modify the .startup.cmd executable according to the code below. As you can see I use ZuluJDK because it has all the capabilities to run this Play! framework application.

The next file we need to edit is the definitionFile. You can define your vm size, the name of your project and most importantly, the Endpoint. (The latter should look similar to the code below.)
The name property doesn’t count but the localPort should be set to 9000 or whatever your application listens to. The port property needs to be set to 80, because it redirects your request to your application listening to port 9000.

DefinitionFile.csdef 
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ServiceDefinition xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" name="AzureDeploymentProject">
  <WorkerRole name="WorkerRole1" vmsize="Medium">
    <Startup>
      <!-- Sample startup task calling startup.cmd from the role's approot folder -->
      <Task commandLine="util/.start.cmd .startup.cmd" executionContext="elevated" taskType="simple"/>
    </Startup>
    <Runtime executionContext="elevated">
        <EntryPoint>
            <!-- Sample entry point calling run.cmd from the role's approot folder -->
            <ProgramEntryPoint commandLine="run.cmd" setReadyOnProcessStart="true"/>
        </EntryPoint>
    </Runtime>
    <Imports>
       
       
    </Imports>
    <Endpoints>
        <InputEndpoint localPort="9000" name="play" port="80" protocol="tcp"/>
    </Endpoints>
  </WorkerRole>
</ServiceDefinition>

As mentioned in the article above, you have to modify a few things in <appName>.bat. I think it is easier to set the JAVAOK flag to true instead of setting the JAVAINSTALLED flag to 1. Of course, there is still a problem with the argument list being too long, so you shouldn’t forget about shortening the APP_CLASSPATH variable either. Those two operations can be easily replaced with one single command line script.

Fix-PlayDistBat.ps1 
<#
.SYNPOSIS
 
.DESCRIPTION
.PARAMETER File
.NOTES
AUthor: Barnabás Oláh, 2015.11.27
#>
Param([Parameter(Mandatory=$true)][string]$File)
try{
Replace-In-File $File "JAVAOK=false" "JAVAOK=true"
Replace-In-File $File "set "APP_CLASSPATH=%APP_LIB_DIR%\\.*" "set "APP_CLASSPATH=%APP_LIB_DIR%\*`""
catch [System.Exception] {
    Write-Host $_.Exception.ToString()
    exit 1
}

Download this script!

If you are new to powershell you can use this:

.\Fix-PlayDistBat.ps1 -File "path\to\file"

After you are done with all the modifications, you need to create the WorkerRole package. On our deployment server this cspack is added to the Enviroment Variables.

cspack ServiceDefinition.csdef \out:ServicePackage.cspkg

Now you have everything ready to deploy your packed application to Azure. Just a quick overview of what’s going on during the deployment process:

  1. You will be “signed in” with the help of your PublishSettings file and the Name of your Subscription; this will identify you.
  2. Your package will be uploaded to your storage.
    1. This is kinda necessary. I wasn’t able to deploy from my local machine, because the connection timed out – deployment failed.
  3.  Checking if the given slot of your CloudService is free.
    1. If the slot is free, a new deployment will be created. It will publish your package from your storage account to the given slot of the CloudeService (with the Configuration you described in you ServiceConfiguration file (*.cscfg).)
    2. If the slot is ‘taken’ there it will be replaced by your package.
  4. Checking if the deployment was successful.
Deploy-Azure.ps1 
<#
.SYNPOSIS
.DESCRIPTION
.PARAMETER PublishSettings
.PARAMETER StorageAccount
.PARAMETER Subscription
.PARAMETER CloudService
.PARAMETER ContainerName
.PARAMETER ServiceConfig
.PARAMETER ServicePackage
.PARAMETER Production
.NOTES
AUthor: Barnabás Oláh, 2015.11.27
#>
Param([Parameter(Mandatory=$true)][string]$PublishSettings,
      [Parameter(Mandatory=$true)][string]$StorageAccount,
      [Parameter(Mandatory=$true)][string]$Subscription,
      [Parameter(Mandatory=$true)][string]$CloudService,
      [Parameter(Mandatory=$true)][string]$ContainerName,
      [Parameter(Mandatory=$true)][string]$ServiceConfig,
      [Parameter(Mandatory=$true)][string]$ServicePackage,
      [Parameter(Mandatory=$false)][switch]$Production)
 
Function Set-Slot($IsProduction){
    if($IsProduction) {
        "Production"
    } else {
        "Staging"
    }
}
Function Set-AzureSettings($PublishSettings, $Subscription, $StorageAccount){
    Import-AzurePublishSettingsFile $PublishSettings
 
    Set-AzureSubscription -SubscriptionName $Subscription -CurrentStorageAccount $StorageAccount
 
    Select-AzureSubscription $Subscription
}
 
Function Upload-Package($ServicePackage, $ContainerName){
    $blob = "$CloudService.package.$(get-date -f yyyy_MM_dd_hh_ss).cspkg"
     
    $containerState = Get-AzureStorageContainer -Name $ContainerName -ErrorAction silentlycontinue
    if ($containerState -eq $null)
    {
        New-AzureStorageContainer -Name $ContainerName > $null
    }
     
    Set-AzureStorageBlobContent -File $ServicePackage -Container $ContainerName -Blob $blob -Force > $null
    $blobState = Get-AzureStorageBlob -blob $blob -Container $ContainerName
 
    $blobState.ICloudBlob.uri.AbsoluteUri
}
 
Function Create-Deployment($ServicePackageUrl, $CloudService, $slot, $ServiceConfig){
    $opstat = New-AzureDeployment -Slot $slot -Package $ServicePackageUrl -Configuration $ServiceConfig -ServiceName $CloudService
}
  
Function Upgrade-Deployment($ServicePackageUrl, $CloudService, $slot, $ServiceConfig){
    $setdeployment = Set-AzureDeployment -Upgrade -Slot $slot -Package $ServicePackageUrl -Configuration $ServiceConfig -ServiceName $CloudService -Force
}
 
Function Check-Deployment($CloudService, $slot){
    $completeDeployment = Get-AzureDeployment -ServiceName $CloudService -Slot $slot
    $completeDeployment.deploymentid
}
 
try{
    
    Set-AzureSettings -publishsettings $PublishSettings -subscription $Subscription -storageaccount $StorageAccount
    
    $ServicePackageUrl = Upload-Package -ServicePackage $ServicePackage -ContainerName $ContainerName
 
    $slot = Set-Slot($Production)
    $deployment = Get-AzureDeployment -ServiceName $CloudService -Slot $slot -ErrorAction silentlycontinue
    if ($deployment.Name -eq $null) {
        Create-Deployment -ServicePackageUrl $ServicePackageUrl -CloudService $CloudService -slot $slot -ServiceConfig $ServiceConfig
    } else {
        Upgrade-Deployment -ServicePackageUrl $ServicePackageUrl -CloudService $CloudService -slot $slot -ServiceConfig $ServiceConfig
    }
    $deploymentid = Check-Deployment -CloudService $CloudService -slot $slot
    exit 0
}
catch [System.Exception] {
    Write-Host $_.Exception.ToString()
    exit 1
}

Download this script!

 

Using it is pretty easy: you will need

  • the name of the appropriate storage and container,
  • the name of your subscription,
  • the name of your CloudService,
  • the configuration and package files and
  • to decide which slot you want to use (Production or Staging)
.\Deploy-Azure.ps1 -PublishSettings path\to\ServicePublishSettings.publishsettings -StorageAccount "$storageAccount" -ContainerName "$containerName" [-Production] 
-Subscription "$subscriptionName" -CloudService "$CloudServiceName"
-Serviceconfig path\to\ServiceConfiguration.cscfg -ServicePackage path\to\ServicePackage.cspkg

 

Conclusion

According to this description you can create a Jenkins job which helps your productivity even more. If you have any questions, don’t be afraid to ask. I’m not an expert but spent a few hours finding a solution to our deployment problem – maybe I can help you even more.

I’m not really satisfied with the concept of deploying your Play! framework application like it’s described above. The solution Heroku provides is quite smooth, but Heroku is more expensive compared to Microsoft Azure, hopefully Microsoft will provide better support for deploying Play! framework Apps to Azure.
Cheers!

Barnabás Oláh

Barnabás Oláh

Full-stack Developer at Wanari Ltd.
Never underestimate the capabilities of a lazy person.