Microsoft Service Fabric Notebook

   Submit to Reddit      
  

Overview

The following information relates to Service Fabric SDK version 6.

Attaching the Debugger

If a service is running as a domain user, the debugger in Visual Studio (running as Administrator) will not be able to break into the CreateServiceInstanceListeners() method on your StatelessService or CreateServiceReplicaListeners() method on your StatefulService UNLESS the domain user is also a member of the local Administrators group.

Windows Principal / Identity

When a Service Fabric service is running:

  • System.Threading.Thread.CurrentPrincipal.Identity is an instance of System.Security.Principal.GenericIdentity - with a blank Name!
  • System.Security.Principal.WindowsIdentity.GetCurrent() returns an instance of System.Security.Principal.WindowsIdentity with correct details of the account the service is running under.

Target .NET Version

  • Service Fabric 6 SDK (and the 2.8 NuGet packages) still require the full .NET Framework (on Windows).
  • Recommendation: When you create a new service in your fabric application, at least target .NET Framework 4.7.1.
  • Support for running on .NET Core 2.0 (instead of the full .NET Framework) is coming soon - with the 3.0 Service Fabric NuGet libraries.
  • See my .NET Notebook

Naming & Max Path Limitations

  • Windows Server 2012 R2 has a max path limitation of 260 characters.
  • Windows Server 2016 has a Group Policy setting allowing that limitation to be removed to support long paths - at Computer Configuration > Administrative Templates > System > Filesystem > Enable NTFS long paths
  • The default Service Fabric┬ádata root (FabricDataRoot specified in the cluster configuration) is D:\ProgramData\SF
  • Service Fabric deploys service binaries to:
    • [FabricDataRoot][NodeName]\Fabric\work\Applications[ApplicationTypeName]_App??[ServiceName]Pkg.Code.[Version]\
    • e.g. D:\ProgramData\SF\myserver-vm0\Fabric\work\Applications\MyCompany.MyDomain.MySubDomain.FabricAppType_App23\MyAwesomeServicePkg.Code.1.0.10\MyCompany.MyDomain.MySubDomain.MyAwesomeService.Logic.dll.config
    • The example above is 206 characters and is well within the max path limit.
  • Naming Recommendation:
    • Ensure that the package name (service name + "Pkg") does not contain the full namespace - as that could easily cause the max path limit to be exceeded
    • e.g. set the package name to "MyAwesomeServicePkg" instead of "MyCompany.MyDomain.MySubDomain.MyAwesomeServicePkg"
    • In the ServiceManifest.xml, set the package name in the XML attribute //ServiceManifest@Name - to e.g. "MyAwesomeServicePkg"
    • In the ApplicationManifest.xml, set the same package name in the XML attribute //ServiceManifestImport/ServiceManifestRef@ServiceManifestName

How to Create a Service Hosted with Kestrel

NuGet Packages To Install

When you first create a Kestrel service (and of course you selected .NET 4.7.1 or later), perform the following NuGet library installations in order so that a bunch of .NET Standard 1.x System libraries are not installed:

Install:

  • Microsoft.AspNetCore.Server.Kestrel (version > 2.0)
  • Microsoft.AspNetCore.Server.Kestrel.Https

Update:

  • Microsoft.NETCore.Platforms

Install:

  • Microsoft.ServiceFabric.AspNetCore.Kestrel (which depends on Microsoft.AspNetCore.Server.Kestrel version >1.0)

Configure Kestrel Options for HTTPS & Dynamic Ports

In order to configure HTTPS, you will need to use the KestrelServerOptions.Listen() method. However, KestrelServerOptions.Listen() requires an IPEndpoint or IPAddress and Port to be specified.

Choose a Service Fabric Reverse Proxy Compatible IP Address

Note that:

  • IPAddress.Loopback will cause the IP address "127.0.0.1" to be resolved by the Service Fabric Reverse Proxy
  • IPAddress.Any will cause the IP address "0.0.0.0" to be resolved by the Service Fabric Reverse Proxy.

In both of these cases, and depending on the number of instances of a given service, a multi-server cluster will experience delays and even failures when an API call is performed via the Service Fabric Reverse Proxy.

IPAddress.IPv6Any is the only IP Address that is compatible with the Service Fabric Reverse Proxy. This will cause the server's fully qualified domain name to be registered correctly and resolved by the Service Fabric Reverse Proxy.

Choose a Dynamic Port

As per the Kestrel documentation, port number 0 will cause a port to be allocated dynamically.

Example Kestrel Configuration

/// <summary>
/// Optional override to create listeners (e.g., TCP, HTTP) for this service replica to handle client or user requests.
/// </summary>
/// <returns>A collection of listeners.</returns>
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new ServiceInstanceListener[]
    {
        new ServiceInstanceListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, (url, listener) =>
                new WebHostBuilder()
                    .UseKestrel(ConfigureKestrelServerOptions)
                    .ConfigureServices(services => services.AddSingleton<StatelessServiceContext>(serviceContext))
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseReverseProxyIntegration | ServiceFabricIntegrationOptions.UseUniqueServiceUrl)
                    .UseStartup<Startup>()
                    .UseUrls(url)
                    .Build()
            ))
    };
}

private void ConfigureKestrelServerOptions(KestrelServerOptions options)
{
    options.AddServerHeader = false;
    // options.Limits.
    // options.ApplicationServices

    IPAddress serviceFabricReverseProxyCompatibleIPAddress = IPAddress.IPv6Any;
    const int dynamicPort = 0;

    options.Listen(serviceFabricReverseProxyCompatibleIPAddress, dynamicPort);
    
    // FOR HTTPS, DO THIS INSTEAD:
    //options.Listen(
    //    serviceFabricReverseProxyCompatibleIPAddress,
    //    dynamicPort,
    //    listenOptions => listenOptions.UseHttps(new HttpsConnectionAdapterOptions
    //    {
    //        CheckCertificateRevocation = true,
    //        ClientCertificateMode = ClientCertificateMode.NoCertificate,
    //        //ClientCertificateValidation = myAdditionalClientCertificateValidationCallback
    //        //ServerCertificate = TODO get an X509Certificate2
    //        SslProtocols = System.Security.Authentication.SslProtocols.Tls12
    //    })
    //);
}

Configuration & Deployment

  • When deployment is performed, Service Fabric will update and reformat your ApplicationManifest.xml and other configuration files. For instance, attributes with default values will be removed.

  • Deployment completely fails when a <SecurityAccessPolicy> is defined within <Policies><SecurityAccessPolicies>:

    <Principals>
        <Users>
            <User Name="ServiceAccount" AccountType="DomainUser" AccountName="mydomain\myaccount" Password="encrypted=" PasswordEncrypted="true" />
        </Users>
    </Principals>
    <Policies>
        <SecurityAccessPolicies>
            <SecurityAccessPolicy ResourceRef="ServiceEndpoint" PrincipalRef="ServiceAccount" GrantRights="Full" />
        </SecurityAccessPolicies>
    </Policies>
    

    The deployment error is:

    2>Upload to Image Store succeeded
    2>Registering application type...
    2>Register-ServiceFabricApplicationType : Object reference not set to an instance of an object.
    2>At C:\Program Files\Microsoft SDKs\Service
    2>Fabric\Tools\PSModule\ServiceFabricSDK\Publish-NewServiceFabricApplication.ps1:251 char:9
    2>+         Register-ServiceFabricApplicationType -ApplicationPathInImageStore $appl ...
    2>+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    2>    + CategoryInfo          : InvalidOperation: (Microsoft.Servi...usterConnection:ClusterConnection) [Register-ServiceFabricApplicationType], FabricException
    2>    + FullyQualifiedErrorId : RegisterApplicationTypeErrorId,Microsoft.ServiceFabric.Powershell.RegisterApplicationType
    

RunAs Policy, HTTP/S URL Reservation & Elevated Privilege Requirement

This section is only relevant when needing to reserve a URL for HTTP or HTTPS - such as when writing WCF services within Service Fabric (using the WcfCommunicationListener). That requirement is described in this WCF documentation due to WCF's usage of Http.sys.

The information below pertains to running Service Fabric services under a domain user account - using the various types of RunAs policy configuration options in the ApplicationManifest.xml file.

The user that attempts to reserve the URL requires elevated privileges - such as being a local Administrator. URL reservation can be achieved in a few ways:

  • Manually login in as an administrator and run netsh as per this WCF documentation - but that would require static ports to be defined (an approach that is not really suitable in a modern, distributed services orchestration platform such as Service Fabric);
  • Actually grant the RunAs user membership of the local Administrator group (which is not a good idea because the service will be running as a local administrator and not abiding by the Principle of Least Privilege);
  • Use <DefaultRunaAsPolicy> instead of an explicit RunAsPolicy specified in <ServiceManifestImport>;
  • When using an explicit RunAsPolicy in in <ServiceManifestImport>, specify a <SecurityAccessPolicy> as per this documentation.

The following scenarios are true for a domain user account that is NOT a member of the local Administrators group.

(a) THIS WORKS -> configuring a <DefaultRunaAsPolicy> with a domain user (without a sibling SecurityAccessPolicy):

<Principals>
    <Users>
        <User Name="ServiceAccount" AccountType="DomainUser" AccountName="mydomain\myaccount" Password="encrypted=" PasswordEncrypted="true" />
    </Users>
</Principals>
<Policies>
    <DefaultRunAsPolicy UserRef="ServiceAccount" />
</Policies>

(b) THIS DOES NOT WORK -> configuring a RunAsPolicy in the <ServiceManifestImport> section (without a sibling SecurityAccessPolicy):

<ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="Stateful1Pkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
    <Policies>
        <RunAsPolicy CodePackageRef="Code" EntryPointType="Main" UserRef="ServiceAccount" />
    </Policies>
</ServiceManifestImport>

Regardless of whether EntryPointType is set to All or Main, the following AddressAccessDeniedException exception occurs:

Unhealthy event: SourceId='System.RA', Property='ReplicaOpenStatus', HealthState='Warning', ConsiderWarningAsError=false.
Replica had multiple failures during open on _Node_0. API call: IStatefulServiceReplica.ChangeRole(P); Error = System.ServiceModel.AddressAccessDeniedException (-2146233087)
HTTP could not register URL http://+:30004/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).
   at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
   at Microsoft.ServiceFabric.Services.Communication.Wcf.Runtime.WcfCommunicationListener`1.b__10_0(IAsyncResult ar)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.ServiceFabric.Services.Runtime.StatefulServiceReplicaAdapter.d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.ServiceFabric.Services.Runtime.StatefulServiceReplicaAdapter.d__18.MoveNext()
For more information see: http://aka.ms/sfhealth

When this occurs, Service Fabric reports the service as unhealthy and repeatedly calls the CreateServiceInstanceListeners() method on your StatelessService or CreateServiceReplicaListeners() method on your StatefulService - hoping that one day it will work.

(c) THIS WORKS -> configuring a RunAsPolicy in the <ServiceManifestImport> section - with a sibling SecurityAccessPolicy as per this documentation:

<ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="Stateful1Pkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
    <Policies>
        <RunAsPolicy CodePackageRef="Code" UserRef="ServiceAccount" />
        <SecurityAccessPolicy ResourceRef="ServiceEndpoint" PrincipalRef="ServiceAccount" GrantRights="Full" />
    </Policies>
</ServiceManifestImport>

Who Reserved the URL & Who is the Service Running As?

The command netsh http show urlacl can be run to show the current HTTP/S URL reservations.

(a) When running without an explicit RunAs policy in the ApplicationManifest.xml (which means the Network Service local account will be used by default), the Network Service account reserves the HTTP URLs:

Reserved URL            : http://+:30012/
    User: NT AUTHORITY\NETWORK SERVICE
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;NS)

Reserved URL            : http://localhost:30012/
    User: NT AUTHORITY\NETWORK SERVICE
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;NS)

and:

  • User name from process in Task Manager: "NETWORK SERVICE"
  • System.Threading.Thread.CurrentPrincipal.Identity: ""
  • System.Security.Principal.WindowsIdentity.GetCurrent(): "NT AUTHORITY\NETWORK SERVICE"

(b) When using a <DefaultRunaAsPolicy> with a domain user:

<Principals>
    <Users>
        <User Name="ServiceAccount" AccountType="DomainUser" AccountName="mydomain\myaccount" Password="encrypted=" PasswordEncrypted="true" />
    </Users>
</Principals>
<Policies>
    <DefaultRunAsPolicy UserRef="ServiceAccount" />
</Policies>

as expected, the domain user account reserves the HTTP URLs:

Reserved URL            : http://+:30035/
    User: mydomain\myaccount
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;S-1-5-21-2197669547-3912936102-3481227320-25699)

Reserved URL            : http://localhost:30035/
    User: mydomain\myaccount
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;S-1-5-21-2197669547-3912936102-3481227320-25699)

and:

  • User name from process in Task Manager: "myaccount"
  • System.Threading.Thread.CurrentPrincipal.Identity: ""
  • System.Security.Principal.WindowsIdentity.GetCurrent(): "mydomain\myaccount"

(c) When configuring a RunAsPolicy in the <ServiceManifestImport> section - with a sibling SecurityAccessPolicy:

<ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="Stateful1Pkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
    <Policies>
        <RunAsPolicy CodePackageRef="Code" UserRef="ServiceAccount" />
        <SecurityAccessPolicy ResourceRef="ServiceEndpoint" PrincipalRef="ServiceAccount" GrantRights="Full" />
    </Policies>
</ServiceManifestImport>

as expected, the domain user account reserves the HTTP URLs:

Reserved URL            : http://+:30018/
    User: mydomain\myaccount
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;S-1-5-21-2197669547-3912936102-3481227320-25699)

Reserved URL            : http://localhost:30018/
    User: mydomain\myaccount
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;S-1-5-21-2197669547-3912936102-3481227320-25699)

and:

  • User name from process in Task Manager: "myaccount"
  • System.Threading.Thread.CurrentPrincipal.Identity: ""
  • System.Security.Principal.WindowsIdentity.GetCurrent(): "mydomain\myaccount"

(d) When the RunAs policy is specified in <ServiceManifestImport> (AND the domain user account is a member of the local Administrators group):

<ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="Stateful1Pkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
    <Policies>
        <RunAsPolicy CodePackageRef="Code" EntryPointType="All" UserRef="ServiceAccount" />
    </Policies>
</ServiceManifestImport>

However, the Network Service account reserves the HTTP URLs:

Reserved URL            : http://+:30037/
    User: NT AUTHORITY\NETWORK SERVICE
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;NS)

Reserved URL            : http://localhost:30037/
    User: NT AUTHORITY\NETWORK SERVICE
        Listen: Yes
        Delegate: No
        SDDL: D:(A;;GX;;;NS)

even though the identity is still the domain account:

  • User name from process in Task Manager: "myaccount"
  • System.Threading.Thread.CurrentPrincipal.Identity: ""
  • System.Security.Principal.WindowsIdentity.GetCurrent(): "mydomain\myaccount"

Endpoint Configuration & HTTPS URL Reservation

  • Endpoints must be setup via configuration in the ServiceManifest.xml.
  • I haven't discovered how to configure an endpoint in code - especially one that uses a dynamic port and has Service Fabric perform the URL reservation. Wish I knew how...
  • Specifying the Protocol attribute in the <Endpoint> in the ServiceManifest.xml file is important - otherwise the URL reservation does not work.
  • The endpoint configuration (such as the Protocol attribute) can be overridden in the <ResourceOverrides> section of the ApplicationManifest.xml.
  • Even if you retrieve and change the endpoint configuration from/in the ServiceContext, URL reservation does not take place on your changed values.

Troubleshooting

  • If a service does not start, there could be multiple reasons:

    • The listener is failing to initialise:
      • RunAs issues with a service account - incorrect credentials, group membership, disabled account
      • HTTPS URL reservation is failing because the endpoints are not configured correctly
        • Issue with the endpoint configuration - see above regarding RunAsPolicy, DefaultRunAsPolicy, SecurityAccessPolicy
        • EndpointBindingPolicy for binding a certificate as part of the HTTPS URL reservation
    • Max path issue (see above)
    • Replicator failures:
      • Do not add your own custom parameters into the <ReplicatorConfig> section - if you do, your services will not start - a replica error
      • Replicator security configuration - issues with the certificate not being found or being invalid
  • Diagnosis techniques:

    • RDP to the node and attempt to execute the service executable - to determine if it fails due to a max path error
    • Turn on <ConsoleRedirection> in your ServiceManifest.xml and inspect the log files - if they are created
      • On a local developer cluster, the logs are in C:\SfDevCluster\Data_App[Node.x][ApplicationType]\log
      • On a default installation in Azure, the logs are in D:\SvcFab_App[ApplicationType]\log
    • Inspect the Windows Event Log
      • The Application log
      • The Service Fabric Admin and Ops logs
    • Use PerfView to monitor Event Tracing for Windows (ETW) events
    • Use Microsoft Message Analyzer

Bookmarks