Service Fabric testing inspired by AZ-202 certification study

I studied for AZ-202 Azure Developer transition certification in March. I had earlier completed the 70-532 certification so I was able to go AZ-202 (transition) certification exam. AZ-202 covered the following topics:

Develop for cloud storage

  • Develop solutions that use file storage
  • Develop solutions that use a relational database

Create Platform as a Service (PaaS) Solutions

  • Create an app service Logic App
  • Create an app or service that runs on Service Fabric
  • Schedule bulk operations
  • Design and develop applications that run in containers

Secure cloud solutions

  • Implement access control

Develop for an Azure cloud model

  • Develop for autoscaling

Implement cloud integration solutions

  • Configure a message-based integration architecture
  • Develop an application message model

Topics were quite familiar to me except Service Fabric. It was a new technology that I hadn't used in any work or personal projects. The best study method for me is to do hands-on practice while browsing materials and documents. So I decided to refactor my Blog application while learning Service Fabric. This blog is now running top of the Service Fabric.

This blog post is not a deep dive into the Service Fabric. I describe this Blog application changes mostly from the architectural point of view. I had some difficulties with the SSL configuration show I described in more detail. 

Basic information about Azure Service Fabric

A few words about Service Fabric. What is an Azure Service Fabric? Microsoft has described Azure Service Fabric: "Azure Service Fabric is a distributed systems platform that makes it easy to package, deploy, and manage scalable and reliable microservices. You can host Service Fabric clusters anywhere: Azure, in an on-premises data center, or on any cloud provider. Basically, Service Fabric is a microservice backbone that enables you to build scalable and reliable applications. Microsoft has built multiple Azure services on top of Service Fabric like Azure SQL Database, Cosmos DB, PowerBI, etc.

Service Fabric has Stateless and Statefull services. Stateless service is a logical choice when the service's persistent state is stored in an external service (ex. Database). Statefull Service allows Service Fabric to maintain the state via Reliable Collections or Reliable Actors.

Pluralsight courses

Pluralsight has good courses about Service Fabric which I recommend going through while learning:

Creating a Service Fabric Cluster

You can create a Service Fabric Cluster via Azure CLI by using the az sf cluster create command. More details about the command can be found here. And of course, you can create a cluster in Azure Portal. Notice that provisioning the Service Fabric Cluster might take some time. Nilay Parikh has written a good step-by-step guide to setting up the Service Fabric Cluster.

Service Fabric Blog Application architecture

I currently have three microservice components in my Service Fabric Cluster:

1) Blog Web Application (Stateless Service)
2) Backend Content Service (Stateless Service)
3) Analytics Service (Stateful Service)

Communication between services is handled via Service Fabric service remoting. Another possibility is of course to use HTTP endpoints. I wanted to use service remoting because it's a Service Fabric's unique way to do communicating between services.

Blog Web Application (Stateless Service)

Blog Web is the application that shows the Blog content for end users (front-end). The application is stateless because it doesn't have any state. This application is the same .NET Core MVC application that I created a year ago.

Content Service (Stateless Service)

Content Service is a stateless component that delivers all blog content for Blog Web Application. Basically, during the refactoring, I moved all ButterCMS API call logic from the earlier version of my Blog Application to this new Service Fabric Stateless Service project. The project has nothing special. It executes calls to the ButterCMS API which is outside of the Service Fabric Cluster. Maybe later I change this project to Statefull and I'll use Reliable Collections as a caching purpose.

Analytics Service (Stateful Service)

I wanted also to learn more about Reliable Collections so I created one Stateful Service. Analytics Statefull service stores all pageview data to the Reliable Collection. Data in the Reliable Collections are replicated across the Service Fabric Cluster.

Basically, I have an ASP.NET Core Middleware component in the Blog application which gets page and user agent information and sends it to the Analytics service via service remoting.

Configure SSL certificate for your site

I found several instructions on the internet, on how to do this but didn't find a complete guide which works. This section shows how I managed to get this working.

Upload your site SSL certificate to the KeyVault

Before uploading your site certificate to KeyVault convert your CRT-file certificate to PFX using ex. OpenSSL:

openssl pkcs12 -export -out "d:\certificates\mysite.pfx" -inkey "d:\certificates\mysite.key" -in "d:\certificates\mysite.crt"

After uploading the open certificate from Keyvault and copying the secret identifier value to the clipboard

Add certificate to the Service Fabric nodes

Use Add-AzureRmServiceFabricApplicationCertificate to install a certificate to all nodes in the cluster. You can specify a certificate you already have or have the system generate a new one for you, and upload it to a new or existing Azure key vault. Source: Add-AzureRmServiceFabricApplicationCertificate

$secret = "https://[MyKeyvault].vault.azure.net/secrets/[MyCertificate]/[MyCertificateId]"
$groupname="[MyResourceGroupName]"
$clustername = "[MyServiceFabricClusterName]"

Add-AzureRmServiceFabricApplicationCertificate -ResourceGroupName $groupname -Name $clustername -SecretIdentifier $secret -Verbose

Open 443 Port in Load Balancer

I found this script from here.

$probename = "AppPortProbe"
$rulename="AppPortLBRule"
$RGname="[MyResourceGroupName]"
$port=443

# Get the load balancer resource
$resource = Get-AzResource | Where {$_.ResourceGroupName –eq $RGname -and $_.ResourceType -eq "Microsoft.Network/loadBalancers"}
$slb = Get-AzLoadBalancer -Name $resource.Name -ResourceGroupName $RGname

# Add a new probe configuration to the load balancer
$slb | Add-AzLoadBalancerProbeConfig -Name $probename -Protocol Tcp -Port $port -IntervalInSeconds 15 -ProbeCount 2

# Add rule configuration to the load balancer
$probe = Get-AzLoadBalancerProbeConfig -Name $probename -LoadBalancer $slb
$slb | Add-AzLoadBalancerRuleConfig -Name $rulename -BackendAddressPool $slb.BackendAddressPools[0] -FrontendIpConfiguration $slb.FrontendIpConfigurations[0] -Probe $probe -Protocol Tcp -FrontendPort $port -BackendPort $port

# Set the goal state for the load balancer
$slb | Set-AzLoadBalancer

Service Fabric ApplicationManifest changes

This rule allows the Network service to get access to the certificate.

 <Principals>
    <Users>
      <User Name="NETWORK SERVICE" AccountType="NetworkService" />
    </Users>
  </Principals>
  <Policies>
    <SecurityAccessPolicies>
      <SecurityAccessPolicy ResourceRef="HttpsCert" PrincipalRef="NETWORK SERVICE" ResourceType="Certificate" />
    </SecurityAccessPolicies>
  </Policies>
  <Certificates>
    <SecretsCertificate X509FindValue="[MyCertificateThumbprint]" Name="HttpsCert" />
  </Certificates>

ServiceManifest changes in the web project

Add a new 443 HTTPS endpoint to the service manifest.

  <Resources>
    <Endpoints>
      <Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="80" />      
      <Endpoint Protocol="https" Name="ServiceHttpsEndpoint" Type="Input" Port="443" />
    </Endpoints>
  </Resources>

Endpoint changes (web project)

Service Listener changed to use a new HTTPS endpoint. The certificate will be fetched from the VM nodes certificate store.

 /// <summary>
        /// Optional override to create listeners (like tcp, http) for this service instance.
        /// </summary>
        /// <returns>The collection of listeners.</returns>
        protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
        {
            return new ServiceInstanceListener[]
              {
                      new ServiceInstanceListener(serviceContext =>
                          new KestrelCommunicationListener(serviceContext, "ServiceHttpsEndpoint", (url, listener) =>
                          {
                              ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

                              return new WebHostBuilder()
                                          .UseKestrel(opt =>
                                          {
                                              int port = serviceContext.CodePackageActivationContext.GetEndpoint("ServiceHttpsEndpoint").Port;
                                              opt.Listen(IPAddress.IPv6Any, port, listenOptions =>
                                              {
                                                  listenOptions.UseHttps(GetCertificateFromStore());
                                              });
                                          })
                                          .ConfigureServices(
                                              services => services
                                                  .AddSingleton<StatelessServiceContext>(serviceContext))
                                          .UseContentRoot(Directory.GetCurrentDirectory())
                                          .UseStartup<Startup>()
                                          .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                                          .UseUrls(url)
                                          .Build();
                          }))
              };
        }

 public static X509Certificate2 GetCertificateFromStore()
        {
            var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            try
            {
                store.Open(OpenFlags.ReadOnly);
                var certCollection = store.Certificates;
                var currentCerts = certCollection.Find(X509FindType.FindByThumbprint, "[MyCertificateThumbprintFromConfiguration]", false);
                return currentCerts.Count == 0 ? null : currentCerts[0];
            }
            finally
            {
                store.Close();
            }
        }

That's it now it's working.

Comments