Динамические настройки и переопределения Servicefabric
Есть ли способ вообще не рассказывать сервису о настройках и просто предоставлять их на уровне приложения?
Я все еще недоволен тем, как работает конфигурация servicefabric.
Насколько я могу судить, я должен указать в файле settings.xml службы все возможные значения конфигурации. Затем я могу переопределить их в ApplicationParameters приложения. По документации это выглядит так же, как и для переменных среды.
Сложность заключается в том, что наша конфигурация используется для гидратации опций во многих случаях с массивами.
Например, рассмотрим JSON:
{
"AuthorizationOptions": {
"Policies": [
{
"Name": "User",
"Groups": [ "Domain Users" ]
}
]
}
}
Есть 2 массива; что необходимо и полезно. Чтобы выразить это в конфигурации сервисной фабрики, это означает:
<Section Name="AuthorizationOptions">
<Parameter Name="Policies:0:Name" Value="User"/>
<Parameter Name="Policies:0:Groups:0" Value="Domain Users"/>
</Section>
Хотя перевод не приятен по сравнению со структурированным объектом, он полностью пригоден для использования.
Однако, если я не укажу раздел и параметры в сервисе, я не могу переопределить их в приложении. Поэтому в этом случае мне нужно будет определить точное количество политик и групп для каждой политики в службе, и приложение может изменить имя политики или значения группы, но не общее количество политик или общее количество групп.
Есть ли способ вообще не рассказывать сервису о настройках и просто предоставлять их на уровне приложения?
Если нет, то какие существуют альтернативы для повторного использования сервиса в приложениях, которые я, возможно, захочу использовать, чтобы по-разному предоставлять этот тип динамической конфигурации?
Последняя часть головоломки, которая может помочь в ответе на этот вопрос, заключается в том, что я использую некоторый предварительный код для перевода настроек фабричной службы в Microsoft.Extensions.Configuration.IConfiguration. Тем не менее, это просто принимает настройки, которые он находит; это не причина проблемы переопределения, с которой я сталкиваюсь.
Пример настройки сервиса. Xml:
<?xml version="1.0" encoding="utf-8" ?>
<Settings xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2011/01/fabric">
<Section Name="AuthorizationOptions">
<!-- I should not have to provide these at the application level!
However, it fails to deploy if I don't. -->
<Parameter Name="Policies:0:Name" Value="User"/>
<Parameter Name="Policies:0:Groups:0" Value="Domain Users"/>
</Section>
</Settings>
Пример приложения ApplicationManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ApplicationTypeName="ServiceFabric.ExampleType" ApplicationTypeVersion="1.0.0" xmlns="http://schemas.microsoft.com/2011/01/fabric">
<Parameters>
<Parameter Name="Service.Example_ASPNETCORE_ENVIRONMENT" DefaultValue="" />
<Parameter Name="Service.Example_InstanceCount" DefaultValue="-1" />
<Parameter Name="Service.Example_AuthorizationOptions_Policies_0_Name" DefaultValue="Users" />
<Parameter Name="Service.Example_AuthorizationOptions_Policies_0_Groups_0" DefaultValue="Domain Users" />
</Parameters>
<ServiceManifestImport>
<ServiceManifestImport>
<ServiceManifestRef ServiceManifestName="Service.ExamplePkg" ServiceManifestVersion="1.0.0" />
<ConfigOverrides>
<ConfigOverride Name="Config">
<Settings>
<Section Name="AuthorizationOptions">
<Parameter Name="Policies:0:Name" Value="[Service.Example_AuthorizationOptions_Policies_0_Name]" />
<Parameter Name="Policies:0:Groups:0" Value="[Service.Example_AuthorizationOptions_Policies_0_Groups_0]" />
</Section>
</Settings>
</ConfigOverride>
</ConfigOverrides>
<EnvironmentOverrides CodePackageRef="code">
<EnvironmentVariable Name="ASPNETCORE_ENVIRONMENT" Value="[Service.Example_ASPNETCORE_ENVIRONMENT]" />
</EnvironmentOverrides>
</ServiceManifestImport>
<DefaultServices>
<Service Name="Service.Example" ServicePackageActivationMode="ExclusiveProcess">
<StatelessService ServiceTypeName="Service.ExampleType" InstanceCount="[Service.Example_InstanceCount]">
<SingletonPartition />
</StatelessService>
</Service>
</DefaultServices>
</ApplicationManifest>
Пример приложения ApplicationParameters (Local.1Node.xml):
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Name="fabric:/ServiceFabric.Example" xmlns="http://schemas.microsoft.com/2011/01/fabric">
<Parameters>
<Parameter Name="Service.Example_ASPNETCORE_ENVIRONMENT" Value="Development" />
<Parameter Name="Service.Example_InstanceCount" Value="-1" />
<Parameter Name="Service.Example_AuthorizationOptions_Policies_0_Name" Value="Users" />
<Parameter Name="Service.Example_AuthorizationOptions_Policies_0_Groups_0" Value="Domain Users" />
</Parameters>
</Application>
Пример приложения PublishProfiles (Local.1Node.xml):
<?xml version="1.0" encoding="utf-8"?>
<PublishProfile xmlns="http://schemas.microsoft.com/2015/05/fabrictools">
<ClusterConnectionParameters />
<ApplicationParameterFile Path="..\ApplicationParameters\Local.1Node.xml" />
</PublishProfile>
Должен быть не связан, но пример потребления настроек:
internal sealed class Example : StatelessService
{
public Example(StatelessServiceContext context)
: base(context)
{ }
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new HttpSysCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting HttpSys on {url}");
return new WebHostBuilder()
.UseHttpSys(options =>
{
options.Authentication.Schemes = AuthenticationSchemes.Negotiate; // Microsoft.AspNetCore.Server.HttpSys
options.Authentication.AllowAnonymous = false;
}).ConfigureServices(services => services.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddServiceFabricConfiguration(FabricRuntime.GetActivationContext(), options => {
options.IncludePackageName=false;
});
})
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseUrls(url)
.Build();
}))
};
}
}
С этого момента все находится в объекте IConfiguration, как и ожидалось.
1 ответ
Существует множество способов настройки приложения Service Fabric, и каждый из этих подходов ставит перед вами разные задачи.
Команда SF рекомендует этот подход в документах, потому что вы можете лучше управлять версиями конфигураций и усложнять совершение ошибок, так как это явно объявлено в файле, я использовал несколько разных подходов из-за таких ограничений, как ваш, следующий подход может решить вашу проблему:
Сконфигурируйте, как в первоначальном подходе, но со сложными типами, которые хранятся в виде значений JSON: это наиболее близкое решение к рекомендованному дизайну, и вы по-прежнему можете контролировать версии конфигурации в системе контроля версий.
Это было бы что-то вроде:
Settings.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Settings xmlns... namespaces here...>
<Section Name="AuthorizationOptions">
<Parameter Name="Policies"/>
</Section>
</Settings>
ApplicationManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest ApplicationTypeName="ServiceFabric.ExampleType" ApplicationTypeVersion="1.0.0" xmlns:...namespaces....>
<Parameters>
<Parameter Name="Service.Example_ASPNETCORE_ENVIRONMENT" DefaultValue="" />
<Parameter Name="Service.Example_InstanceCount" DefaultValue="-1" />
<Parameter Name="Service.Example_AuthorizationOptions_Policies" DefaultValue="[]" />
</Parameters>
<ServiceManifestImport>
<ServiceManifestImport>
<ServiceManifestRef ServiceManifestName="Service.ExamplePkg" ServiceManifestVersion="1.0.0" />
<ConfigOverrides>
<ConfigOverride Name="Config">
<Settings>
<Section Name="AuthorizationOptions">
<Parameter Name="Policies" Value="[Service.Example_AuthorizationOptions_Policies]" />
</Section>
</Settings>
</ConfigOverride>
</ConfigOverrides>
<EnvironmentOverrides CodePackageRef="code">
<EnvironmentVariable Name="ASPNETCORE_ENVIRONMENT" Value="[Service.Example_ASPNETCORE_ENVIRONMENT]" />
</EnvironmentOverrides>
</ServiceManifestImport>
<DefaultServices>
<Service Name="Service.Example" ServicePackageActivationMode="ExclusiveProcess">
<StatelessService ServiceTypeName="Service.ExampleType" InstanceCount="[Service.Example_InstanceCount]">
<SingletonPartition />
</StatelessService>
</Service>
</DefaultServices>
</ApplicationManifest>
ApplicationParameters.xml
<?xml version="1.0" encoding="utf-8"?>
<Application Name="fabric:/ServiceFabric.Example" xmlns:...namespaces....>
<Parameters>
<Parameter Name="Service.Example_ASPNETCORE_ENVIRONMENT" Value="Development" />
<Parameter Name="Service.Example_InstanceCount" Value="-1" />
<Parameter Name="Service.Example_AuthorizationOptions_Policies" Value="[{'Name': 'User','Groups': ['Domain Users']}, {'Name': 'Admin','Groups': ['Administrators']}]" />
</Parameters>
</Application>
В вашем сервисном коде:
public class Policy
{
public string Name { get; set; }
public string[] Groups { get; set; }
}
var settings = this.Context.CodePackageActivationContext.GetConfigurationPackageObject("Config").Settings;
var authOptions = settings.Sections["AuthorizationOptions"].Parameters["Policies"].Value;
var obj = JsonConvert.DeserializeObject<Policy[]>(authOptions);
Вы можете пойти на уровень дальше и сохранить все
AuthorizationOptions
как JSON, но, как уже говорилось ранее, чем более универсальным он станет, тем легче будет совершать ошибки и труднее находить проблемы с конфигурацией.