Exception when trying to containerize and publish StatelessService in Service Fabric

Viewed 219

I have a .NET Core Reliable Service that is pretty much a template service, obtained from the Add > New Service Fabric Service > .NET Core Stateless Service. This means that it contains the ServiceEventSource, a Stateless1 class that inherits from the StatelessService class, and a Program.cs. The contents of the Program.cs are the default ones:

private static void Main()
{
    try
    {
        ServiceRuntime.RegisterServiceAsync("Stateless1Type",
            context => new Stateless1(context)).GetAwaiter().GetResult();                ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Stateless1).Name);
        Thread.Sleep(Timeout.Infinite);
    }
    catch (Exception e)
    {                ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
        throw;
    }
}

Now, this is working fine, I am able to start the Service Fabric application correctly and see logs, etc.

What I am trying to accomplish is to containerize this service, as per the following article: https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-services-inside-containers . This means that I added the SFBinaryLoader.cs and added the following file to my Program.cs:

static Program()
{
    SFBinaryLoader.Initialize();
}

However, by trying this a lot of questions and problems arise which aren't addressed by the documentation.

I am using the microsoft/service-fabric-reliableservices-windowsservercore:1803 as a base image to build my container. This, since the windows server host is version 1803. After running the CreateDockerPackage.ps1 I notice that the Microsoft.ServiceFabric.Data.Interfaces.dll and the System.Fabric.*.dll are being removed.

I think this is why we must be supplied again on runtime. This is performed by the SFBinaryLoader.cs by adding an event listener on the AppDomain.CurrentDomain.AssemblyResolve. This class should look at the FabricCodePath environment variable (which on runtime looks to be binding to the C:\SFFabricBin\ directory) and manually add those binaries.

I would say that in order for the program to run, I had to also remove the *.deps.json file from the container, because I was getting an exception before even running the dotnet Stateless1.dll stating that some missing binaries declared on the *.deps.json file were not found.

Now, it looks like my container is starting up fine by the fabric, and that assemblies are being correctly loaded. However, whenever trying to register the service to the fabric by the following line:

ServiceRuntime.RegisterServiceAsync("Stateless1Type",
                    context => new Stateless1(context)).GetAwaiter().GetResult();

This throws the following exception:

An exception occurred registering the service System.Fabric.FabricException: Service Type is already registered. ---> System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x80071BD1 at System.Fabric.Interop.NativeRuntime.IFabricRuntime.EndRegisterStatelessServiceFactory(IFabricAsyncOperationContext context) at System.Fabric.Interop.Utility.<>c__DisplayClass22_0.b__0(IFabricAsyncOperationContext context) at System.Fabric.Interop.AsyncCallOutAdapter21.Finish(IFabricAsyncOperationContext context, Boolean expectedCompletedSynchronously) --- End of inner exception stack trace --- at Microsoft.ServiceFabric.Services.Runtime.ServiceRuntime.RegisterServiceAsync(String serviceTypeName, Func2 serviceFactory, TimeSpan timeout, CancellationToken cancellationToken) at Eventellect.Fabric.TestDocker.Program.Main() in C:\MyLocalPath\StatelessService\Program.cs:line 33

Do you know why is this happening? I also find it very weird that somehow my published service fabric knows anything at all about my local path.

I have already surrounded the RegisterServiceAsync call inside a try...catch block, but then, even though the thread goes to sleep succesfully and my docker container does not stop, actions inside my Stateless1.cs class are never performed. This means that not even the constructor is invoked.

Is there something I might be missing?

2 Answers

I had the same issue but I achieved to containerize a stateless service in a different way than indicated in the microsoft documentation.

  1. microsoft/service-fabric-reliableservices-windowsservercore:1803 docker image has .Net Core runtime 2.0 installed. If your stateless service is running on later versions it won't work. For this reason, I built my docker image in this way docker image servicefabric-runtime.
  2. I realized SFBinaryLoader.cs class is not necessary for running Service Fabric Reliable Services or Reliable Actors inside containers. I didn't add it to my code.
  3. I didn't execute CreateDockerPackage.ps1 for build the container, I only modified ServiceManifest.xml for changing EntryPoint section as follow:
    <EntryPoint>
      <ContainerHost>
        <ImageName>statelesscontainer:0.1</ImageName>
      </ContainerHost>
    </EntryPoint>
  1. I changed my build project output path to pub/ and manually I added in the root of stateless service the Dockerfile based on step 1 as follow:
    FROM edalx/servicefabric-runtime:dotnetcore-3.1.2
    WORKDIR /app
    ADD pub .
    CMD ["ApiService.exe"]
  1. Besides in ApplicationManifest.xml you need to add PortBinding tag.
  <ContainerHostPolicies CodePackageRef="Code" Isolation="process" ContainersRetentionCount="2">
      <HealthConfig />
      <PortBinding ContainerPort="0" EndpointRef="ServiceEndpoint" />
      <ImageOverrides>
         <Image Name="edalx.azurecr.io/statelesscontainer:0.1" />
      </ImageOverrides>
  </ContainerHostPolicies>
  1. I'm using Visual Studio Community 2019 and for deploying, it detects automatically it is a container and show the wizard like a non-containerized service and it will do all the work.

I tried using hyperv isolation but for me it didn't work.

You can find a complete example in my repo https://github.com/edalx/servicefabric-examples/tree/master/servicefabric-container

Please check ServiceManifest.xml of the containerized service package, for UseImplicitHost. UseImplicitHost needs to be set to 'False' for the reliable user process to be able to register with Runtime. UseImplicitHost is set to true for GuestContainers and GuestExecutables.

Related