Implantar Orleans nos Aplicativos de Contêiner do Azure

Neste tutorial, você aprenderá a implantar um aplicativo de carrinho de compras do Orleans de exemplo nos Aplicativos de Contêiner do Azure. Este tutorial expande a funcionalidade do aplicativo de carrinho de compras de exemplo do Orleans, introduzido em Implantar o Orleans no Serviço de Aplicativo do Azure. O aplicativo de exemplo adiciona a autenticação B2C (entre empresas e consumidores) do Azure Active Directory (AAD) e é implantado nos Aplicativos de Contêiner do Azure.

Você aprenderá a implantar com o GitHub Actions, as CLIs do .NET e do Azure e o Azure Bicep. Além disso, você aprenderá a configurar a entrada HTTP do Aplicativo de Contêiner.

Neste tutorial, você aprenderá como:

  • Implantar um aplicativo do Orleans nos Aplicativos de Contêiner do Azure
  • Automatizar a implantação usando as GitHub Actions e o Azure Bicep
  • Configurar a entrada HTTP

Pré-requisitos

Executar o aplicativo localmente

Para executar o aplicativo localmente, bifurque o repositório de Amostras do Azure: carrinho de compras Orleans no Aplicativos de Contêiner do Azure e clone-o em sua máquina local. Depois de clonado, abra a solução em um IDE de sua escolha. Se você estiver usando o Visual Studio, clique com o botão direito do mouse no projeto Orleans.ShoppingCart.Silo e selecione Definir como Projeto de Inicialização e execute o aplicativo. Caso contrário, você poderá executar o aplicativo usando o seguinte comando da CLI do .NET:

dotnet run --project Silo\Orleans.ShoppingCart.Silo.csproj

Para obter mais informações, confira dotnet run. Com o aplicativo em execução, você recebe uma página de aterrissagem que discute a funcionalidade do aplicativo. No canto superior direito, você verá um botão de entrada. Você pode se inscrever em uma conta ou entrar se já tiver uma. Uma vez conectado, você pode navegar e testar seus recursos. Toda a funcionalidade do aplicativo ao ser executado localmente depende da persistência na memória, do clustering local e usa o pacote NuGet Falso para gerar produtos falsos. Interrompa o aplicativo selecionando a opção Parar Depuração no Visual Studio ou pressionando Ctrl+C na CLI do .NET.

AAD B2C

Embora ensinar os conceitos de autenticação esteja além do escopo deste tutorial, você pode aprender como Criar um locatário do Azure Active Directory B2C e Registrar um aplicativo Web para consumi-lo. No caso desse aplicativo de exemplo de carrinho de compras, a URL dos Aplicativos de Contêiner implantada resultante precisará ser registrada no locatário B2C. Para obter mais informações, confira Autenticação e autorização no Blazor do ASP.NET Core.

Importante

Depois que o Aplicativo de Contêiner for implantado, você precisará registrar a URL do aplicativo no locatário B2C. Na maioria dos cenários de produção, você só precisará registrar a URL do aplicativo uma vez, pois ela não deve ser alterada.

Para ajudar a visualizar como o aplicativo é isolado no ambiente dos Aplicativos de Contêiner do Azure, confira o seguinte diagrama:

Entrada HTTP dos Aplicativos de Contêiner do Azure.

No diagrama anterior, todo o tráfego de entrada para o aplicativo é canalizado por meio de uma entrada HTTP protegida. O ambiente dos Aplicativos de Contêiner do Azure contém uma instância de aplicativo, que contém um host ASP.NET Core, que expõe a funcionalidade do aplicativo Blazor Server e Orleans.

Implantar nos Aplicativos de Contêiner do Azure

Para implantar o aplicativo nos Aplicativos de Contêiner do Azure, o repositório usa GitHub Actions. Antes que essa implantação possa ocorrer, você precisará de alguns recursos do Azure e precisará configurar o repositório GitHub corretamente.

Antes de implantar o aplicativo, você precisa criar um Grupo de Recursos do Azure (ou usar um existente). Para criar um novo Grupo de Recursos do Azure, use um dos seguintes artigos:

Anote o nome do grupo de recursos escolhido, você precisará dele mais tarde para implantar o aplicativo.

Criar uma entidade de serviço

Para automatizar a implantação do aplicativo, você precisará criar uma entidade de serviço. Essa é uma conta Microsoft que tem permissão para gerenciar recursos do Azure em seu nome.

az ad sp create-for-rbac --sdk-auth --role Contributor \
  --name "<display-name>"  --scopes /subscriptions/<your-subscription-id>

As credenciais JSON criadas serão semelhantes às seguintes, mas com valores reais para seu cliente, assinatura e locatário:

{
  "clientId": "<your client id>",
  "clientSecret": "<your client secret>",
  "subscriptionId": "<your subscription id>",
  "tenantId": "<your tenant id>",
  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com/",
  "resourceManagerEndpointUrl": "https://brazilus.management.azure.com",
  "activeDirectoryGraphResourceId": "https://graph.windows.net/",
  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
  "galleryEndpointUrl": "https://gallery.azure.com",
  "managementEndpointUrl": "https://management.core.windows.net"
}

Copie a saída do comando na área de transferência e continue para a próxima etapa.

Criar um segredo do GitHub

O GitHub fornece um mecanismo para criar segredos criptografados. Os segredos que você cria estão disponíveis para uso em fluxos de trabalho das GitHub Actions. Você verá como o GitHub Actions pode ser usado para automatizar a implantação do aplicativo, em conjunto com o Azure Bicep. Bicep é uma DSL (linguagem específica de domínio) que usa uma sintaxe declarativa para implantar recursos do Azure. Para obter mais informações, confira O que é o Bicep? Usando a saída da etapa Criar uma entidade de serviço, você precisará criar o segredo AZURE_CREDENTIALS do GitHub com as credenciais formatadas em JSON.

No repositório do GitHub, selecione Configurações>Segredos>Criar um novo segredo. Insira o nome AZURE_CREDENTIALS e cole as credenciais JSON da etapa anterior no campo Valor.

Repositório do GitHub: Configuração > Segredos

Para obter mais informações, confira GitHub: segredos criptografados.

Preparar para a implantação do Azure

O aplicativo precisará ser empacotado para implantação. No projeto Orleans.ShoppingCart.Silos, definimos um elemento Target que é executado após a etapa Publish. Isso vai compactar o diretório de publicação em um arquivo silo.zip:

<Target Name="ZipPublishOutput" AfterTargets="Publish">
    <Delete Files="$(ProjectDir)\..\silo.zip" />
    <ZipDirectory SourceDirectory="$(PublishDir)" DestinationFile="$(ProjectDir)\..\silo.zip" />
</Target>

Há muitas maneiras de implantar um aplicativo .NET nos Aplicativos de Contêiner do Azure. Neste tutorial, você usará os GitHub Actions, o Azure Bicep e os CLIs do .NET e do Azure. Considere o arquivo ./github/workflows/deploy.yml na raiz do repositório do GitHub:

name: Deploy to Azure Container Apps

on:
  push:
    branches:
    - main

env:
  UNIQUE_APP_NAME: orleanscart
  SILO_IMAGE_NAME: orleanscart-silo
  AZURE_RESOURCE_GROUP_NAME: orleans-resourcegroup
  AZURE_RESOURCE_GROUP_LOCATION: eastus

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Setup .NET 6.0
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 6.0.x

    - name: .NET publish shopping cart app
      run: dotnet publish ./Silo/Orleans.ShoppingCart.Silo.csproj --configuration Release

    - name: Login to Azure
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS }}

    - name: Flex ACR Bicep
      run: |
        az deployment group create \
          --resource-group ${{ env.AZURE_RESOURCE_GROUP_NAME }} \
          --template-file '.github/workflows/flex/acr.bicep' \
          --parameters location=${{ env.AZURE_RESOURCE_GROUP_LOCATION }}

    - name: Get ACR Login Server
      run: |
        ACR_NAME=$(az deployment group show -g ${{ env.AZURE_RESOURCE_GROUP_NAME }} -n acr \
        --query properties.outputs.acrName.value | tr -d '"')
        echo "ACR_NAME=$ACR_NAME" >> $GITHUB_ENV
        ACR_LOGIN_SERVER=$(az deployment group show -g ${{ env.AZURE_RESOURCE_GROUP_NAME }} -n acr \
        --query properties.outputs.acrLoginServer.value | tr -d '"')
        echo "ACR_LOGIN_SERVER=$ACR_LOGIN_SERVER" >> $GITHUB_ENV

    - name: Prepare Docker buildx
      uses: docker/setup-buildx-action@v1

    - name: Login to ACR
      run: |
        access_token=$(az account get-access-token --query accessToken -o tsv)
        refresh_token=$(curl https://${{ env.ACR_LOGIN_SERVER }}/oauth2/exchange -v \
        -d "grant_type=access_token&service=${{ env.ACR_LOGIN_SERVER }}&access_token=$access_token" | jq -r .refresh_token)
        # The null GUID 0000... tells the container registry that this is an ACR refresh token during the login flow
        docker login -u 00000000-0000-0000-0000-000000000000 \
        --password-stdin ${{ env.ACR_LOGIN_SERVER }} <<< "$refresh_token"

    - name: Build and push Silo image to registry
      uses: docker/build-push-action@v2
      with:
        push: true
        tags: ${{ env.ACR_LOGIN_SERVER }}/${{ env.SILO_IMAGE_NAME }}:${{ github.sha }}
        file: Silo/Dockerfile

    - name: Flex ACA Bicep
      run: |
        az deployment group create \
          --resource-group ${{ env.AZURE_RESOURCE_GROUP_NAME }} \
          --template-file '.github/workflows/flex/main.bicep' \
          --parameters location=${{ env.AZURE_RESOURCE_GROUP_LOCATION }} \
            appName=${{ env.UNIQUE_APP_NAME }} \
            acrName=${{ env.ACR_NAME }} \
            repositoryImage=${{ env.ACR_LOGIN_SERVER }}/${{ env.SILO_IMAGE_NAME }}:${{ github.sha }} \
          --debug

    - name: Get Container App URL
      run: |
        ACA_URL=$(az deployment group show -g ${{ env.AZURE_RESOURCE_GROUP_NAME }} \
        -n main --query properties.outputs.acaUrl.value | tr -d '"')
        echo $ACA_URL

    - name: Logout of Azure
      run: az logout

O fluxo de trabalho anterior do GitHub será:

  • Publique o aplicativo de carrinho de compras como um arquivo zip, usando o comando dotnet publish.
  • Faça logon no Azure usando as credenciais da etapa Criar uma entidade de serviço.
  • Avalie o arquivo acr.bicep e inicie um grupo de implantação usando criar grupo de implantação de az.
  • Obtenha o servidor de logon do ACR (Registro de Contêiner do Azure) do grupo de implantação.
  • Faça logon no ACR usando o segredo dos repositórios AZURE_CREDENTIALS.
  • Compile e publique a imagem do silo no ACR.
  • Avalie o arquivo main.bicep e inicie um grupo de implantação usando az deployment group create.
  • Implantar o silo
  • Logoff do Azure.

O fluxo de trabalho é acionado por um push na ramificação principal. Para obter mais informações, confira GitHub Actions e .NET.

Dica

Se você encontrar problemas ao executar o fluxo de trabalho, talvez seja necessário verificar se a entidade de serviço tem todos os namespaces de provedor necessários registrados. Os seguintes namespaces de provedor são obrigatórios:

  • Microsoft.App
  • Microsoft.ContainerRegistry
  • Microsoft.Insights
  • Microsoft.OperationalInsights
  • Microsoft.Storage

Para mais informações, confira Resolver erros de registro do provedor de recursos.

O Azure impõe convenções e restrições de nomenclatura para recursos. Você precisa atualizar os valores de arquivo deploy.yml para o seguinte:

  • UNIQUE_APP_NAME
  • SILO_IMAGE_NAME
  • AZURE_RESOURCE_GROUP_NAME
  • AZURE_RESOURCE_GROUP_LOCATION

Defina esses valores como o nome do aplicativo exclusivo, o nome e o local do grupo de recursos do Azure.

Para saber mais, confira Regras de nomenclatura e restrições para recursos do Azure.

Explorar os modelos do Bicep

Quando o comando az deployment group create for executado, ele avaliará uma determinada referência de arquivo .bicep. Esse arquivo contém informações declarativas que detalham os recursos do Azure que você deseja implantar. Uma maneira de pensar nessa etapa é que ela provisiona todos os recursos para implantação.

Importante

Se você estiver usando o Visual Studio Code, a experiência de criação do bicep será melhorada ao usar a Extensão Bicep.

O primeiro arquivo Bicep avaliado é o arquivo acr.bicep. Esse arquivo contém os detalhes do recurso do servidor de logon do ACR (Registro de Contêiner do Azure):

param location string = resourceGroup().location

resource acr 'Microsoft.ContainerRegistry/registries@2021-09-01' = {
  name: toLower('${uniqueString(resourceGroup().id)}acr')
  location: location
  sku: {
    name: 'Basic'
  }
  properties: {
    adminUserEnabled: true
  }
}

output acrLoginServer string = acr.properties.loginServer
output acrName string = acr.name

Esse arquivo bicep gera o servidor de logon do ACR e o nome correspondente. O próximo arquivo Bicep encontrado contém mais do que apenas um único resource. Considere o arquivo main.bicep composto principalmente por definições de delegação module:

param appName string
param acrName string
param repositoryImage string
param location string = resourceGroup().location

resource acr 'Microsoft.ContainerRegistry/registries@2021-09-01' existing = {
  name: acrName
}

module env 'environment.bicep' = {
  name: 'containerAppEnvironment'
  params: {
    location: location
    operationalInsightsName: '${appName}-logs'
    appInsightsName: '${appName}-insights'
  }
}

var envVars = [
  {
    name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
    value: env.outputs.appInsightsInstrumentationKey
  }
  {
    name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
    value: env.outputs.appInsightsConnectionString
  }
  {
    name: 'ORLEANS_AZURE_STORAGE_CONNECTION_STRING'
    value: storageModule.outputs.connectionString
  }
  {
    name: 'ASPNETCORE_FORWARDEDHEADERS_ENABLED'
    value: 'true'
  }
]

module storageModule 'storage.bicep' = {
  name: 'orleansStorageModule'
  params: {
    name: '${appName}storage'
    location: location
  }
}

module siloModule 'container-app.bicep' = {
  name: 'orleansSiloModule'
  params: {
    appName: appName
    location: location
    containerAppEnvironmentId: env.outputs.id
    repositoryImage: repositoryImage
    registry: acr.properties.loginServer
    registryPassword: acr.listCredentials().passwords[0].value
    registryUsername: acr.listCredentials().username
    envVars: envVars
  }
}

output acaUrl string = siloModule.outputs.acaUrl

O arquivo Bicep anterior:

  • Faz referência a um recurso ACR existing, para obter mais informações, confira Azure Bicep: recursos existentes.
  • Define um module env delegado para o arquivo de definição environment.bicep.
  • Define um module storageModule delegado para o arquivo de definição storage.bicep.
  • Declara vários envVars compartilhados que são usados pelo módulo de silo.
  • Define um module siloModule delegado para o arquivo de definição container-app.bicep.
  • Gera a URL da ACA (isso pode ser usado potencialmente para atualizar um URI de redirecionamento de registro de aplicativo do AAD B2C existente).

O main.bicep é delegado para vários outros arquivos Bicep. O primeiro é o arquivo environment.bicep:

param operationalInsightsName string
param appInsightsName string
param location string

resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
  name: appInsightsName
  location: location
  kind: 'web'
  properties: {
    Application_Type: 'web'
    WorkspaceResourceId: logs.id
  }
}

resource logs 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
  name: operationalInsightsName
  location: location
  properties: {
    retentionInDays: 30
    features: {
      searchVersion: 1
    }
    sku: {
      name: 'PerGB2018'
    }
  }
}

resource env 'Microsoft.App/managedEnvironments@2022-03-01' = {
  name: '${resourceGroup().name}env'
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'log-analytics'
      logAnalyticsConfiguration: {
        customerId: logs.properties.customerId
        sharedKey: logs.listKeys().primarySharedKey
      }
    }
  }
}

output id string = env.id
output appInsightsInstrumentationKey string = appInsights.properties.InstrumentationKey
output appInsightsConnectionString string = appInsights.properties.ConnectionString

O arquivo bicep define os recursos do Azure Log Analytics e do Application Insights. O recurso appInsights é um tipo web e o recurso logs é um tipo PerGB2018. O recurso appInsights e o recurso logs são provisionados no local do grupo de recursos. O recurso appInsights está vinculado ao recurso logs por meio da propriedade WorkspaceResourceId. Há três saídas definidas neste bicep, usadas posteriormente pelos Aplicativos de Contêiner module. Agora vamos examinar o arquivo storage.bicep:

param name string
param location string

resource storage 'Microsoft.Storage/storageAccounts@2021-08-01' = {
  name: name
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
}

var key = listKeys(storage.name, storage.apiVersion).keys[0].value
var protocol = 'DefaultEndpointsProtocol=https'
var accountBits = 'AccountName=${storage.name};AccountKey=${key}'
var endpointSuffix = 'EndpointSuffix=${environment().suffixes.storage}'

output connectionString string = '${protocol};${accountBits};${endpointSuffix}'

O arquivo bicep anterior define o seguinte:

  • Dois parâmetros para o nome do grupo de recursos e o nome do aplicativo.
  • A definição de resource storage da conta de armazenamento.
  • Um único output que constrói a cadeia de conexão para a conta de armazenamento.

O último arquivo Bicep é o arquivo container-app.bicep:

param appName string
param location string
param containerAppEnvironmentId string
param repositoryImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
param envVars array = []
param registry string
param registryUsername string
@secure()
param registryPassword string

resource containerApp 'Microsoft.App/containerApps@2022-03-01' = {
  name: appName
  location: location
  properties: {
    managedEnvironmentId: containerAppEnvironmentId
    configuration: {
      activeRevisionsMode: 'multiple'
      secrets: [
        {
          name: 'container-registry-password'
          value: registryPassword
        }
      ]
      registries: [
        {
          server: registry
          username: registryUsername
          passwordSecretRef: 'container-registry-password'
        }
      ]
      ingress: {
        external: true
        targetPort: 80
      }
    }
    template: {
      revisionSuffix: uniqueString(repositoryImage, appName)
      containers: [
        {
          image: repositoryImage
          name: appName
          env: envVars
        }
      ]
      scale: {
        minReplicas: 1
        maxReplicas: 1
      }
    }
  }
}

output acaUrl string = containerApp.properties.configuration.ingress.fqdn

A extensão do Visual Studio Code mencionada anteriormente para o Bicep inclui um visualizador. Todos esses arquivos bicep são visualizados da seguinte maneira:

Orleans: renderização do visualizador de provisionamento do Bicep do aplicativo de amostra do carrinho de compras.

Resumo

À medida que você atualiza o código-fonte e push é alterado para main na ramificação do repositório, o fluxo de trabalho deploy.yml será executado. Ele provisiona os recursos do Azure definidos nos arquivos Bicep e implanta o aplicativo. As revisões são registradas automaticamente em seu Registro de Contêiner do Azure.

Além do visualizador da extensão bicep, a página do grupo de recursos portal do Azure seria semelhante ao exemplo a seguir após o provisionamento e a implantação do aplicativo:

Portal do Azure: recursos de aplicativo de exemplo de carrinho de compras do Orleans para os Aplicativos de Contêiner do Azure.

Confira também