While developing new solutions, we often use Service Fabric services as controllers. Executing actions on demand, checking the status of other Azure services, running maintenance routines, cleaning up data, the possibilities with Service Fabric are endless. Often, that means we have a cluster of services deployed to different environments, but want to disable a particular service.
In the Application Parameters, it's possible to set services to run a given instance count, but that number is not allowed to be 0. This has been an open requested issue for a long time (https://feedback.azure.com/forums/169386-cloud-services-web-and-worker-role/suggestions/743252-allow-a-role-instance-count-of-0).
To allow our services to be disabled, we wrote something we call a NoopService. It's a service implementation that does nothing except wait:
public class NoopService : StatelessService
{
public NoopService(StatelessServiceContext context) : base(context) {}
protected override async Task RunAsync(CancellationToken cancellationToken)
{
await Task.Run(() => cancellationToken.WaitHandle.WaitOne());
}
On it, we also find a static method that will check for a config setting:
public static bool NoopEnabled(StatelessServiceContext context)
{
var configurationPackage = context.CodePackageActivationContext.GetConfigurationPackageObject("Config");
var serviceEnabled = configurationPackage.Settings.Sections["Default"].Parameters["Enabled"].Value;
return !serviceEnabled.Equals("true", StringComparison.OrdinalIgnoreCase);
}
}
This code is extracted out to a separate project, since all the services in the solution can use it. In time, we could turn it into a Nuget package, so it can be reused by everyone.
Next, we need to configure the config setting that IsEnabled() method looks for, for every service that we might want to disable, in the Application Manifest:
<Parameters>
<Parameter Name="MyService_InstanceCount" DefaultValue="1" />
<Parameter Name="MyService_Enabled" DefaultValue="true" />
</Parameters>
<ServiceManifestImport>
<ServiceManifestRef ServiceManifestName="MyServicePkg" ServiceManifestVersion="1.0.0" />
<ConfigOverrides>
<ConfigOverride Name="Config">
<Settings>
<Section Name="Default">
<Parameter Name="Enabled" Value="[MyService_Enabled]" />
</Section>
</Settings>
</ConfigOverride>
</ConfigOverrides>
</ServiceManifestImport>
And in the service's config file:
<Section Name="Default">
<Parameter Name="Enabled" Value="" />
</Section>
Once this basic setup is done, each service's Main method can decide which service to instantiate, the NoopService if the service is disabled in the configuration setting, or the real service if it isn't:
ServiceRuntime.RegisterServiceAsync("MyServiceType", context => (NoopService.NoopEnabled(context) ?
(StatelessService)new NoopService(context) :
new MyService(context))).GetAwaiter().GetResult();
In our Application Parameters file, we can now simply provide a value for the 'Enabled' configuration setting to enable or disable a service by environment.
As you can see, the principle behind this disabling is simple, yet offers us a lot of power: we can disable a service without changing its InstanceCount, we can choose exactly which services to enable in which environment, and we can disable a service without impacting any other operation. The service will show up as having healthy instances, but won't do anything.
Additionally, this has proven very useful during debugging on the local cluster. We can disable the entire application except the service we want, and so debug it in complete isolation.
Give it a try, let us know what you think!
That's true, Ivan, but then you can't enable or disable them per environment. It's true this method uses a little more resources, but it does make it very quick to enable and disable services without any extra maintenance or environment-specific manifests and their overhead.