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
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)
The solution here is to use a script named Start-Service.ps1 in the Scripts folder in Application project.
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
来源:https://stackoverflow.com/questions/50441340/auto-creation-of-service-without-defaultservices-on-developer-machines