I have a Windows Azure site which is deployed to two separate hosted services. One for test, one for production. When we're ready to push to production, we spin up a staging deployment in the production service, push there, then do a VIP swap. All good.
The question is, now we want to move up from XS web instances, but it doesn't really make sense to spend the extra money on the test deployment. Is there any way to use an XS instance for test, and then say medium instances for production? I know I can change the number of instances for each service config, but I can only change the instance size for all configs.
I'm thinking of just leaving it XS in the config, and then remembering to switch it to Medium before I deploy to production. Is there any reason why I shouldn't do this? Is there a better way?
Cheers!
There are a few ways to do this... Simpler way is to do a little "hacking" of the CCPROJ file:
1) create a clone of the CSDEF file for every environment that matches the Configuration Name (Release/Debug/QA/UAT/etc):
ServiceDefinition.Release.csdef, ServiceDefinition.Debug.csdef, etc.
2) Add those files manually to the CCPROJ file by using a notepad editor
3) Define a Pre-Build Event command that copies the ServiceDefinition.$(ConfigurationName).csdef into ServiceDefintion.csdef
voila, now your ServiceDefintion will adapt to whatever configuration you're using.
If you want to get fancier or see more details, check out this blog entry that can help you switch all sorts of settings in unison
http://www.paraleap.com/blog/post/Managing-environments-in-a-distributed-Azure-or-other-cloud-based-NET-solution.aspx
Edit: Here is a config that works. Notice that other files are included as type "None" instead of ServiceDefinition to avoid the multiple definitions error.
<ItemGroup>
<ServiceConfiguration Include="ServiceConfiguration.Local.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.Development 1.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.Development 2.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.Local Dev 1.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.Local Dev 2.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.QA 1.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.QA 2.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.Pre-Production 1.cscfg" />
<ServiceConfiguration Include="ServiceConfiguration.Production.cscfg" />
<ServiceDefinition Include="ServiceDefinition.csdef" />
<None Include="ServiceDefinition.Local.csdef" />
<None Include="ServiceDefinition.Development 1.csdef" />
<None Include="ServiceDefinition.Development 2.csdef" />
<None Include="ServiceDefinition.Local Dev 1.csdef" />
<None Include="ServiceDefinition.Local Dev 2.csdef" />
<None Include="ServiceDefinition.QA 1.csdef" />
<None Include="ServiceDefinition.QA 2.csdef" />
<None Include="ServiceDefinition.Pre-Production 1.csdef" />
<None Include="ServiceDefinition.Production.csdef" />
</ItemGroup>
You can use the Web Publishing TransformXml
MSBuild task to transform only the parts of the ServiceDefinition you want (like you can do now with Web.Config).
- Create a
ServiceDefinition.[BuildConfigName].csdef
file next to the ServiceDefinition.csdef file (you'll probably need to do this in File Explorer)
- Create the transform file like you'd created a Web.config transform. I explicitly set the root namespace, just in case, so my root element is:
<ServiceDefinition name="Cloud.JobsWorker"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"
schemaVersion="2013-10.2.2">
- Manually add it to your ccproj using:
<ServiceDefinition Include="ServiceDefinition.csdef" />
<None Include="ServiceDefinition.Release.csdef" />
- At the bottom of your project include:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<PropertyGroup>
<ServiceDefinitionTransform>ServiceDefinition.$(Configuration).csdef</ServiceDefinitionTransform>
</PropertyGroup>
<Target Name="TransformServiceDefinition" BeforeTargets="ResolveServiceDefinition" Condition="exists('$(ServiceDefinitionTransform)')">
<!-- Generate transformed service config in the intermediate directory -->
<TransformXml Source="@(ServiceDefinition)" Destination="$(IntermediateOutputPath)%(Filename)%(Extension)" Transform="$(ServiceDefinitionTransform)" />
<!--Force build process to use the transformed configuration file from now on.-->
<ItemGroup>
<ServiceDefinition Remove="ServiceDefinition.csdef" />
<ServiceDefinition Include="$(IntermediateOutputPath)ServiceDefinition.csdef" />
</ItemGroup>
</Target>
When you package or publish your cloud app, your csdef should be transformed depending on the build config you're using.
This is adapted from here: http://blogs.staykov.net/2011/06/windows-azure-configuration-settings.html
Using transformation as David Faivre suggested is much cleaner and without the overhead to update all files after adding single property.
This is the transformation xml for changing vm size:
<?xml version="1.0"?>
<ServiceDefinition name="CloudServiceName"
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2015-04.2.6"
xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<WorkerRole name="WorkerRoleName.Role" vmsize="Medium" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
</ServiceDefinition>
VM size is handled in the ServiceDefinition.csdef file, which is something you can't edit at run or deploy time. You would need to change the setting in the .csdef, repackage your solution, and then redeploy.
One solution may be to have multiple Windows Azure deployment projects set up. One project would be your "test" project that has the .csdef configured to use a XS. Another project would be the "production" project that uses a larger instance. This would allow you to use the standard Windows Azure / Visual Studio tools to manage the project - which may be nice depending on your process.
David Faivre proposed a good solution. Few comments from me:
- If your ServiceDefinition file contains links to some directories (e.g. section), than there will be an error caused by the fact that transformed file is in intermediate directory. For my use I solved this problem by placing transformed file alongside original ServiceDefinition.csdef (don't forget to add *.transformed into .gitignore):
<TransformXml Source="@(ServiceDefinition)" Destination="ServiceDefinition.csdef.transformed" Transform="$(ServiceDefinitionTransform)" />
$(Configuration)
variable corresponds to Build configuration (Debug, Release etc.). If you want to have profile specific transformation (corresponding to ServiceConfiguration..cscfg), you need to use $(TargetProfile)
variable:
<ServiceDefinitionTransform>ServiceDefinition.$(TargetProfile).csdef</ServiceDefinitionTransform>