Where should I add ServicePlacementPreferPrimaryDomainPolicyDescription in code or manifest file? - azure-service-fabric

I have a cluster span regions and I want specify preferred domain. Problem is looking at sample code in https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-resource-manager-advanced-placement-rules-placement-policies, I have no idea where serviceDescription comes from. Anyone knows where I should have these code in my service fabric service code base?
Also, isn't there a similar way to specify it in service fabric manifest file instead of code change (Like how people specify frontend/backend placement)?
Thanks,

Use this code to change a service description:
FabricClient fabricClient = new FabricClient();
StatefulServiceDescription serviceDescription = new StatefulServiceDescription();
serviceDescription.PlacementConstraints = "(HasSSD == true && SomeProperty >= 4)";
await fabricClient.ServiceManager.CreateServiceAsync(serviceDescription);
(source here)
According to the XSD, you can define them as xml, similar to placement constraints.
C:\Program Files\Microsoft SDKs\Service Fabric\schemas\ServiceFabricServiceModel.xsd

As per MS Docs, you can certainly configure it per code, like this:
FabricClient fabricClient = new FabricClient();
StatefulServiceDescription serviceDescription = new StatefulServiceDescription();
serviceDescription.PlacementConstraints = "(HasSSD == true && SomeProperty >= 4)";
// add other required servicedescription fields
//...
await fabricClient.ServiceManager.CreateServiceAsync(serviceDescription);
As for your question about where to do this from - depending on your needs, there are a few options:
Write a separate application that gets run as part of CI/CD deployment pipeline (or do it via powershell New-ServiceFabricService)
The problem with this approach is that it's a bit annoying for developers having to run this after each deployment - as such I prefer:
Write a stateless service that gets instantiated by default (i.e. put an entry inside <DefaultServices> within ApplicationManifest.xml). Within this service you can then instantiate/modify placement constraints using FabricClient, policies, metrics - and drive these via application parameters/configuration
Instantiate services lazily via your API gateway. This works particularly well for partitioned/stateful services (e.g. if you partition by tenantid) or where you want to maintain a pool of workers (e.g. partition_id = random_number % pool size), etc.
You can also configure placement constraints declaratively via application parameters. Unfortunately this doesn't seem to work atm for placement policies (and probably not for metrics either).
If your needs are simple, then the declarative approach is probably simplest! As per above link, add a [Stateless1_InstanceCount] parameter in your application manifest file and put this under default services:
<DefaultServices>
<Service Name="Stateless1">
<StatelessService ServiceTypeName="Stateless1Type" InstanceCount="[Stateless1_InstanceCount]">
<SingletonPartition />
<PlacementConstraints>[Stateless1_PlacementConstraints]</PlacementConstraints>
</StatelessService>
</Service>
</DefaultServices>

Related

How to avoid hosting restart when deploying Azure Functions with deployment slot?

I configured pipeline to do zero down time deployment for Azure Functions. For that purpose I have following steps:
create slot
deploy to slot
start swap with preview
complete swap
My understanding of this is process is that all restarts should happen only on preview slot (so only JobHost should be restarted) and this should have a place before final swap. However, I noticed on Application Insight that Hosting stopped which result in on 503 code when I was hitting function. Is there away of avoiding this? I'm not sure if it matters but I use Premium plan.
You cannot avoid the restart but you could use a custom warm-up if your function needs it.
The swap operation waits for the warm-up to finish before swapping with the target swap. You configure this in a web.config file, example below:
<system.webServer>
<applicationInitialization>
<add initializationPage="/" hostName="[app hostname]" />
<add initializationPage="/Home/About" hostName="[app hostname]" />
</applicationInitialization>
</system.webServer>
You can also customize the warm-up behavior with one or both of the following app settings:
WEBSITE_SWAP_WARMUP_PING_PATH: The path to ping to warm up your site. Add this app setting by specifying a custom path that begins with a slash as the value. An example is /statuscheck. The default value is /.
WEBSITE_SWAP_WARMUP_PING_STATUSES: Valid HTTP response codes for the warm-up operation. Add this app setting with a comma-separated list of HTTP codes. An example is 200,202 . If the returned status code isn't in the list, the warmup and swap operations are stopped. By default, all response codes are valid.
I was able to achieve zero downtime deployment setting variable WEBSITE_ADD_SITENAME_BINDINGS_IN_APPHOST_CONFIG to 1. For more info you can take a look here. One drwaback of this is slowness on processing requests during deployment.
I also recommend to follow this github issue where is discussion about zer/miniam downtime deployment.

Is it possible to have a single frontend select between backends (defined dynamically)?

I am currently looking into deploying Traefik/Træfik on our service fabric cluster.
Basically I have a setup where I have any number of Applications (services), defined with a tenant name and each of these services is in fact a separate Web UI.
I am trying to figure out if I can configure a single frontend to target a backend so I don't have to define a new frontend each time I deploy a new UI app. Something like
[frontend.tenantui]
rule = "HostRegexp:localhost,{tenantName:[a-z]+}.example.com"
backend = "fabric:/WebApp/{tenantName}"
The idea is to have it such that I can just deploy new UI services without updating the frontend configuration.
I am currently using the Service Fabric provider for my backend services, but I am open to using the file provider or something else if that is required.
Update:
The servicemanifset contains labels, so as to let traefik create backends and frontends.
The labels are defined for one service, lets call it WebUI as an example. Now when I deploy an instance of WebUI it gets a label and traefik understands it.
Then I deploy ANOTHER instance with a DIFFERENT set of parameters, its still the WebUI service and it uses the same manifest, so it gets the same labels, and the same routing. But what I would really want was to let it have a label containing some sort of rule so I could route to the name of the service instance (determine at runtime not design time). Specifically I would like for the runtime part to be part of the domainname (thus the suggestion of a HostRegexp style rule)
I don't think it is possible to use the matched group from the HostRegexp to determine the backend.
A possibility would be to use the Property Manager API to dynamically set the frontend rule for the service instance after creating it. Also, see this for a complete example on using the API.

Different Endpoint Certificates Per Environment in Service Fabric

I have a service fabric application that exposes an SSL endpoint. I would like to use a different certificate based on the environment. I'm trying to do this with parameters in the ApplicationMainfest.xml file in the same way that I specify other things, such as instance counts. However, parameters appear not to be working for this. I'm wondering if this is actually true and if there are certain things that you cannot parameterize. Also, is there any way to specify a different certificate based on the environment?
Here are the relevant pieces from my application manifest:
<Parameter Name="CERTNAME" DefaultValue="MyCert" />
...
<Certificates>
<EndpointCertificate X509FindValue="..." Name="MyCert" />
<EndpointCertificate X509FindValue="..." Name="SVSSL" />
</Certificates>
<Policies>
<EndpointBindingPolicy EndpointRef="ServiceEndpointHttps" CertificateRef="[CERTNAME]" />
</Policies>
On deployment, I get the following error:
Register-ServiceFabricApplicationType : The CertificateRef '[CERTNAME]' in EndpointBindingPolicy is invalid. There is no matching Certificate in the corresponding ApplicationManifest.
Today the certificate value itself is parameterizable but not the Ref. So instead of changing the reference or the name, you would parameterize the X509FindValue and keep the endpointbindingpolicy the same.
As a note, just any time you run into something you want to parameterize but can't figure out how to do it, there are a few options. Consider for example most things in the Service Manifest, like the port that the service listens on (if you have it statically configured). There are some other ways around this:
Create different manifests (service manifests or application manifests) and replacing them when creating the application package for a given environment
Using something during your build/deployment stages (such as the Tokenizer Task in VSTS) to replace a stub value with the actual value given the environment that the package is being crafted for
Move most of the endpoint configuration stuff to settings.xml and replace those values via the normal application parameter/override behavior. This would mean taking on the work of configuring your endpoints yourself, however.

Publicly exposing a WCF restful service via http from Service Fabric

I am trying to expose a WCF based restful service via http and am so far unsuccessful. I'm trying on my local machine first so prove it works. I found a suggestion here that I remove my local cluster and then manually run this powershell command from the SF SDK folder as administrator to recreate it with the machine name binding: .\DevClusterSetup.ps1 -UseMachineName
It created the cluster successfully. I can use the SF Explorer and see in the cluster manifest that entries in the NodeList show the machine name rather than localhost. This seems good.
But the first problem I notice is that if I expand my way through SF Explorer down to the node my app is running on I see an endpoints entry but the URL is not what I'd expect. I am seeing this: http://surfacelap/d5be9425-3247-4290-b77f-1a90f728fb8d/39cda0f7-cef4-4c7f-8af2-d05786a834b0-131111019607641260
Is that what I should see even though I have an endpoint setup? I did not expect the guid and other numbers in the path. This makes me suspect that SF is not seeing my service as being publicly accessible and instead is maybe only setup for internal access within the app? If I dig down into my service manifest I see this as expected:
<Resources>
<Endpoints>
<Endpoint Name="ResolverEndpoint" Protocol="http" Type="Input" Port="80" />
</Endpoints>
</Resources>
But how do I know if the service itself is mapped to it? When I use the crazy long url above and try a simple method of my service I get an http 202 response and no response data as expected. If I then change the method name to one that doesn't exist I get the same thing, not the expected http 404. I've tried using both my machine name and localhost. Same result.
So clearly I'm doing something wrong. Below is my CreateServiceInstanceListeners override. In it you can see I use "ResolverEndpoint" as my endpoint resource name, which matches the service manifest:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[] { new ServiceInstanceListener((context) =>
new WcfCommunicationListener<IResolverV2>(
serviceContext: context,
wcfServiceObject: new ResolverServiceV2(),
listenerBinding: new WebHttpBinding(WebHttpSecurityMode.None),
endpointResourceName: "ResolverEndpoint"
)
)};
}
What am I doing wrong here?
Here's a way to get it to work: https://github.com/loekd/ServiceFabric.WcfCalc
Essential changes to your code are the use of the public name of your cluster as endpoint URL and an additional WebHttpBehavior behavior on that endpoint.
The endpoint resources specified in the service manifest is shared by all of the replica listeners in the process that use the same endpoint resource name. So if your service has more than one partition it is possible that more than one replica from different partition may end up in the same process. In order to differentiate the messages addressed to different partitions, the listener adds partition ID and additional instance GUID to the path.
If you are going to have a singleton partition service and know that there will not be more than one replicas in the same process you can directly supply EndpointAddress you want to the listener to open at. Use the CodePackageActivationContext API to get the port from the endpoint resource name, node name or IP address from the NodeContext and then provide the path you want the listener to open at.
Here is the code in the WcfCommunicationListener that constructs the Listen Address.
private static Uri GetListenAddress(
ServiceContext serviceContext,
string scheme,
int port)
{
return new Uri(
string.Format(
CultureInfo.InvariantCulture,
"{0}://{1}:{2}/{5}/{3}-{4}",
scheme,
serviceContext.NodeContext.IPAddressOrFQDN,
port,
serviceContext.PartitionId,
serviceContext.ReplicaOrInstanceId,
Guid.NewGuid()));
}
Please note that you can now have only once application, one service and one partition on a node and when you are testing locally, keep the instance count of that service as 1. When deployed in the actual cluster you can use -1 instance count.

Need ServiceConfiguration.cscfg to populate web.config sessionstate and connection strings

I need to propagate connection string changes for entity framework, asp.net membership (which are both in the connectionstrings section of web.config) and session state (which is in sessonstate's sqlconnectionstring) in web.config when I adjust these settings in windows azure's service configuration.
During development we test our app as a standard asp.net webforms app, but once it is deployed it is running in azure. So we need to allow for the site running in both non-azure and an azure context. That's why we're just relying upon the values in web.config for now.Since these connection strings are not called directly in my code writing a utility class which grabs from azure service config if that is available or otherwise grabs from web.config is not a possibility for these values.
I realize that editing web.config would cause a disruption in service - and i only plan to do this during off hours.
I believe that the best approach is to wrap your configuration information in a service. Then, in the service, use RoleEnvironment to determine which settings to use. For example
public static class Config
{
public static string ConnStr
{
get
{
if (RoleEnvironment.IsAvailable)
return RoleEnvironment.GetConfigurationSettingValue("ConnStr");
return ConfigurationManager.AppSettings["ConnStr"];
}
}
}
If that doesn't work, and you need to change the actual web.config (for instance, using named connection strings), then you'll need to modify the config at runtime. In your role start, do something like the following:
var config = WebConfigurationManager.OpenWebConfiguration(null);
var connStrs = WebConfigurationManager.OpenWebConfiguration(null).GetSection("connectionStrings") as ConnectionStringsSection;
connStrs.ConnectionStrings["ConnStr"].ConnectionString = RoleEnvironment.GetConfigurationSettingValue("ConnStr");
config.Save();
To handle when the configuration changes after the role is running, just call the same code as above from the RoleEnvironment.Changing event.
Good luck,
Erick