Desacoplando aplicações em Azure Container Apps utilizando Dapr
Published Oct 16 2023 05:25 PM 2,801 Views
Microsoft

O avanço das arquiteturas de microsserviços levou a uma reorganização das responsabilidades entre serviços. Isso impulsionou a adoção de contêineres e consequentemente, soluções de orquestração como o Kubernetes. No entanto, essa abordagem trouxe desafios, como o acoplamento excessivo entre aplicações e serviços externos.

Para mitigar isso, surgiu o Dapr, um runtime que permite comunicação desacoplada entre aplicações. Este artigo foca em apresentar o Dapr e como configurá-lo no Azure Container Apps, abordando funcionalidades essenciais e procedimentos práticos para uma integração bem-sucedida.

 

Azure Container Apps

O Azure Container Apps é a solução de orquestração de contêineres do Azure. Ele permite que você implante e gerencie contêineres sem se preocupar com a infraestrutura subjacente. O Azure Container Apps é baseado no Azure Kubernetes Service, mas oferece uma experiência simplificada para implantar e gerenciar contêineres.

Podemos definir o Azure Container Apps como sendo uma solução serverless de Kubernetes.

Esse serviço é um ótimo acelerador para empresas que estão iniciando sua jornada de adoção de contêineres, pois permite que você implante e gerencie contêineres sem se preocupar com a infraestrutura.

 

Dapr

O Dapr codifica as melhores práticas para a construção de aplicativos baseados em microsserviços em APIs abertas e independentes chamadas de building block, que permitem que você crie aplicativos portáteis com a linguagem e o framework de sua escolha. Cada building block é totalmente independente, e você pode usar um, alguns ou todos eles em seu aplicativo.

Usando o Dapr, você pode migrar gradualmente seus aplicativos existentes para uma arquitetura de microsserviços, adotando padrões nativos da nuvem, como dimensionamento sob demanda, resiliência e implantações independentes.

Além disso, o Dapr é independente de plataforma, o que significa que você pode executar seus aplicativos localmente, em qualquer cluster Kubernetes, em máquinas virtuais ou físicas e em outros ambientes de hospedagem que o Dapr integra. Isso permite que você construa aplicativos baseados em microsserviços que podem ser executados na nuvem e na borda.

Arquitetura DaprArquitetura Dapr

 

Building Blocks

Um building block é uma API HTTP ou gRPC, que pode ser chamada a partir do seu código e utiliza um ou mais componentes do Dapr. O Dapr é composto por um conjunto de unidades de construção de API, com a capacidade de adicionar novas unidades de construção para estender suas funcionalidades.

Abaixo está uma lista dos principais building blocks do Dapr:

  1. State:

    • O building block State permite que os aplicativos armazenem e recuperem estado de maneira confiável. Ele oferece suporte a várias opções de estado, incluindo estado persistente e temporário.
  2. Pub/Sub:

    • O Pub/Sub (Publicação/Assinatura) facilita a comunicação entre os diferentes componentes de um aplicativo distribuído. Os aplicativos podem publicar eventos e se inscrever para receber notificações quando esses eventos ocorrem.
  3. Bindings:

    • Bindings permitem que os aplicativos interajam facilmente com recursos externos, como bancos de dados, sistemas de mensagens e serviços da nuvem, por meio de adaptadores pré-construídos.
  4. Secrets:

    • O building block Secrets gerencia e fornece acesso seguro a segredos sensíveis, como chaves de autenticação e senhas, para os aplicativos.
  5. Actors:

    • O Actors é um modelo de programação baseado em atores que facilita a criação de aplicativos de estado escaláveis e com estado isolado.
  6. Observability:

    • O Observability é uma parte fundamental do Dapr que fornece recursos de rastreamento, métricas e registro para facilitar a monitoração e solução de problemas de aplicativos.
  7. Middleware:

    • Middleware permite a adição de funcionalidades personalizadas a aplicativos Dapr, como autenticação, autorização e manipulação de solicitações HTTP.
  8. HTTP API:

    • O HTTP API permite que os aplicativos exponham APIs HTTP de maneira simplificada, facilitando a comunicação com outros serviços.

Na perspectiva das aplicações em execução no Kubernetes, esses building blocks são implementados como sidecars de contêineres. Isso significa que cada building block é executado como um contêiner adjacente à aplicação principal.

Integração Building BlockIntegração Building Block

 

Componente

Um building block é exposto através de um contêiner sidecar associado a cada aplicação, atuando de forma isolada para fornecer uma funcionalidade específica, como gerenciamento de estado ou comunicação de eventos.

Por outro lado, um componente do Dapr é uma peça central que lida com a complexidade da comunicação com o serviço final. Em vez de ser um contêiner sidecar individual, ele serve como uma camada intermediária entre a aplicação e os serviços externos.

Portanto, enquanto os building blocks oferecem funcionalidades específicas diretamente para cada aplicação por meio de sidecars, os componentes do Dapr desempenham um papel mais central e abrangente.

A imagem abaixo representa claramente o papel do componente:

EXEMPLO COMPONENTESEXEMPLO COMPONENTES

 

Componente no Container App Environment

Os componentes usam um design modular, e podem ser compartilhados entre aplicações. Eles são executados como um processo separado, fora do escopo da aplicação.

Os componentes são configurados em nível de ambiente do Container App, mesmo que a plataforma se responsabilize por criar e configurar o Dapr, ainda precisaremos configurar os componentes.

Essa configuração é feita através de arquivos de definição yaml, cada tipo de componente conterá suas próprias propriedades.

No artigo, estamos demonstrando a integração do componente de pub/sub do Dapr com o serviço do Service Bus.

Existe uma diferença entre o arquivo utilizado para criação de componentes direto em um cluster Kubernetes e o arquivo utilizado para criação de componentes no ambiente do Container App.

Todos os componentes de código aberto do Dapr seguem o seguinte esquema básico:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: [COMPONENT-NAME]
  namespace: [COMPONENT-NAMESPACE]
spec:
  type: [COMPONENT-TYPE]
  version: v1
  initTimeout: [TIMEOUT-DURATION]
  ignoreErrors: [BOOLEAN]
  metadata:
    - name: [METADATA-NAME]
      value: [METADATA-VALUE]

 

No Azure Container Apps, o esquema acima foi ligeiramente simplificado para suportar os componentes do Dapr e remover campos desnecessários, incluindo apiVersion, kind e propriedades redundantes de metadados e especificações.

componentType: [COMPONENT-TYPE]
version: v1
initTimeout: [TIMEOUT-DURATION]
ignoreErrors: [BOOLEAN]
metadata:
  - name: [METADATA-NAME]
    value: [METADATA-VALUE]

 

No nosso exemplo, o arquivo ficou da seguinte forma:

componentType: pubsub.azure.servicebus.queues
version: v1
ignoreErrors: false
secrets:
  - name: connectionstring
    value: <VALOR DA CONNECTION-STRING>
metadata:
  - name: connectionString
    secretRef: connectionstring
scopes:
  - app-publisher
  - app-subscriber

A propriedade scopes define quais aplicações terão acesso ao componente, portanto é obrigatória.

 

Para criar o componente no ambiente do Container App, rode o comando:

az containerapp env dapr-component set --name ENVIRONMENT_NAME --resource-group RESOURCE_GROUP_NAME --dapr-component-name pubsub --yaml "./pubsub.yaml"

 

Se o comando funcionou corretamente, verá um resultado parecido com:

{
  "id": "/subscriptions/60e0dc1e-a3f7-44cb-8561-17980fce2670/resourceGroups/tdc-huebr/providers/Microsoft.App/managedEnvironments/tdc-huebr-env/daprComponents/pubsubgeneric",
  "name": "pubsub",
  "properties": {
    "componentType": "pubsub.azure.servicebus.queues",
    "ignoreErrors": false,
    "metadata": [
      {
        "name": "connectionString",
        "secretRef": "connectionstring"
      }
    ],
    "scopes": [
      "app-publisher",
      "app-subscriber"
    ],
    "secrets": [
      {
        "name": "connectionstring"
      }
    ],
    "version": "v1"
  },
  "resourceGroup": "RESOURCE_GROUP_NAME",
  "systemData": {
    "createdAt": "2023-09-13T13:08:13.2180324Z",
    "createdBy": "<SEU-EMAIL>",
    "createdByType": "User",
    "lastModifiedAt": "2023-09-13T13:08:13.2180324Z",
    "lastModifiedBy": "<SEU-EMAIL>",
    "lastModifiedByType": "User"
  },
  "type": "Microsoft.App/managedEnvironments/daprComponents"
}

 

Projeto PUB/SUB

Para demonstrar o uso do Dapr, criamos um projeto de exemplo que utiliza o componente de pub/sub do Dapr para publicar e consumir mensagens de um tópico do Service Bus. O projeto é composto por duas aplicações, uma que publica mensagens e outra que consome as mensagens.

O building block de publicação e inscrição do Dapr fornece um framework de API agnóstico à plataforma para enviar e receber mensagens. Seus serviços publicam mensagens em um tópico. Seus serviços se inscrevem em um tópico para consumir mensagens. O serviço faz chamadas à API de publicação/assinatura (pub/sub) no sidecar do Dapr. O sidecar então faz chamadas para o componente do Dapr criado previamente que encapsula a lógica para comunicação com o Service Bus.

 

Aplicação Publicadora

Utilizamos o Dapr .NET Sdk, para lidar com a complexidade da comunicação com o container sidecar do Dapr. O código abaixo mostra como configurar o DaprClient para publicar mensagens no tópico TOPICO:

using Dapr.Client;

var daprClient = new DaprClientBuilder().Build();
await daprClient.PublishEventAsync<int>("NOME_DO_COMPONENTE", "TOPICO", new object());

 

Esse trecho de código será responsável por encapsular a chamada HTTP ou gRPC para o sidecar do Dapr, essa chamada vai acontecer no Endpoint:

http://localhost:<dapr-port>/v1.0/publish/<pub-sub-name>/<topic>

 

Aplicação Consumidora

Essa aplicação vai funcionar de forma passiva, onde o sidecar do Dapr vai ficar escutando o tópico TOPICO e quando uma mensagem for publicada, o sidecar vai fazer uma chamada HTTP ou gRPC para a aplicação consumidora.

No início, o tempo de execução do Dapr chamará o aplicativo em um ponto de extremidade conhecido para identificar e criar as assinaturas necessárias:

http://localhost:<appPort>/dapr/subscribe

 

O sidecar vai utilizar da resposta desse endpoint como insumo para mapear quais são os tópicos que a aplicação consome.

A biblioteca Dapr.AspNetCore deixa esse processo trivial para o desenvolvedor, onde com poucas linhas de código, podemos configurar esse endpoint que o sidecar vai usar posteriormente.

O primeiro passo é instalar o pacote Dapr.AspNetCore:

dotnet add package Dapr.AspNetCore

 

Podemos utilizar a classe de atributo Topic sobre o método que vai receber as mensagens do tópico TOPICO, dessa forma:

    [Topic("NOME_DO_COMPONENTE","TOPICO")] 
    [HttpPost("/count")]
    public IActionResult Post(int value) {
        ...
        return Ok();
    }

 

Após isso, precisamos configurar a aplicação fazendo modificações na classe Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers().AddDapr();

var app = builder.Build();

app.UseCloudEvents();

app.MapControllers();

app.MapSubscribeHandler();

app.Run();

 

O segmento de código .AddDapr() registra os serviços necessários como o DaprClient. O segmento de código MapSubscribeHandler() registra o endpoint que o sidecar vai utilizar para identificar e criar as assinaturas necessárias, para atingir esse objetivo ele mapeia todos os endpoints decorados com o atributo Topic.

A fins de curiosidade, a lógica que o método MapSubscribeHandler() utiliza para identificar os endpoints decorados com o atributo Topic é a seguinte:

 private static IEndpointConventionBuilder CreateSubscribeEndPoint(IEndpointRouteBuilder endpoints, SubscribeOptions options = null)
        {
            if (endpoints is null)
            {
                throw new System.ArgumentNullException(nameof(endpoints));
            }

            return endpoints.MapGet("dapr/subscribe", async context =>
            {
                var logger = context.RequestServices.GetService<ILoggerFactory>().CreateLogger("DaprTopicSubscription");
                var dataSource = context.RequestServices.GetRequiredService<EndpointDataSource>();
                var subscriptions = dataSource.Endpoints
                    .OfType<RouteEndpoint>()
                    .Where(e => e.Metadata.GetOrderedMetadata<ITopicMetadata>().Any(t => t.Name != null))
                    .SelectMany(e =>
                    {
                        ...
                    })
                    .Distinct()
                    .GroupBy(e => new { e.PubsubName, e.Name })
                    .Select(e => e.OrderBy(e => e.Priority))
                    .Select(e =>
                    {
                       ...
                    })
                    .OrderBy(e => (e.PubsubName, e.Topic));

                await context.Response.WriteAsync(JsonSerializer.Serialize(subscriptions,
                    new JsonSerializerOptions
                    {
                        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
                    }));
            });
        }

 

Utilizando a classe EndpointDatasource a lib consegue identificar todos os endpoints decorados com o atributo Topic através do seguimento de código .Where(e => e.Metadata.GetOrderedMetadata<ITopicMetadata>().Any(t => t.Name != null)). No final ele vai retornar um json com todas as assinaturas que o sidecar vai criar, no endpoint dapr/subscribe.

Para saber mais veja o código fonte no repositório: Dapr.AspNetCore.

 

Teste local

Para testar seu projeto, você pode executar as aplicações localmente, para isso, você precisa instalar o Dapr CLI.

Após instalar o Dapr CLI, você precisa inicializar o Dapr localmente, para isso, rode o comando:

dapr init

 

Após isso, você precisa configurar o componente, para isso entre na pasta .dapr no local de instalação, depois na pasta components, crie o arquivo pubsub.yaml e cole o conteúdo abaixo:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
spec:
  type: pubsub.azure.servicebus.queues
  version: v1
  metadata:
  - name: connectionString
    value: "<CONNECTION_STRING>"

 

Após a criação do componente, basta rodar as aplicações, para isso, abra os terminais no diretório das aplicações e rode os comandos:

dapr run --app-id publisher -- dotnet run
dapr run --app-id subscriber -- dotnet run

 

Conclusão

Ao realizar a leitura deste artigo, você estará pronto para iniciar sua jornada de adoção do Dapr no Azure Container Apps. O Dapr é uma ferramenta poderosa para desacoplar aplicações, ele permite que você implante e gerencie contêineres sem se preocupar com a infraestrutura. Além disso, o Dapr é independente de plataforma, o que significa que você pode executar seus aplicativos localmente, em qualquer cluster Kubernetes, em máquinas virtuais ou físicas e em outros ambientes de hospedagem que o Dapr integra.

Co-Authors
Version history
Last update:
‎Oct 16 2023 10:31 AM
Updated by: