ASP.NET Core no Reliable Services do Azure Service Fabric

O ASP.NET Core é uma estrutura multiplataforma e de código aberto. Essa estrutura foi projetada para a criação de aplicativos conectados à Internet e baseados em nuvem, como aplicativos Web, aplicativos de IoT e back-ends móveis.

Este artigo é um guia detalhado de hospedagem de serviços do ASP.NET Core nos Reliable Services do Service Fabric usando o conjunto Microsoft.ServiceFabric.AspNetCore. de pacotes NuGet.

Para obter um tutorial introdutório sobre o ASP.NET Core no Service Fabric e instruções sobre como configurar seu ambiente de desenvolvimento, consulte Tutorial: criar e implantar um aplicativo com um serviço de front-end da API Web do ASP.NET Core e um serviço de back-end com estado.

O restante deste artigo pressupõe que você já esteja familiarizado com o ASP.NET Core. Se não estiver, leia os Conceitos básicos do ASP.NET Core.

ASP.NET Core no ambiente do Service Fabric

Os aplicativos ASP.NET Core e Service Fabric podem ser executados no .NET Core ou no .NET Framework completo. O ASP.NET Core pode ser usado de duas maneiras diferentes no Service Fabric:

  • Hospedado como um executável convidado. Essa forma é usada principalmente para executar aplicativos do ASP.NET Core existentes no Service Fabric sem alterações de código.
  • Executar em um serviço confiável. Essa forma permite uma melhor integração com o runtime do Service Fabric e permite os serviços do ASP.NET Core com estado.

O restante deste artigo explica como usar o ASP.NET Core em um serviço confiável por meio dos componentes de integração do ASP.NET Core que são fornecidos com o SDK do Service Fabric.

Hospedagem de serviços do Service Fabric

No Service Fabric, uma ou mais instâncias e/ou réplicas do serviço são executadas em um processo de host do serviço: um arquivo executável que executa o código de serviço. Como autor do serviço, você é responsável pelo processo de host do serviço, e o Service Fabric o ativa e monitora para você.

O ASP.NET tradicional (até 5 MVC) está estreitamente relacionado ao IIS por meio de System.Web.dll. O Núcleo do ASP.NET fornece uma separação entre o servidor Web e o aplicativo Web. Essa separação permite que os aplicativos Web sejam portáteis entre diferentes servidores Web. Ele também permite que os servidores Web sejam auto-hospedados. Isso significa que você pode iniciar um servidor Web em seu próprio processo, ao invés de um processo de propriedade de um software de servidor Web dedicado, como os IIS.

Para combinar um serviço do Service Fabric ao ASP.NET, como um executável de convidado ou em um serviço confiável, você deve poder iniciar o ASP.NET no processo de host de serviço. A hospedagem interna do Núcleo do ASP.NET permite que você faça isso.

Hospedar o ASP.NET Core em um serviço confiável

Normalmente, aplicativos do Núcleo do ASP.NET auto-hospedados criam um WebHost no ponto de entrada do aplicativo, como o método static void Main() em Program.cs. Nesse caso, o ciclo de vida do WebHost está associado ao ciclo de vida do processo.

Hospedando o Núcleo do ASP.NET em um processo

Mas o ponto de entrada do aplicativo não é o lugar certo para criar um Webhost em um serviço confiável. Isso porque o ponto de entrada do aplicativo somente é usado para registrar um tipo de serviço com o runtime do Service Fabric de forma que ele possa criar instâncias daquele tipo de serviço. O WebHost deve ser criado em um serviço confiável em si. No processo de host do serviço, as instâncias de serviço e/ou réplicas podem passar por vários ciclos de vida.

Uma instância de um serviço confiável é representada por sua classe de serviço derivando de StatelessService ou StatefulService. A pilha de comunicação para um serviço está contida em uma implementação ICommunicationListener em sua classe de serviço. Os pacotes NuGet Microsoft.ServiceFabric.AspNetCore.* contêm implementações de ICommunicationListener que iniciam e gerenciam o WebHost do ASP.NET Core para Kestrel ou HTTP.sys em um serviço confiável.

Diagrama da hospedagem do ASP.NET Core em um serviço confiável

Núcleo do ASP.NET ICommunicationListeners

As implementações ICommunicationListener para Kestrel e HTTP.sys nos pacotes NuGet Microsoft.ServiceFabric.AspNetCore.* possuem padrões similares. Mas eles executam ações um pouco diferentes, específicas para cada servidor Web.

Ambos os ouvintes de comunicação fornecem um construtor que usa os seguintes argumentos:

  • ServiceContext serviceContext : Este é o objeto ServiceContext, que contém informações sobre o serviço em execução.
  • string endpointName : Este é o nome de uma configuração Endpoint em ServiceManifest.xml. É principalmente onde os dois ouvintes da comunicação são diferentes. O HTTP.sys exige uma configuração Endpoint, ao contrário do Kestrel.
  • Func<string, AspNetCoreCommunicationListener, IWebHost> build : Este é um lambda que você implementa e no qual cria e retorna um IWebHost. Ele permite que você configure IWebHost da maneira como faria normalmente em um aplicativo do ASP.NET Core. O lambda fornece uma URL que é gerada para você, dependendo das opções de integração do Service Fabric que você usar e da configuração Endpoint que você fornecer. Em seguida, você pode modificar ou usar essa URL para iniciar o servidor Web.

Middleware de integração do Service Fabric

O pacote do NuGet Microsoft.ServiceFabric.AspNetCore inclui o método de extensão UseServiceFabricIntegration no IWebHostBuilder que adiciona o middleware com reconhecimento do Service Fabric. Este middleware configura o Kestrel ou o HTTP.sys ICommunicationListener para registrar uma URL de serviço exclusiva com o Serviço de Nomenclatura do Service Fabric. Em seguida, ele valida as solicitações do cliente para garantir que os clientes estejam se conectando ao serviço certo.

Essa etapa é necessária para impedir que os clientes se conectem erroneamente ao serviço errado. Isso ocorre porque, em um ambiente de host compartilhado, como Service Fabric, vários aplicativos Web podem ser executados na mesma máquina física ou virtual, mas não usam nomes de host exclusivos. Esse cenário é descrito em mais detalhes na próxima seção.

Um caso de identidade incorreta

Independentemente do protocolo, as réplicas de serviço escutam em uma combinação de IP:porta exclusiva. Depois que uma réplica de serviço começa a escutar em um ponto de extremidade IP:porta, ela relata esse endereço do ponto de extremidade para o Serviço de Nomenclatura do Service Fabric. Lá, os clientes ou outros serviços podem descobri-lo. Se os serviços usarem portas de aplicativos atribuídas dinamicamente, uma réplica de serviço poderá usar coincidentemente o mesmo ponto de extremidade IP:porta que outro serviço que estava anteriormente na mesma máquina física ou virtual. Isso pode fazer com que um cliente se conecte por engano ao serviço incorreto. Este cenário poderá acontecer se a seguinte sequência de eventos ocorrer:

  1. O serviço A escuta em 10.0.0.1:30000 via HTTP.
  2. O cliente resolve o serviço A e obtém o endereço 10.0.0.1:30000.
  3. O Serviço A move-se para um nó diferente.
  4. O serviço B é colocado em 10.0.0.1 e usa coincidentemente a mesma porta 30000.
  5. O cliente tenta se conectar ao serviço A com o endereço de cache 10.0.0.1:30000.
  6. Agora o cliente é conectado com êxito ao serviço B sem perceber que está conectado ao serviço errado.

Isso pode causar bugs em momentos aleatórios, que podem ser difíceis de diagnosticar.

Usando URLs de serviço exclusivas

Para evitar esses bugs, os serviços podem postar um ponto de extremidade para o Serviço de Nomenclatura com um identificador exclusivo e validá-lo durante as solicitações do cliente. Essa é uma ação cooperativa entre serviços em um ambiente confiável de locatário não hostil. Ela não fornece autenticação segura em um ambiente de locatário hostil.

Em um ambiente confiável, o middleware adicionado pelo método UseServiceFabricIntegration acrescenta automaticamente um identificador exclusivo ao endereço postado para o Serviço de Nomenclatura. Ele valida esse identificador em cada solicitação. Se o identificador não for correspondente, o middleware retornará imediatamente uma resposta HTTP 410 Não existe mais.

Os serviços que usam uma porta atribuída dinamicamente devem fazer uso desse middleware.

Os serviços que usam uma porta exclusiva fixa não têm esse problema em um ambiente cooperativo. Uma porta fixa é normalmente usada para serviços voltados para o exterior que exigem uma porta conhecida para a conexão de aplicativos clientes. Por exemplo, a maioria dos aplicativos Web voltados para a Internet usará a porta 80 ou 443 para conexões do navegador da Web. Nesse caso, o identificador exclusivo não deve ser habilitado.

O diagrama a seguir mostra o fluxo de solicitação com o middleware habilitado:

Integração de núcleo do ASP.NET do Service Fabric

As implementações ICommunicationListener do Kestrel e do HTTP.sys usam esse mecanismo exatamente da mesma maneira. Embora o HTTP.sys possa diferenciar internamente as solicitações com base em caminhos de URL exclusivos usando o recurso subjacente de compartilhamento de porta HTTP.sys, essa funcionalidade não é usada pela implementação ICommunicationListener do HTTP.sys. Isso ocorre porque ela resulta em códigos de status de erro HTTP 503 e HTTP 404 no cenário descrito anteriormente. Isso, por sua vez, dificulta que os clientes determinem a intenção do erro, já que o HTTP 503 e HTTP 404 são frequentemente usados para indicar outros erros.

Portanto, as implementações ICommunicationListener do HTTP.sys e do Kestrel são padronizadas no middleware fornecido pelo método de extensão UseServiceFabricIntegration. Portanto, os clientes só precisam executar uma ação de nova resolução do ponto de extremidade de serviço em respostas HTTP 410.

HTTP.sys nos Reliable Services

Você pode usar o HTTP.sys em Reliable Services importando o pacote NuGet Microsoft.ServiceFabric.AspNetCore.HttpSys. Este pacote contém HttpSysCommunicationListener, uma implementação de ICommunicationListener. HttpSysCommunicationListener permite que você crie um WebHost do ASP.NET Core dentro de um serviço confiável usando o HTTP.sys como o servidor Web.

O HTTP.sys se baseia na API do Windows HTTP Server. Essa API usa o driver de kernel HTTP.sys para processar solicitações HTTP e roteá-las para processos que executam os aplicativos Web. Isso permite que vários processos na mesma máquina física ou virtual hospedem aplicativos Web na mesma porta, sem ambiguidade graças a um caminho de URL ou nome do host exclusivo. Esses recursos são úteis no Service Fabric para hospedar vários sites no mesmo cluster.

Observação

A implementação do HTTP.sys funciona apenas na plataforma Windows.

O diagrama a seguir ilustra como o HTTP.sys usa o driver de kernel HTTP.sys no Windows para o compartilhamento de porta:

Diagrama HTTP.sys

HTTP.sys em um serviço sem estado

Para usar HttpSys em um serviço sem estado, substitua o método CreateServiceInstanceListeners e retorne uma instância HttpSysCommunicationListener:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new ServiceInstanceListener[]
    {
        new ServiceInstanceListener(serviceContext =>
            new HttpSysCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
                new WebHostBuilder()
                    .UseHttpSys()
                    .ConfigureServices(
                        services => services
                            .AddSingleton<StatelessServiceContext>(serviceContext))
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                    .UseStartup<Startup>()
                    .UseUrls(url)
                    .Build()))
    };
}

HTTP.sys em um serviço com estado

O HttpSysCommunicationListener no momento não é projetado para uso em serviços com estado, devido a complicações com o recurso de compartilhamento de porta subjacente, HTTP.sys. Para obter mais informações, consulte a seção a seguir sobre a alocação de porta dinâmica com o HTTP.sys. Para serviços com estado, o Kestrel é o servidor Web sugerido.

Configuração de ponto de extremidade

Uma configuração Endpoint é necessária para servidores Web que usam a API do Windows HTTP Server, incluindo o HTTP.sys. Servidores Web que usam a API do Windows HTTP Server devem primeiro reservar a URL com HTTP.sys (isso normalmente é feito com a ferramenta netsh).

Esta ação exige privilégios elevados que seus serviços não têm por padrão. As opções "http" ou "https" para a propriedade Protocol da configuração Endpoint em ServiceManifest.xml são usadas especificamente para instruir o runtime do Service Fabric a registrar uma URL com HTTP.sys em seu nome. Ele faz isso usando o prefixo de URL curinga forte.

Por exemplo, para reservar http://+:80 para um serviço, use a seguinte configuração no ServiceManifest.xml:

<ServiceManifest ... >
    ...
    <Resources>
        <Endpoints>
            <Endpoint Name="ServiceEndpoint" Protocol="http" Port="80" />
        </Endpoints>
    </Resources>

</ServiceManifest>

E o nome do ponto de extremidade deve ser passado para o construtor HttpSysCommunicationListener:

 new HttpSysCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
 {
     return new WebHostBuilder()
         .UseHttpSys()
         .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
         .UseUrls(url)
         .Build();
 })

Usar HTTP.sys com uma porta estática

Para usar uma porta estática com o HTTP.sys, forneça o número da porta na configuração Endpoint:

  <Resources>
    <Endpoints>
      <Endpoint Protocol="http" Name="ServiceEndpoint" Port="80" />
    </Endpoints>
  </Resources>

Usar HTTP.sys com uma porta dinâmica

Para usar uma porta atribuída dinamicamente com o HTTP.sys, omita a propriedade Port na configuração Endpoint:

  <Resources>
    <Endpoints>
      <Endpoint Protocol="http" Name="ServiceEndpoint" />
    </Endpoints>
  </Resources>

Uma porta dinâmica alocada por uma configuração Endpoint fornece apenas uma porta por processo de host. O modelo de hospedagem atual do Service Fabric permite múltiplas instâncias e/ou réplicas de serviços a serem hospedadas no mesmo processo. Isso significa que cada uma compartilhará a mesma porta quando alocada por meio da configuração Endpoint. Várias instâncias do HTTP.sys podem compartilhar uma porta usando o recurso de compartilhamento de porta HTTP.sys subjacente. Mas não há suporte para HttpSysCommunicationListener devido às complicações que ele apresenta para solicitações do cliente. Para o uso de portas dinâmicas, o Kestrel é o servidor Web sugerido.

Kestrel em Serviços Confiáveis

Você pode usar o Kestrel nos Reliable Services importando o pacote Microsoft.ServiceFabric.AspNetCore.Kestrel do NuGet. Este pacote contém KestrelCommunicationListener, uma implementação de ICommunicationListener. KestrelCommunicationListener permite que você crie um WebHost do ASP.NET Core dentro de um serviço confiável usando o Kestrel como o servidor Web.

O Kestrel é um servidor Web multiplataforma para o ASP.NET Core. Diferentemente do HTTP.sys, o Kestrel não usa um gerenciador de ponto de extremidade centralizado. Também diferente do HTTP.sys, o Kestrel não dá suporte ao compartilhamento de porta entre vários processos. Cada instância do Kestrel deve usar uma porta exclusiva. Para obter mais informações sobre o Kestrel, consulte os Detalhes da implementação.

Diagrama do Kestrel

Kestrel em um serviço sem estado

Para usar Kestrel em um serviço sem estado, substitua o método CreateServiceInstanceListeners e retorne uma instância KestrelCommunicationListener:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new ServiceInstanceListener[]
    {
        new ServiceInstanceListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
                new WebHostBuilder()
                    .UseKestrel()
                    .ConfigureServices(
                        services => services
                            .AddSingleton<StatelessServiceContext>(serviceContext))
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl)
                    .UseStartup<Startup>()
                    .UseUrls(url)
                    .Build();
            ))
    };
}

Kestrel em um serviço com estado

Para usar Kestrel em um serviço com estado, substitua o método CreateServiceReplicaListeners e retorne uma instância KestrelCommunicationListener:

protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
    return new ServiceReplicaListener[]
    {
        new ServiceReplicaListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, (url, listener) =>
                new WebHostBuilder()
                    .UseKestrel()
                    .ConfigureServices(
                         services => services
                             .AddSingleton<StatefulServiceContext>(serviceContext)
                             .AddSingleton<IReliableStateManager>(this.StateManager))
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl)
                    .UseStartup<Startup>()
                    .UseUrls(url)
                    .Build();
            ))
    };
}

Neste exemplo, uma instância única de IReliableStateManager é fornecida ao contêiner de injeção de dependência do WebHost. Isso não é estritamente necessário, mas permite que você use IReliableStateManager e Coletas Confiáveis em seus métodos de ação do controlador MVC.

Um nome de configuração Endpointnão é fornecido para KestrelCommunicationListener em um serviço com estado. Isso será explicado mais detalhadamente na seção a seguir.

Configurar o Kestrel para usar HTTPS

Ao habilitar o HTTPS com o Kestrel no seu serviço, você precisará definir várias opções de escuta. Atualize o ServiceInstanceListener para usar um ponto de extremidade EndpointHttps e escutar uma porta específica (como a porta 443). Ao configurar o host da Web para usar o servidor Web Kestrel, será preciso configurar o Kestrel para escutar endereços IPv6 em as interfaces de rede:

new ServiceInstanceListener(
serviceContext =>
    new KestrelCommunicationListener(
        serviceContext,
        "EndpointHttps",
        (url, listener) =>
        {
            ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

            return new WebHostBuilder()
                .UseKestrel(opt =>
                {
                    int port = serviceContext.CodePackageActivationContext.GetEndpoint("EndpointHttps").Port;
                    opt.Listen(IPAddress.IPv6Any, port, listenOptions =>
                    {
                        listenOptions.UseHttps(GetCertificateFromStore());
                        listenOptions.NoDelay = true;
                    });
                })
                .ConfigureAppConfiguration((builderContext, config) =>
                {
                    config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
                })

                .ConfigureServices(
                    services => services
                        .AddSingleton<HttpClient>(new HttpClient())
                        .AddSingleton<FabricClient>(new FabricClient())
                        .AddSingleton<StatelessServiceContext>(serviceContext))
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseStartup<Startup>()
                .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                .UseUrls(url)
                .Build();
        }))

Para ver um exemplo completo em um tutorial, confira Configurar o Kestrel para usar HTTPS.

Configuração de ponto de extremidade

Uma configuração Endpoint não é necessária para usar o Kestrel.

O Kestrel é um servidor Web autônomo simples. Ao contrário do HTTP.sys (ou HttpListener), ele não precisa de uma configuração Endpoint no ServiceManifest.xml porque ele não requer o registro da URL antes de iniciar.

Usar Kestrel com uma porta estática

Você pode configurar uma porta estática na configuração Endpoint do ServiceManifest.xml para uso com o Kestrel. Embora não seja estritamente necessário, há dois benefícios potenciais:

  • Se a porta não estiver no intervalo de portas do aplicativo, ela será aberta por meio do firewall do sistema operacional pelo Service Fabric.
  • A URL fornecida através de KestrelCommunicationListener usará essa porta.
  <Resources>
    <Endpoints>
      <Endpoint Protocol="http" Name="ServiceEndpoint" Port="80" />
    </Endpoints>
  </Resources>

Se um Endpoint for configurado, o nome deverá ser passado para o construtor KestrelCommunicationListener:

new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) => ...

Se o ServiceManifest.xml não usar uma configuração Endpoint, omita o nome no construtor KestrelCommunicationListener. Nesse caso, ele usará uma porta dinâmica. Para obter mais informações sobre este assunto, confira a próxima seção.

Usar Kestrel com uma porta dinâmica

O Kestrel não pode usar a atribuição de porta automática da configuração Endpoint no ServiceManifest.xml. Isso ocorre porque a atribuição automática de porta de uma configuração Endpoint atribui uma porta exclusiva por processo de host, e um único processo de host pode conter várias instâncias do Kestrel. Isso não funciona com o Kestrel porque não dá suporte ao compartilhamento de porta. Portanto, cada instância do Kestrel deve ser aberta em uma porta exclusiva.

Para usar a atribuição de porta dinâmica com o Kestrel, omita inteiramente a configuração Endpoint no ServiceManifest.xml e não passe um nome de ponto de extremidade para o construtor KestrelCommunicationListener, conforme a seguir:

new KestrelCommunicationListener(serviceContext, (url, listener) => ...

Nessa configuração, KestrelCommunicationListener selecionará automaticamente uma porta não utilizada do intervalo de portas de aplicativo.

Para o HTTPS, ele deve ter o ponto de extremidade configurado com o protocolo HTTPS sem uma porta especificada no ServiceManifest.xml e passar o nome do ponto de extremidade para o construtor KestrelCommunicationListener.

Integração de hospedagem mínima e IHost

Além de IWebHost/IWebHostBuilder, KestrelCommunicationListener e HttpSysCommunicationListener dê suporte à criação de serviços do ASP.NET Core usando IHost/IHostBuilder. Isso está disponível na versão v5.2.1363 e posteriores dos pacotes Microsoft.ServiceFabric.AspNetCore.Kestrel e Microsoft.ServiceFabric.AspNetCore.HttpSys.

// Stateless Service
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new ServiceInstanceListener[]
    {
        new ServiceInstanceListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
            {
                return Host.CreateDefaultBuilder()
                        .ConfigureWebHostDefaults(webBuilder =>
                        {
                            webBuilder.UseKestrel()
                                .UseStartup<Startup>()
                                .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                                .UseContentRoot(Directory.GetCurrentDirectory())
                                .UseUrls(url);
                        })
                        .ConfigureServices(services => services.AddSingleton<StatelessServiceContext>(serviceContext))
                        .Build();
            }))
    };
}

// Stateful Service
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
    return new ServiceReplicaListener[]
    {
        new ServiceReplicaListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
            {
                return Host.CreateDefaultBuilder()
                        .ConfigureWebHostDefaults(webBuilder =>
                        {
                            webBuilder.UseKestrel()
                                .UseStartup<Startup>()
                                .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl)
                                .UseContentRoot(Directory.GetCurrentDirectory())
                                .UseUrls(url);
                        })
                        .ConfigureServices(services =>
                        {
                            services.AddSingleton<StatefulServiceContext>(serviceContext);
                            services.AddSingleton<IReliableStateManager>(this.StateManager);
                        })
                        .Build();
            }))
    };
}

Observação

Como KestrelCommunicationListener e o HttpSysCommunicationListener são destinados a serviços Web, é necessário registrar/configurar um servidor Web (usando o método ConfigureWebHostDefaults ou ConfigureWebHost) no IHost

O ASP.NET 6 introduziu o modelo de Hospedagem Mínima, que é um modo mais simplificado de criar aplicativos Web. O modelo de hospedagem mínima também pode ser usado com KestrelCommunicationListener e HttpSysCommunicationListener.

// Stateless Service
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new ServiceInstanceListener[]
    {
        new ServiceInstanceListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
            {
                var builder = WebApplication.CreateBuilder();

                builder.Services.AddSingleton<StatelessServiceContext>(serviceContext);
                builder.WebHost
                            .UseKestrel()
                            .UseContentRoot(Directory.GetCurrentDirectory())
                            .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                            .UseUrls(url);

                builder.Services.AddControllersWithViews();

                var app = builder.Build();

                if (!app.Environment.IsDevelopment())
                {
                    app.UseExceptionHandler("/Home/Error");
                }

                app.UseHttpsRedirection();
                app.UseStaticFiles();
                app.UseRouting();
                app.UseAuthorization();
                app.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");

                return app;
            }))
    };
}
// Stateful Service
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
    return new ServiceReplicaListener[]
    {
        new ServiceReplicaListener(serviceContext =>
            new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
            {
                var builder = WebApplication.CreateBuilder();

                builder.Services
                            .AddSingleton<StatefulServiceContext>(serviceContext)
                            .AddSingleton<IReliableStateManager>(this.StateManager);
                builder.WebHost
                            .UseKestrel()
                            .UseContentRoot(Directory.GetCurrentDirectory())
                            .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl)
                            .UseUrls(url);

                builder.Services.AddControllersWithViews();

                var app = builder.Build();

                if (!app.Environment.IsDevelopment())
                {
                    app.UseExceptionHandler("/Home/Error");
                }
                app.UseStaticFiles();
                app.UseRouting();
                app.UseAuthorization();
                app.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");

                return app;
            }))
    };
}

Provedor de configuração do Service Fabric

A configuração de aplicativos no ASP.NET Core se baseia em pares chave-valor estabelecidos pelos provedores de configuração. Leia Configuração no ASP.NET Core para saber mais sobre o suporte geral de configuração do ASP.NET Core.

Esta seção descreve como o provedor de configuração do Service Fabric se integra com a configuração do ASP.NET Core importando o pacote Microsoft.ServiceFabric.AspNetCore.Configuration do NuGet.

Extensões de inicialização do AddServiceFabricConfiguration

Depois de importar o pacote Microsoft.ServiceFabric.AspNetCore.Configuration do NuGet, você precisa registrar a fonte de configuração do Service Fabric com a API de configuração do ASP.NET Core. Faça isso verificando as extensões AddServiceFabricConfiguration no namespace Microsoft.ServiceFabric.AspNetCore.Configuration em relação a IConfigurationBuilder.

using Microsoft.ServiceFabric.AspNetCore.Configuration;

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddServiceFabricConfiguration() // Add Service Fabric configuration settings.
        .AddEnvironmentVariables();
    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

Agora, o serviço do ASP.NET Core pode acessar as definições de configuração do Service Fabric, assim como qualquer outra configuração de aplicativo. Por exemplo, você pode usar o padrão de opções para carregar configurações em objetos fortemente tipados.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration);  // Strongly typed configuration object.
    services.AddMvc();
}

Mapeamento de chave padrão

Por padrão, o provedor de configuração do Service Fabric inclui o nome do pacote, da seção e da propriedade. Juntos, eles formam a chave de configuração do ASP.NET Core, da seguinte maneira:

$"{this.PackageName}{ConfigurationPath.KeyDelimiter}{section.Name}{ConfigurationPath.KeyDelimiter}{property.Name}"

Por exemplo, se você tiver um pacote de configuração chamado MyConfigPackage com o conteúdo a seguir, o valor de configuração estará disponível no IConfiguration do ASP.NET Core por meio do MyConfigPackage:MyConfigSection:MyParameter.

<?xml version="1.0" encoding="utf-8" ?>
<Settings xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2011/01/fabric">  
  <Section Name="MyConfigSection">
    <Parameter Name="MyParameter" Value="Value1" />
  </Section>  
</Settings>

Opções de configuração do Service Fabric

O provedor de configuração do Service Fabric também dá suporte a ServiceFabricConfigurationOptions para alterar o comportamento padrão do mapeamento de chaves.

Configurações criptografadas

O Service Fabric dá suporte a configurações criptografadas, assim como seu provedor de configuração. As configurações criptografadas não são descriptografadas para o IConfiguration do ASP.NET Core por padrão. Em vez disso, os valores criptografados são armazenados lá. Mas se você quiser descriptografar o valor a ser armazenado na IConfiguration do ASP.NET Core, poderá definir o sinalizador DecryptValue como false na extensão AddServiceFabricConfiguration, da seguinte forma:

public Startup()
{
    ICodePackageActivationContext activationContext = FabricRuntime.GetActivationContext();
    var builder = new ConfigurationBuilder()        
        .AddServiceFabricConfiguration(activationContext, (options) => options.DecryptValue = false); // set flag to decrypt the value
    Configuration = builder.Build();
}

Pacotes de configuração multiplataforma

O Service Fabric dá suporte a vários pacotes de configuração. Por padrão, o nome do pacote é incluído na chave de configuração. Mas você pode definir o sinalizador IncludePackageName como false, da seguinte forma:

public Startup()
{
    ICodePackageActivationContext activationContext = FabricRuntime.GetActivationContext();
    var builder = new ConfigurationBuilder()        
        // exclude package name from key.
        .AddServiceFabricConfiguration(activationContext, (options) => options.IncludePackageName = false); 
    Configuration = builder.Build();
}

Mapeamento de chave personalizado, extração de valor e população de dados

O provedor de configuração do Service Fabric também dá suporte a cenários mais avançados para personalizar o mapeamento de chave com o ExtractKeyFunc e extrair de forma personalizada os valores com o ExtractValueFunc. Você pode até mesmo alterar todo o processo de popular os dados de uma configuração do Service Fabric para o ASP.NET Core usando ConfigAction.

Os exemplos a seguir ilustram como usar ConfigAction para personalizar a população de dados:

public Startup()
{
    ICodePackageActivationContext activationContext = FabricRuntime.GetActivationContext();
    
    this.valueCount = 0;
    this.sectionCount = 0;
    var builder = new ConfigurationBuilder();
    builder.AddServiceFabricConfiguration(activationContext, (options) =>
        {
            options.ConfigAction = (package, configData) =>
            {
                ILogger logger = new ConsoleLogger("Test", null, false);
                logger.LogInformation($"Config Update for package {package.Path} started");

                foreach (var section in package.Settings.Sections)
                {
                    this.sectionCount++;

                    foreach (var param in section.Parameters)
                    {
                        configData[options.ExtractKeyFunc(section, param)] = options.ExtractValueFunc(section, param);
                        this.valueCount++;
                    }
                }

                logger.LogInformation($"Config Update for package {package.Path} finished");
            };
        });
  Configuration = builder.Build();
}

Atualizações de configuração

O provedor de configuração do Service Fabric também dá suporte a atualizações de configuração. Você pode usar o ASP.NET Core IOptionsMonitor para receber notificações de alteração e, em seguida, usar IOptionsSnapshot para recarregar os dados de configuração. Para obter mais informações, confira Opções do ASP.NET Core.

Essas opções têm suporte por padrão. Nenhuma codificação adicional é necessária para habilitar atualizações de configuração.

Cenários e configurações

Esta seção fornece a combinação de servidor Web, configuração de porta, opções de integração do Service Fabric e configurações diversas que recomendamos para solucionar os problemas dos seguintes cenários:

  • Serviços sem monitoração de estado do Núcleo do ASP.NET expostos externamente
  • Serviço do ASP.NET Core sem estado somente interno
  • Serviço do ASP.NET Core com estado somente interno

Um serviço exposto externamente é aquele que expõe um ponto de extremidade chamado de fora do cluster, geralmente por meio de um balanceador de carga.

Um serviço somente interno é aquele cujo ponto de extremidade só é chamado por meio do cluster.

Observação

Os pontos de extremidade do serviço com estado geralmente não devem ser expostos à Internet. Os clusters por trás dos balanceadores de carga que não reconhecem a resolução de serviço do Service Fabric, como o Azure Load Balancer, não terão capacidade de expor serviços com estado. Isso ocorre porque o balanceador de carga não poderá localizar e rotear o tráfego para a réplica de serviço com estado apropriada.

Serviços sem monitoração de estado do Núcleo do ASP.NET expostos externamente

O Kestrel é o servidor Web sugerido para serviços de front-end que expõem pontos de extremidade HTTP externos para a Internet. No Windows, o HTTP.sys pode fornecer a capacidade de compartilhamento de porta, o que permite a hospedagem de diversos serviços Web no mesmo conjunto de nós usando a mesma porta. Nesse cenário, os serviços Web são diferenciados por caminho ou nome de host, sem depender de um proxy de front-end ou gateway para fornecer o roteamento HTTP.

Quando exposto à Internet, um serviço sem estado deve usar um ponto de extremidade conhecido e estável que possa ser acessado por meio de um balanceador de carga. Você fornecerá essa URL para os usuários do seu aplicativo. Recomendamos a seguinte configuração:

Tipo Recomendação Observações
Servidor Web Kestrel O Kestrel é o servidor Web preferencial, pois ele tem suporte no Windows e no Linux.
Configuração de portas static Uma porta estática conhecida deve ser configurada na configuração do Endpoints de ServiceManifest.XML, como 80 para HTTP ou 443 para HTTPS.
ServiceFabricIntegrationOptions Nenhum Use a opção ServiceFabricIntegrationOptions.None quando a configuração de middleware de integração do Service Fabric para que o serviço não tente validar as solicitações de entrada para um identificador exclusivo. Os usuários externos do aplicativo não saberão as informações de identificação exclusivas usadas pelo middleware.
Contagem de Instâncias -1 Em casos de uso típicos, a configuração de contagem de instâncias deve ser definida como -1. Isso é feito para que uma instância esteja disponível em todos os nós que recebam tráfego de um balanceador de carga.

Se vários serviços expostos externamente compartilharem o mesmo conjunto de nós, é possível usar o HTTP.sys com um caminho de URL exclusivo, mas estável. É possível conseguir isso modificando-se a URL fornecida ao configurar IWebHost. Observe que isso se aplica somente ao HTTP.sys.

new HttpSysCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
    url += "/MyUniqueServicePath";

    return new WebHostBuilder()
        .UseHttpSys()
        ...
        .UseUrls(url)
        .Build();
})

Serviço do Núcleo do ASP.NET sem estado somente para uso interno

Os serviços sem monitoração de estado que são chamados apenas de dentro do cluster devem usar URLs exclusivas e portas atribuídas dinamicamente para garantir a cooperação entre vários serviços. Recomendamos a seguinte configuração:

Tipo Recomendação Observações
Servidor Web Kestrel Embora seja possível usar o HTTP.sys para serviços internos sem estado, o Kestrel é o melhor servidor para permitir que várias instâncias do serviço compartilhem um host.
Configuração de portas atribuídas dinamicamente Várias réplicas de um serviço com estado podem compartilhar um processo de host ou o sistema operacional do host e, assim, precisarão de portas exclusivas.
ServiceFabricIntegrationOptions UseUniqueServiceUrl Com a atribuição dinâmica de portas essa configuração impede o problema de confusão de identidade descrito anteriormente.
InstanceCount any A configuração de contagem de instâncias pode ser definida como qualquer valor necessário para operar o serviço.

Somente interno serviço com estado do Núcleo do ASP.NET

Os serviços com monitoração de estado que são chamados apenas de dentro do cluster devem usar portas atribuídas dinamicamente para garantir a cooperação entre vários serviços. Recomendamos a seguinte configuração:

Tipo Recomendação Observações
Servidor Web Kestrel O HttpSysCommunicationListener não foi projetado para uso pelos serviços com estado em que as réplicas compartilham um processo de host.
Configuração de portas atribuídas dinamicamente Várias réplicas de um serviço com estado podem compartilhar um processo de host ou o sistema operacional do host e, assim, precisarão de portas exclusivas.
ServiceFabricIntegrationOptions UseUniqueServiceUrl Com a atribuição dinâmica de portas essa configuração impede o problema de confusão de identidade descrito anteriormente.

Próximas etapas

Depurar seu aplicativo do Service Fabric usando o Visual Studio