Introduction
One of the things you have to consider in any application is configuration. In windows and web forms, we have *.config files to help configure our application prior to start. They are a useful place to store things like provider configuration, IOC container configuration, connection strings, service end points, etc. Let’s face it – we use configuration files a lot.
In this article, I will discuss the different types of configurations available to you in Windows Azure, how they can be leveraged in your application, and how configuration items can be changed at runtime without causing your application roles to restart.
The Problems With Configuration in the Cloud
In Windows Azure applications, configuration can work exactly the same as standard .NET applications. If you have a web role, then you have a web.config. And if you have a worker role, you get an app.config. This allows you to provide configuration information to your role when it starts.
But what about configuration values you want to change after your app is deployed and running? It certainly is a lot harder to get in and change a few angle brackets in your web.config after it is deployed to production in the cloud. Do you really want to have to upload a whole new version of the app package with the new web.config file in it?
Or what about being able to change configuration aspects of all your running instances in one go, and not having to stop them from running to do so? Why should a configuration change necessitate a restart, such as is needed with web.config and app.config files?
In Windows Azure, we have a new method of configuring our roles that gives us flexibility and consistency in our applications.
Service Configuration and Service Definition Files
In Windows Azure, we can get the above benefits of flexibility and consistency in our configuration, via the service configuration file: ServiceConfiguration.cscfg.
This file contains information about your app that can be used at start-up, and can be changed at runtime without requiring a new package upload. This includes the number of running instances, certificate information, and specific application configuration settings, such as connection strings, port numbers, REST endpoints, etc.
The service configuration works in partnership with the service definition file: ServiceDefinition.csdef.
Both files are located in your main cloud project. The service definition is the “boss”. It cannot be changed at run time. When you upload your Azure application, the service configuration is part of the application package, but the service definition is uploaded separately. This is because the service definition contains all the instructions about how an application should be deployed including: details about each of the roles, what their fault and upgrade domains are, TCP ports to open (for worker roles), what local storage should be available, VM size to use, level of trust, and more. After upload, the service definition is shipped off to the fabric controller who will parse it and work out what to do with your cloud package.
We’ll focus on one more thing you will find in the service definition file – information about configuration items. Essentially the service definition states what configuration values will be available in the service configuration file, for each role. It looks something like this:
<workerrole name="ImageSearch.Cloud.Overlord">
<configurationsettings>
<setting name="ImageSearchSettings" />
<setting name="ImageSearchDBC" />
<setting name="SearchMultiplier" />
<setting name="MatchTolerance" />
</configurationsettings>
</workerrole>
If a configuration setting name is not specified in the service definition, it cannot be used in the service configuration file to configure your app. Think of it as an instruction to the fabric controller: “My app can be configured with these 4 values”. Also, once you’ve uploaded your application, you can’t add or remove settings, since the service definition file is not editable; you can only change the configuration values. I’ll show you how later on.
In the service configuration file, our entry would look like this:
<Role name="ImageSearch.Cloud.Overlord">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="ImageSearchSettings" value="UseDevelopmentStorage=true" />
<Setting name="ImageSearchDBC" value="Data Source=..." />
<Setting name="SearchMultiplier" value="3" />
<Setting name="MatchTolerance" value="50" />
</ConfigurationSettings>
</Role>
It's pretty similar to the <appSettings> element you are probably familiar with. There is an element for every role in your cloud project, and the <ConfigurationSettings>
child element contains all the settings of your application as “name/value” pairs.
Setting Configuration Values
When setting values in the service configuration and definition files directly, you will get complete intellisense, making it very easy to work out what the appropriate values are. It is also possible to set configuration values via the tooling.
In your main cloud project (which contains the links to all your roles), you can bring up the properties window for a role. Under the ‘Settings’ tab, you can add new configuration values.
When adding a new setting, the IDE will insert the relevant placeholder in the service definition, and insert the value in the service configuration. This makes the Visual Studio IDE approach a bit of a time saver and helps ensure your definition and configuration files are in sync. In the above screen shot, you can see that we have two configuration items: the first is a cloud storage connection string, while the second is a database connection string. Essentially, all configuration items are strings, but the IDE gives you some shortcuts when creating configuration values for storage accounts via this connection string type. Clicking the ellipses […] delivers a new modal window where you can specify the details of your storage account.
In the end, this just creates another string configuration value that can be used by the app.
<Setting
name="ImageSearchSettings"
value="DefaultEndpointsProtocol=https;AccountName=ImageSearch;AccountKey=3ob...UY=="
/>
The structure of this connection string makes it useful to the API as we’ll see later on.
Changing Configuration Values At Runtime In Azure
To change a configuration value of an app that is already deployed in Azure, you click the Configure button from your project, and this will take you to a screen with a textbox where you can edit all your values directly, or can optionally upload another configuration file. Once saved, that new configuration will take affect. The default behaviour of Windows Azure is to restart your instances now, using the new configuration. However this isn’t ideal, and we’ll look at ways to handle that differently.
Simple Handling Of Configuration Values In Code
The simplest way to leverage configuration items in your code is to use the API that comes with the Cloud tools. This provides a bunch of useful assemblies that give you strongly typed access to all the things you would want to do with your application. In the Microsoft.WindowsAzure.ServiceRuntime
assembly (and namespace) we have a sealed class called RoleEnvironment
which affords us a static method for accessing configuration values at runtime:
var tolerance = RoleEnvironment.GetConfigurationSettingValue("MatchTolerance");
This will bring back the specified value as a string
. If you request a configuration setting that doesn’t exist, a RoleEnvironmentException
will be thrown with an extremely descriptive error message of “error”:
Handling Storage Connection Strings In Code
Earlier, we saw that you can use the IDE to create special connection string configuration values. These connection strings can be used to create an instance of a CloudStorageAccount
which is a special class in the Azure SDK API that wraps up information about a specific storage account, and lets you easily connect to your storage account aspects (queues, blobs, and tables).
The CloudStorageAccount
can be instantiated normally and requires you to pass account credential information to its constructor. This is fine if you are going to hardcode those credentials, but that’s not a great idea of course.
Alternatively (and preferably) you can use some static
constructors and properties to build your storage account client. The easiest way to get moving here is with the FromConfigurationSetting static
method, as illustrated below:
public class StorageAccountFactory
{
public static CloudStorageAccount Create()
{
return CloudStorageAccount.FromConfigurationSetting("ImageSearchSettings");
}
}
This will use the connection string specified in the configuration setting called ‘ImageSearchSettings
’ which is the same connection string we specified using the IDE earlier. This is by far the easiest way to automatically load an instance of the CloudStorageAccount
. But…
The Catch With Using Configuration Methods in the Storage API
API methods that utilise configuration values, such as the CloudStorageAccount.FromConfigurationSetting
method, require that you tell the API in advance how to retrieve configuration values. Currently if you just used the CloudStorageAccount.FromConfigurationSetting
method “as is” you’ll get an InvalidOperationException
when you run your application, with the error message:
SetConfigurationSettingPublisher
needs to be called before FromConfigurationSetting
can be used.
What this is trying to tell you is that you need to instruct the CloudStorageAccount
to use the standard configuration definition file when loading configuration settings. That might seem a little silly at first.. where else could your configuration settings be coming from? Haven’t you just wasted half an hour reading about the Azure configuration files?
Well when running your app in Azure, your configuration will certainly come from the Azure service configuration file. But the tooling guys at Microsoft wanted you to create apps that could still be easily switched back to running on private infrastructure without having to remove all the Azure related integration points; and unfortunately the service configuration file is an Azure only integration point. We need to be able to factor that out into abstraction and make our apps transparent to the source of configuration settings.
Let me explain further with an example. Consider this scenario: you have a web role that puts items into a queue, and a worker role that grabs items out of the queue and processes them. You have your web role setup to use CloudStorageAccount.FromConfigurationSetting
to load your storage client from the service configuration file. However later on, you decide to move your web role on premise, and leave the other bits running in Azure. You detach your web role from the cloud service project; it now stands on its own two feet and can be run on IIS. But that also means you no longer have a service configuration file; you’re stuck with plain old web.config again. Ideally you should still be able to use the same static
method to load your storage account from configuration settings, it's just that those configuration settings are now in a different place.
The SetConfigurationSettingsPublisher
method that the error refers to is another static
method on the CloudStorageAccount
class that lets us specify where configuration should be loaded from. According to the MSDN documentation:
This method should be called once to set up the environment. The environment could be the Windows Azure runtime, in which case the publisher is a simple wrapper of the configuration reading and change event. The environment could also be a .NET environment, in which case the developer can hook up a custom configuration reader and change notification.
As stated in the documentation, you should only setup the configuration publisher once, so this ideally should happen in your role’s OnStart
event. The code to tell CloudStorageAccount
to use the configuration definition uses a delegate which is stored and called every time the configuration is requested:
CloudStorageAccount.SetConfigurationSettingPublisher(
(configName, configSetter) =>
configSetter(RoleEnvironment.GetConfigurationSettingValue(configName))
);
In essence, a configuration setting publisher is just an Action – a delegate that is called when attempting to get the value of a configuration setting name. In the code above, we are telling the storage account client that whenever it needs to get a configuration item (configName
), then call the standard RoleEnvironment.GetConfigurationSettingValue static
method with that name to find and return the value.
How Can I Make My Application Utilise this Abstraction
Great question! Let's say we actually wanted to make our code more flexible and be able to switch between the configuration definition when running in Azure, over to web.config appSettings when running in plain old IIS on our own server. We would like our app to detect that it is no longer running in the world of Azure, and there is a property we can use that does just that: RoleEnvironment.IsAvailable
. We can use it to customise who our setting publisher is. Consider the following static
method on a factory class that I have created called StorageAccountFactory
:
public static Action<string, Func<string,bool>> GetConfigurationSettingPublisher()
{
if (RoleEnvironment.IsAvailable)
return (configName, configSetter) =>
configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
return (configName, configSetter) =>
configSetter(ConfigurationManager.AppSettings[configName]);
}
You would then call the new method like this:
CloudStorageAccount.SetConfigurationSettingPublisher(
StorageAccountFactory.GetConfigurationSettingPublisher()
);
Walla! Our app is now aware of when it is and is not running in Azure and can switch its settings provider as needed, while still giving us the benefit of using the CloudStorageAccount
API to load our storage account from configuration.
Handling Configuration Changes at Runtime
As I mentioned earlier, you can change configuration values at runtime while your app is already strolling along happily in production. The reasons for needing to make a change can vary; perhaps you want to tweak a performance setting or change the format of log file output.
Most importantly, you don’t want your app to always stop and restart on a configuration change. The default behaviour of Azure roles is to restart on any configuration change so if you want to prevent this behaviour, read on!
You can detect changes to the role environment by handling the 2 following static
events, available on the RoleEnvironment
class:
RoleEnvironment.Changing += RoleEnvironmentChanging;
RoleEnvironment.Changed += RoleEnvironmentChanged;
The Changing
event occurs prior to your configuration changes being applied, while the Changed
event occurs after the changes are applied. When you create a new web or worker role, the Changing
event is inserted for you, along with the event handler code:
private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
{
if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
{
e.Cancel = true;
}
}
The default behaviour of this code is to ‘cancel’ the event on any configuration change. Cancelling the event is a bit misleading; what this actually means is that the role will be recycled, configuration changes will be applied, and the role will start up again. So “e.Cancel
” actually means “e.Reboot
”.
Naturally this is the event handler where we can make changes. It's up to you to decide which changes require the role to restart and which ones don’t. Personally, I like to make a static
array of names of configuration items that I don’t want to cause a restart, like so:
private static readonly string[] ExemptConfigurationItems =
new[] { "MatchTolerance", "SearchMultiplier" };
Then in the RoleEnvironmentChanging
event handler, we can decide whether or not to reboot, and if we don’t need to reboot, apply the new configuration changes in the RoleEnvironmentChanged
event:
private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
{
Func<RoleEnvironmentConfigurationSettingChange, bool> changeIsExempt =
x => !ExemptConfigurationItems.Contains(x.ConfigurationSettingName);
var environmentChanges = e.Changes
.OfType<RoleEnvironmentConfigurationSettingChange>();
e.Cancel = environmentChanges.Any(changeIsExempt);
if (!e.Cancel)
{
var newMultiplier = RoleEnvironment
.GetConfigurationSettingValue("SearchMultiplier");
}
}
private void <roleenvironmentconfigurationsettingchange>RoleEnvironmentChanged
(object sender, RoleEnvironmentChangedEventArgs e)
{<roleenvironmentconfigurationsettingchange>
var newMultiplier = RoleEnvironment
.GetConfigurationSettingValue("SearchMultiplier");
}
The RoleEnvironmentChangingEventArgs
supplies a Changes
property which is a list of all changes that caused this event to fire. Typically these will be a change of the number of instances that the role is running, or a change to configuration items. We request all the changes that are of type RoleEnvironmentConfigurationSettingChange
which represents just setting changes. We then compare the changed items to our static
list of configuration names, and if any of the changes aren’t in the list, we’ll do a reboot. If we don’t do a reboot (which means all changes belonged to our exempt list) we will apply those new changes in the Changed
event handler.
Summary
And that’s all there is to know about configuration at this point in time. Here’s a couple of points summarising what we’ve learnt:
- Configuration values can be changed while your app is still running
- Default behaviour in such cases is to reboot the role
- Configuration is just XML but the Visual Studio IDE gives nice design time experience
- Connection strings are a special kind of configuration string that store details for a Windows Azure Storage account
- Using the API, we can abstract our settings publisher to create flexibility of usage in Azure and non-Azure applications
- Runtime configuration changes can be checked and instance recycling can be avoided if necessary
Please keep in mind that this was valid at the time of writing and that the Azure platform is continually evolving, so there might already be a better technique for what you want to do.
Additional Links