2

At the recent Service Fabric Community Q&A 24th Edition there was a lot of discussion around using the DefaultService construct in the ApplicationManifest.xml and it's drawbacks. Microsoft suggested omitting this from an ApplicationManifest entirely and instead modifying the Deploy-FabricApplication.ps1 to construct a default implementation of an application so developers still have a decent F5 experience.

So I have modified the Deploy-FabricApplication.ps1 to the following (this excerpt is the bottom of the script):

   if ($IsUpgrade)
{
    $Action = "RegisterAndUpgrade"
    if ($DeployOnly)
    {
        $Action = "Register"
    }

    $UpgradeParameters = $publishProfile.UpgradeDeployment.Parameters

    if ($OverrideUpgradeBehavior -eq 'ForceUpgrade')
    {
        # Warning: Do not alter these upgrade parameters. It will create an inconsistency with Visual Studio's behavior.
        $UpgradeParameters = @{ UnmonitoredAuto = $true; Force = $true }
    }

    $PublishParameters['Action'] = $Action
    $PublishParameters['UpgradeParameters'] = $UpgradeParameters
    $PublishParameters['UnregisterUnusedVersions'] = $UnregisterUnusedApplicationVersionsAfterUpgrade

    Publish-UpgradedServiceFabricApplication @PublishParameters
}
else
{
    $Action = "RegisterAndCreate"
    if ($DeployOnly)
    {
        $Action = "Register"
    }

    $PublishParameters['Action'] = $Action
    $PublishParameters['OverwriteBehavior'] = $OverwriteBehavior
    $PublishParameters['SkipPackageValidation'] = $SkipPackageValidation

    Publish-NewServiceFabricApplication @PublishParameters
    #Get-ServiceFabricApplication

   New-ServiceFabricService -Stateless -ApplicationName "fabric:/Acme.Hierarchy" -ServiceTypeName "Acme.Hierarchy.HierarchyServiceType" -ServiceName "fabric:/Acme.Hierarchy/Acme.Hierarchy.HierarchyService"-InstanceCount 1 -PartitionSchemeSingleton
}

The above fails with the error

FabricElementNotFoundException

However, if you uncomment the line #Get-ServiceFabricApplication you will see that it does in fact return an application of

ApplicationName        : fabric:/Acme.Hierarchy
ApplicationTypeName    : Acme.HierarchyType
ApplicationTypeVersion : 1.0.0
ApplicationParameters  : { "_WFDebugParams_" = "[{"CodePackageName":"Code","Cod
                         ePackageLinkFolder":null,"ConfigPackageName":null,"Con
                         figPackageLinkFolder":null,"DataPackageName":null,"Dat
                         aPackageLinkFolder":null,"LockFile":null,"WorkingFolde
                         r":null,"ServiceManifestName":"Quantium.RetailToolkit.
                         Fabric.Hierarchy.HierarchyServicePkg","EntryPointType"
                         :"Main","DebugExePath":"C:\\Program Files 
                         (x86)\\Microsoft Visual Studio\\2017\\Professional\\Co
                         mmon7\\Packages\\Debugger\\VsDebugLaunchNotify.exe","D
                         ebugArguments":" 
                         {6286e1ef-907b-4371-961c-d833ab9509dd} -p [ProcessId] 
                         -tid [ThreadId]","DebugParametersFile":null}]";
                         "Acme.Hierarchy.HierarchyServ
                         ice_InstanceCount" = "1" }

Create application succeeded.

and running the command that fails after the publish script has finished works perfectly.

Does anyone have a solution as to how I can get a good developer experience by not using DefaultServices and instead using Powershell scripts?

Thanks in advance

KnowHoper
  • 4,352
  • 3
  • 39
  • 54

2 Answers2

7

I have updated the answer to add more details why the default services should not be used (In production only).

On service fabric, you have two options to create your services:

  • The Declarative way, done via Default Services feature where you describe services that should run as part of your application using the ApplicationManifest.
  • and the Dynamic(Imperative) way using powershell commands to create these services once the application is deployed.

The declarative way bring you the convenience of defining the expected structure of your application, so that Service Fabric does the job of creating and starting instances of your services according to the declaration in the ApplicationManifest. The convenience it gives you is very useful for development purposes, imagine if every time you had to debug an application, it had to: Build > Package > Deployed to Service Fabric > You had to manually start the many services that define your application. This would be too inconvenient, so that is why default service become handy.

Another scenario is when your application definition immutable, that means, the same number of services and instances will stay the same without variation throughout the time it is deployed in production.

But we know this is high unlikely that these definitions will keep the same throughout the years or even hours in a day, because the idea of microservices is that they should be scalable and flexible, so that we can tweak the configuration of individual services independent of each other.

By using default services, would be too complex for the orchestration logic identify what changes has been made on your services compared to the default specified in the original deployment, and in cases of conflict, which configuration should have priority, for example:

  • The deployed default services define a service with 5 instances, after it is deployed you execute an powershell script to update to 10 instances, then a new application upgrade comes in with the default services having 5 instances or a new value of 8, what should happen? which one is the correct?
  • You add an extra named services (service of same type with other name) to an existing deployment that is not defined in the default services, what happens when the new deployment comes in and say that this service should not be expected? Delete it? And the data? How this service should be removed from production? If I removed by mistake during development?
  • A new version deletes an existing service, the deployment fails, how the old service should be recreated? And if there was any data there to be migrated as part of the deployment?
  • A service has been renamed. How do I track that it was renamed instead of removing the old and adding a new one?

These is some of the many issues that can happen. This is why you should move away from default services and create them dynamically(imperatively), with dynamic services, service fabric will receive an upgrade command and what will happen is:

"This is my new application type package with new service type definitions, whatever version you get deployed there, replace for this version and keep the same configuration".

If a new configuration is required, you will provide as parameters for the deployment to override the old values or change it on a separate command. This will make things much simpler, as SF won't have to worry about different configurations and will just apply package changes to deployed services.

You can also find a nice info about these issues on these links:

Regarding your main question:

Does anyone have a solution as to how I can get a good developer experience by not using DefaultServices and instead using Powershell scripts?

if you want a good experience you should use the default services, it is intended for this, give the developer a good experience without worrying about services required to run at startup.

The trick is, during your CI process, you should remove the default services from the application manifest before you pack your application, so that you don't face the drawbacks later.

Removing the defaultServices during CI (Like VSTS Build), you have the benefits of defaultServices on dev environment, don't have to maintain the powershell script versions(if a new version comes along) and the removal of the default services is a very simple powershell script added as a build step. Other than that, everything keeps the same.

Ps: I don't have a real script at hand now, but will be very simple like this:

$appManifest = "C:\Temp\ApplicationManifest.xml"     #you pass as parameter

[xml]$xml = Get-Content $appManifest

$xml.ApplicationManifest.DefaultServices.RemoveAll()

$xml.save($appManifest)
Diego Mendes
  • 10,631
  • 2
  • 32
  • 36
  • More details would be great as the area of Default Services seems to be a bit poorly understood! But besides that, thanks for this very diligent and complete answer. We are already using an XDT approach in CI however I'd like to upskill the team in the Powershell APIs if possible as these are useful in production. Microsoft did say this approach is possible! – KnowHoper May 21 '18 at 10:00
  • I will update the answer later with more info when I get some time, If you want to create services using powershell, you can use these commands: https://learn.microsoft.com/en-us/powershell/module/servicefabric/new-servicefabricservice?view=azureservicefabricps – Diego Mendes May 21 '18 at 10:11
  • Thanks for the answer. The problem is, those commands don't work I'm the auto generated .ps1! – KnowHoper May 21 '18 at 10:12
  • I've updated the answer with more details. I would not recommend the approach you suggested (modifying the script file) because is not straight forward and will just add extra work. – Diego Mendes Jun 08 '18 at 22:25
  • Also, changing the deployment scripts might lead you to attaching the debugger manually for the services deployed outside of the debug cycle. – Diego Mendes Jun 08 '18 at 22:28
  • Thank you for the very detailed post! I must say though it is somewhat disappointing to see that the imperative approach is recommended for Service Fabric, including OP's reference to MS's own recommendation. The wider industry seems to be embracing declarative deployments, with Docker "stacks" and Kubernetes "deployments". Manually setting up each service in each env (dev/test/prod) would be rather error prone and the idea is to use the same tools throughout the cycle. Is it because of features SF is lacking or design differences the same thing is not possible or safe to do here? – kamilk Sep 14 '20 at 15:04
0

The solution here is to use a script named Start-Service.ps1 in the Scripts folder in Application project.

enter image description here

Below is an example script for the Data Aggregation sample Microsoft have provided.

$cloud = $false
$singleNode = $true
$constrainedNodeTypes = $false

$lowkey = "-9223372036854775808"
$highkey = "9223372036854775807" 

$countyLowKey = 0
$countyHighKey = 57000

$appName = "fabric:/DataAggregation"
$appType = "DataAggregationType"
$appInitialVersion = "1.0.0"

if($singleNode)
{
    $webServiceInstanceCount = -1
    $deviceCreationInstanceCount = -1
    $countyServicePartitionCount = 1
    $deviceActorServicePartitionCount = 1
    $doctorServicePartitionCount = 1
}
else
{
    $webServiceInstanceCount = @{$true=-1;$false=1}[$cloud -eq $true] 
    $deviceCreationInstanceCount = @{$true=-1;$false=1}[$cloud -eq $true] 
    $countyServicePartitionCount = @{$true=10;$false=5}[$cloud -eq $true]  
    $deviceActorServicePartitionCount = @{$true=15;$false=5}[$cloud -eq $true]  
    $doctorServicePartitionCount = @{$true=100;$false=5}[$cloud -eq $true]  

    if($constrainedNodeTypes)
    {
        $webServiceConstraint = "NodeType == "
        $countyServiceConstraint = "NodeType == "
        $nationalServiceConstraint = "NodeType == "
        $deviceServiceConstraint = "NodeType == "
        $doctorServiceConstraint = "NodeType == "   
        $deviceCreationServiceConstraint = "NodeType == "        
    }
    else
    {
        $webServiceConstraint = ""
        $countyServiceConstraint = ""
        $nationalServiceConstraint = ""
        $deviceServiceConstraint = ""
        $doctorServiceConstraint = ""
        $deviceCreationServiceConstraint = ""   
    }
}

$webServiceType = "DataAggregation.WebServiceType"
$webServiceName = "DataAggregation.WebService"

$nationalServiceType = "DataAggregation.NationalServiceType"
$nationalServiceName = "DataAggregation.NationalService"
$nationalServiceReplicaCount = @{$true=1;$false=3}[$singleNode -eq $true]  

$countyServiceType = "DataAggregation.CountyServiceType"
$countyServiceName = "DataAggregation.CountyService"
$countyServiceReplicaCount = @{$true=1;$false=3}[$singleNode -eq $true]  

$deviceCreationServiceType = "DataAggregation.DeviceCreationServiceType"
$deviceCreationServiceName = "DataAggregation.DeviceCreationService"

$doctorServiceType = "DataAggregation.DoctorServiceType"
$doctorServiceName = "DataAggregation.DoctorService"
$doctorServiceReplicaCount = @{$true=1;$false=3}[$singleNode -eq $true]

$deviceActorServiceType = "DeviceActorServiceType"
$deviceActorServiceName= "DataAggregation.DeviceActorService"
$deviceActorReplicaCount = @{$true=1;$false=3}[$singleNode -eq $true]

New-ServiceFabricService -ServiceTypeName $webServiceType -Stateless -ApplicationName $appName -ServiceName "$appName/$webServiceName" -PartitionSchemeSingleton -InstanceCount $webServiceInstanceCount -PlacementConstraint $webServiceConstraint -ServicePackageActivationMode ExclusiveProcess

#create national
New-ServiceFabricService -ServiceTypeName $nationalServiceType -Stateful -HasPersistedState -ApplicationName $appName -ServiceName "$appName/$nationalServiceName" -PartitionSchemeSingleton -MinReplicaSetSize $nationalServiceReplicaCount -TargetReplicaSetSize $nationalServiceReplicaCount -PlacementConstraint $nationalServiceConstraint -ServicePackageActivationMode ExclusiveProcess

#create county
New-ServiceFabricService -ServiceTypeName $countyServiceType -Stateful -HasPersistedState -ApplicationName $appName -ServiceName "$appName/$countyServiceName" -PartitionSchemeUniformInt64 -LowKey $countyLowKey -HighKey $countyHighKey -PartitionCount $countyServicePartitionCount -MinReplicaSetSize $countyServiceReplicaCount -TargetReplicaSetSize $countyServiceReplicaCount -PlacementConstraint $countyServiceConstraint -ServicePackageActivationMode ExclusiveProcess

#create doctor
New-ServiceFabricService -ServiceTypeName $doctorServiceType -Stateful -HasPersistedState -ApplicationName $appName -ServiceName "$appName/$doctorServiceName" -PartitionSchemeUniformInt64 -LowKey $lowkey -HighKey $highkey -PartitionCount $doctorServicePartitionCount -MinReplicaSetSize $doctorServiceReplicaCount -TargetReplicaSetSize $doctorServiceReplicaCount -PlacementConstraint $doctorServiceConstraint -ServicePackageActivationMode ExclusiveProcess

#create device
New-ServiceFabricService -ServiceTypeName $deviceActorServiceType -Stateful -HasPersistedState -ApplicationName $appName -ServiceName "$appName/$deviceActorServiceName" -PartitionSchemeUniformInt64 -LowKey $lowkey -HighKey $highkey -PartitionCount $deviceActorServicePartitionCount -MinReplicaSetSize $deviceActorReplicaCount -TargetReplicaSetSize $deviceActorReplicaCount -PlacementConstraint $deviceServiceConstraint -ServicePackageActivationMode ExclusiveProcess -Verbose

#create device creation
New-ServiceFabricService -ServiceTypeName $deviceCreationServiceType -Stateless -ApplicationName $appName -ServiceName "$appName/$deviceCreationServiceName" -PartitionSchemeSingleton -InstanceCount $deviceCreationInstanceCount -PlacementConstraint $deviceCreationServiceConstraint -ServicePackageActivationMode ExclusiveProcess
KnowHoper
  • 4,352
  • 3
  • 39
  • 54