A web frontend with a WCF backend in Azure

So you have decided to go with a web application that communicates with a backend server? Sounds like a solid architecture! And you want to host all of this in Windows Azure? No problem! The Windows Azure SDK allows you to easily create web and worker roles. Web roles are obviously meant for web applications, and are hosted in IIS. Worker roles on the other hand are similar to windows services. It’s a process that get’s started by Azure, and shuts down when it’s complete.

Once we’ve created this web and worker role, we need to setup a hosted service in Windows Azure. This is the one and only entry point of the two roles.  This means that we have only one IP address for the two roles, but we can use different ports. Off course for the web role we would use port 80 or 443. The worker role can be hosted on any other port. Besides being the public entry point, the hosted service also serves as a load balancer for multiple instances of each role.

image

Problem 1: Resolving address for intra role communication

When we now want to let the web role communicate with the worker role, we have to find the worker role’s address. There are two ways to do this:

  1. We can dynamically resolve the IP address of one of the worker role instances
  2. We can use the public IP address, but use the worker role port instead of the web port

Both solutions have pros and cons. The pro of the first solution is that we can access the role directly and don’t need to hit the public endpoint. This saves us a bit of bandwidth and thus money in a cloud model. The disadvantage is that we no longer use the integrated load balancer, so we will have to implement some kind of load balancing on our own when we have more then one instance of the worker role. Another one is that the DNS of the WCF service is not always the same. This can cause problems in some scenarios, for example when using Windows Identity Foundation.

The advantage of using the public endpoint is that it’s easier to configure, and you have. But you’ll have to pay for the extra bandwidth.

Problem 2: Staging configuration & VIP Swapping

Upgrading your roles with a new version, can be done through a VIP-Swap. This basically means that you upload a new package to a staging environment where you can test the deployment. And when everything is working fine, you basically swap the Virtual IP (VIP) addresses in the load balancer of the hosted service, et voila: your staging has become production and vice versa without any downtime.

A staging environment is not a Test environment. Functional testing should be done in another environment. But for testing your deployment on a real azure (which will always be a bit different from your emulated environment in Visual Studio) the staging environment is a great solution.

The problem is that your configuration in staging, will be the same as in production. So your WCF configuration also stays the same. This means that we cannot use the 2nd (easiest) option from above to resolve the worker role’s IP address. Actually when you see staging as a pre-production environment and not as a test environment it makes sense that everything is the same.

Problem 3: Cost

You have to pay for every instance you use. When you have 1 web and 1 worker and both have 1 instance, you’re paying for 2 instances. In some cases, no matter if it’s a solid architecture or not, this might be a waste of resources and money. Any out-of-the-box solution to this will affect your architecture, but is that what you really want? I guess not, so stay with me and I’ll show how we can handle this without changing your design decisions.

Problem 4: Latency

When you communicate between roles, you communicate between two machines. Weather it’s over the load balancer, or not. This means that your application will have to do some I/0 on the network level. Like it or not, but this will introduce some form of latency.

Solution: Combining the web app en WCF service applications in one web role

In Windows Azure, it’s possible to host multiple web sites in the same web role. You can even host multiple sites on port 80 and bind specific DNS names to a specific site! Combine this with the fact that WCF services can also be hosted in IIS (a web role is basically a windows server with IIS), and you can see that we’re onto a possible solution here. We can combine the web application, and the web host for the WCF services into one hosted service (one machine) and still have two separate web applications in IIS. But there are a few tasks you need to accomplish, because this is not a scenario that the Visual Studio tooling supports

  1. Manually edit the csdef and cscfg (cloud service definition & configuration) files
  2. Create a build script for the web application and WCF service host
  3. Create a startup task to point localhost to the DHCP-assigned IP address for that role

Manually edit the csdef and cscfg files:

In your hosted service’s configuration file, you’ll only have one Role. But you’ll have to apply the settings (if any) for both applications. Below is an example of a modified cscfg file:

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <ServiceConfiguration serviceName="MyProjectName.Server"

   3:                       xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"

   4:                       osFamily="1" osVersion="*" schemaVersion="2012-10.1.8">

   5:   <Role name="MyProjectName">

   6:     <Instances count="1" />

   7:     <ConfigurationSettings>

   8:       <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="..." />

   9:       <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" />

  10:       <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="accountname" />

  11:       <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="...." />

  12:       <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2021-01-01T23:59:59.0000000+01:00" />

  13:       <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />

  14:       <Setting name="Storage.Pictures" value="..." />

  15:     </ConfigurationSettings>

  16:     <Certificates>

  17:       <Certificate name="MyProjectName.exertus.be"

  18:                    thumbprint="BB5A344FB454C06CC52CB19A329618F43838536B" thumbprintAlgorithm="sha1"/>

  19:       <Certificate name="be"

  20:                    thumbprint="A608582E758BAEEC8CBBAB6FF231AD6EC9EA4A51" thumbprintAlgorithm="sha1" />

  21:       <Certificate name="Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption"

  22:                    thumbprint="E739DCC983EB7526341511F806316C9D68B0445C" thumbprintAlgorithm="sha1" />

  23:     </Certificates>

  24:   </Role>

  25: </ServiceConfiguration>

In the cloud definition file, you will define the “MyProjectName” role you configured in the file above. Each instance of this role will be configured with two web sites. The site bindings and endpoints are all configured for both sites. All other settings must also reflect bot applications. And at the end we’ll define a startup task that I’ll explain later on. This is a sample of a modified csdef file.

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <ServiceDefinition name="MyProjectName.Server"

   3:                    xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"

   4:                    schemaVersion="2012-10.1.8">

   5:   <WebRole name="MyProjectName" vmsize="ExtraSmall">

   6:     <Sites>

   7:       <Site name="WebTier"

   8:             physicalDirectory="WebRole\_publishedWebSites\MyProjectName.WebRole">

   9:         <Bindings>

  10:           <Binding name="Web-Http" endpointName="Web-Http" />

  11:           <Binding name="Web-Https" endpointName="Web-Https" />

  12:           <Binding name="Web-Https-Dev" endpointName="Web-Https-Dev" />

  13:         </Bindings>

  14:       </Site>

  15:       <Site name="ApplicationTier"

  16:       physicalDirectory="ApplicationRole\_publishedWebSites\MyProjectName.ApplicationRole">

  17:         <Bindings>

  18:           <Binding name="App-Https" endpointName="App-Https" />

  19:           <Binding name="App-Https-Dev" endpointName="App-Https-Dev" />

  20:         </Bindings>

  21:       </Site>

  22:     </Sites>

  23:     <Endpoints>

  24:       <InputEndpoint name="Web-Http" protocol="http" port="80" />

  25:       <InputEndpoint name="Web-Https" protocol="https" port="443" certificate="be" />

  26:       <InputEndpoint name="Web-Https-Dev" protocol="https" port="44300" certificate="be" />

  27:       <InputEndpoint name="App-Https" protocol="https" port="8080" certificate="be" />

  28:       <InputEndpoint name="App-Https-Dev" protocol="https" port="44301" certificate="be" />

  29:     </Endpoints>

  30:     <Imports>

  31:       <Import moduleName="Diagnostics" />

  32:       <Import moduleName="RemoteAccess" />

  33:       <Import moduleName="RemoteForwarder" />

  34:     </Imports>

  35:     <Certificates>

  36:       <Certificate name="be" storeLocation="LocalMachine" storeName="My" />

  37:       <Certificate name="MyProjectName.be" storeLocation="LocalMachine" storeName="My" />

  38:     </Certificates>

  39:     <LocalResources>

  40:       <LocalStorage name="MyProjectName.ApplicationRole.svclog" sizeInMB="5120" cleanOnRoleRecycle="false" />

  41:       <LocalStorage name="DiagnosticStore" sizeInMB="5120" cleanOnRoleRecycle="false" />

  42:       <LocalStorage name="TempStorage" cleanOnRoleRecycle="true" sizeInMB="5120" />

  43:     </LocalResources>

  44:     <ConfigurationSettings>

  45:       <Setting name="Storage.Pictures" />

  46:     </ConfigurationSettings>

  47:     <Startup>

  48:       <Task commandLine="SetIp.cmd" executionContext="elevated" taskType="simple">

  49:         <Environment>

  50:           <Variable name="ComputeEmulatorRunning">

  51:             <RoleInstanceValue xpath="/RoleEnvironment/Deployment/@emulated" />

  52:           </Variable>

  53:         </Environment>

  54:       </Task>

  55:     </Startup>

  56:   </WebRole>

  57: </ServiceDefinition>

Create the build script:

In our cloud definition file, we’ll have to point to the actual contents for the web applications. When you only define one website in the web role, you can point to a project directory. But the cloud package command doesn’t support that when multiple websites come into play. The the contents in the physicalDirectory attribute of the ‘Site’ element will be the content of your web app. No build or config transformations take place. I solved this with a custom build script, but there are other solutions.  The PowerShell build script from below builds the two applications, the startup task dll and packages the cspack file using the cspack.exe command line utility. Ideally you should create a MsBuild script for this I think.

   1: Remove-Item -Force -Confirm -Recurse "C:\Dev\Builds\MyProjectName"

   2:

   3:

   4: & "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe"

   5:     "..\..\MyProjectName.Azure.Startup\MyProjectName.Azure.Startup.csproj"

   6:     "/p:outdir=C:\Dev\Builds\MyProjectName\Startup\bin;Configuration=Release";

   7:

   8:

   9: & "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe"

  10:     "..\..\MyProjectName.WebRole\MyProjectName.WebRole.csproj"

  11:     "/t:rebuild" "/t:TransformWebConfig"

  12:     "/p:outdir=C:\Dev\Builds\MyProjectName\WebRole;Configuration=Release";

  13: Remove-Item

  14:     "C:\Dev\Builds\MyProjectName\WebRole\_publishedWebSites\MyProjectName.WebRole\*.config"

  15: Copy-Item

  16:     "..\..\MyProjectName.WebRole\obj\Release\TransformWebConfig\transformed\Web.config"

  17:     "C:\Dev\Builds\MyProjectName\WebRole\_publishedWebSites\MyProjectName.WebRole\web.config"

  18:

  19:

  20: & "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe"

  21:     "..\..\MyProjectName.ApplicationRole\MyProjectName.ApplicationRole.csproj"

  22:     "/t:rebuild" "/t:TransformWebConfig"

  23:     "/p:outdir=C:\Dev\Builds\MyProjectName\ApplicationRole;Configuration=Release";

  24: Remove-Item

  25:     "C:\Dev\Builds\MyProjectName\ApplicationRole\_publishedWebSites\MyProjectName.ApplicationRole\*.config"

  26: Copy-Item

  27:     "..\..\MyProjectName.ApplicationRole\obj\Release\TransformWebConfig\transformed\Web.config"

  28:     "C:\Dev\Builds\MyProjectName\ApplicationRole\_publishedWebSites\MyProjectName.ApplicationRole\web.config"

  29:

  30: Write-Host -ForegroundColor Yellow "Start packaging cloud project"

  31: Copy-Item "ServiceConfiguration.Cloud.cscfg" "c:\Dev\Builds\MyProjectName"

  32: Copy-Item "ServiceDefinition.csdef" "c:\Dev\Builds\MyProjectName"

  33: & "C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\2012-10\bin\cspack.exe"

  34:     "c:\Dev\Builds\MyProjectName\ServiceDefinition.csdef"

  35:     "/role:MyProjectName;C:\Dev\Builds\MyProjectName\Startup;MyProjectName.Azure.Startup.dll"

  36:     "/rolePropertiesFile:MyProjectName;.\AzureRoleProperties.txt"

  37:     "/out:C:\Dev\Builds\MyProjectName\MyProjectName.Server.cspkg"

Create a startup task for the web role:

Great, our IIS in Windows Azure is now configured to run the web site and web host. We now can call the services from the web site by just using ‘localhost’ right? Not exactly. When Windows Azure creates the IIS sites, it binds the sites to a specific IP address. This address is the internal IP address of the role instance and is controlled by DHCP. Only calls from that specific network adapter are handled by IIS. So localhost which maps to 127.0.0.1 or ::1 isn’t. The solution is to modify the hosts file of this instance and point ‘localhost.be’ to that IP address. We can create a startup task that modifies our hosts file. When a role instance starts, windows azure will launch a batch file where we can call this PowerShell script. We will completely rewrite the hosts file because a role start or stop can occur multiple times, because the instance’s image is reused. The image can even be reused when you upload a new package.

Here’s the PowerShell script SetIp.ps1:

   1: [System.IO.File]::WriteAllText(

   2:     "d:\\Windows\\System32\\drivers\\etc\\hosts",

   3:     "127.0.0.1     localhost")

   4:

   5: [System.IO.File]::AppendAllText(

   6:     "d:\\Windows\\System32\\drivers\\etc\\hosts",

   7:     [System.Environment]::NewLine + "::1     localhost")

   8:

   9: Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE -ComputerName . |

  10:     Select-Object -Property IPAddress |

  11:     foreach {

  12:         [System.IO.File]::AppendAllText(

  13:             "d:\\Windows\\System32\\drivers\\etc\\hosts",

  14:             [System.Environment]::NewLine + $_.IPAddress[0] + "     localhost.be")

  15:     }

And here’s the batch file that you configured in the startup task definition in step 1 called SetIp.cmd:

   1: @echo off

   4:

   5:     REM   Attempt to set the execution policy by using PowerShell version 2.0 syntax.

   6:     PowerShell -Version 2.0 -ExecutionPolicy Unrestricted .\SetIp.ps1 >> "%TEMP%\SetIpLog.txt" 2>&1

   7:

   8:     IF %ERRORLEVEL% EQU -393216 (

   9:        REM   PowerShell version 2.0 isn't available Start using powershel 1.1.

  10:

  11:        PowerShell -Command "Set-ExecutionPolicy Unrestricted" >> "%TEMP%\SetIpLog.txt" 2>&1

  12:        PowerShell .\SetIp.ps1 >> "%TEMP%\SetIpLog.txt" 2>&1

  13:     )

  14:

  15: REM   If an error occurred, return the errorlevel.

  16: EXIT /B %errorlevel%

Conclusion:

Hosting multiple applications in one hosted service role is possible without changing your architecture. By doing this can also simplify your configuration and deployment. Latency benefits too because network I/O is no longer needed (only over the loopback adapter offcourse). The solution is still scalable, because when you have a web site that only needs a request/reply backend, chances are you needed to scale the backend together with the front end. You can even scale to only one instance, reducing costs even more.

About these ads


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.