Implantar instâncias de contêiner do Azure com um contêiner de inicialização

Concluído

Às vezes, você precisa fazer certas tarefas antes que um aplicativo seja iniciado. Por exemplo, talvez seja necessário configurar determinados serviços para aceitar conectividade de entrada do contêiner ou injetar segredos do Cofre de Chaves do Azure em um volume. Você pode implementar essas tarefas de validação ou inicialização de pré-requisitos com contêineres de inicialização (init).

Os contêineres de inicialização são um exemplo do padrão de sidecar, mas os contêineres de inicialização são executados antes do início de qualquer outro contêiner no grupo de contêineres. Os contêineres de aplicativos no grupo de contêineres são iniciados somente depois que qualquer contêiner de inicialização definido conclui com êxito suas tarefas. Os contêineres de inicialização das Instâncias de Contêiner do Azure são o mesmo conceito dos contêineres de inicialização do Kubernetes.

Seu cliente deseja acessar sua API usando um FQDN (nome de domínio totalmente qualificado) em vez de um endereço IP. Eles também querem garantir que o FQDN não seja alterado se recriarem o grupo de contêineres. Nesta unidade, você usa um contêiner init para atualizar o DNS (Sistema de Nomes de Domínio) para que os clientes sempre possam acessar a API usando o nome de domínio em vez de um endereço IP.

O diagrama a seguir mostra a topologia do contêiner de inicialização de Instâncias de Contêiner:

Diagram that shows the topology of an ACI Init container.

O contêiner init recupera o endereço IP alocado para o contêiner do aplicativo e atualiza a entrada DNS que os clientes de API usam para alcançar o contêiner. O contêiner init e o contêiner do aplicativo compartilham a mesma pilha de rede, portanto, o endereço IP visível para o contêiner init é o mesmo que o contêiner do aplicativo usa.

Criar o script de inicialização e a zona DNS

Primeiro, você cria uma entidade de serviço do Azure que o contêiner init usa para recuperar o endereço IP do aplicativo e atualizar o DNS, porque você não pode usar identidades gerenciadas em um contêiner init. Neste exemplo, você atribui a função de Colaborador da entidade de serviço para simplificar. Em ambientes de produção, talvez você queira ser mais restritivo.

  1. No Azure Cloud Shell no portal do Azure, execute o seguinte código para criar a entidade de serviço:

    # Create service principal for authentication
    scope=$(az group show -n $rg --query id -o tsv)
    new_sp=$(az ad sp create-for-rbac --scopes $scope --role Contributor --name acilab -o json)
    sp_appid=$(echo $new_sp | jq -r '.appId') && echo $sp_appid
    sp_tenant=$(echo $new_sp | jq -r '.tenant') && echo $sp_tenant
    sp_password=$(echo $new_sp | jq -r '.password')
    
  2. Crie a zona DNS privada do Azure para que os clientes do aplicativo acessem a instância do contêiner e associe a zona à rede virtual.

    # Create Azure DNS private zone and records
    dns_zone_name=contoso.com
    az network private-dns zone create -n $dns_zone_name -g $rg 
    az network private-dns link vnet create -g $rg -z $dns_zone_name -n contoso --virtual-network $vnet_name --registration-enabled false
    

    Nota

    Essa zona DNS é diferente da zona DNS criada na unidade anterior, que a instância de contêiner usou para acessar o Banco de Dados SQL do Azure.

  3. Há muitas maneiras de injetar um script em um contêiner init. Nesse caso, você usa um compartilhamento de Arquivos do Azure para armazenar o script. Execute o código a seguir para criar o script de inicialização, criar um compartilhamento de Arquivos do Azure e carregar o script no compartilhamento.

    # Create script for init container
    storage_account_name="acilab$RANDOM"
    az storage account create -n $storage_account_name -g $rg --sku Standard_LRS --kind StorageV2
    storage_account_key=$(az storage account keys list --account-name $storage_account_name -g $rg --query '[0].value' -o tsv)
    az storage share create --account-name $storage_account_name --account-key $storage_account_key --name initscript
    init_script_filename=init.sh
    init_script_path=/tmp/
    cat <<EOF > ${init_script_path}${init_script_filename}
    echo "Logging into Azure..."
    az login --service-principal -u \$SP_APPID -p \$SP_PASSWORD --tenant \$SP_TENANT
    echo "Finding out IP address..."
    # my_private_ip=\$(az container show -n \$ACI_NAME -g \$RG --query 'ipAddress.ip' -o tsv) && echo \$my_private_ip
    my_private_ip=\$(ifconfig eth0 | grep 'inet addr' | cut -d: -f2 | cut -d' ' -f 1) && echo \$my_private_ip
    echo "Deleting previous record if there was one…"
    az network private-dns record-set a delete -n \$HOSTNAME -z \$DNS_ZONE_NAME -g \$RG -y
    echo "Creating DNS record..."
    az network private-dns record-set a create -n \$HOSTNAME -z \$DNS_ZONE_NAME -g \$RG
    az network private-dns record-set a add-record --record-set-name \$HOSTNAME -z \$DNS_ZONE_NAME -g \$RG -a \$my_private_ip
    EOF
    az storage file upload --account-name $storage_account_name --account-key $storage_account_key -s initscript --source ${init_script_path}${init_script_filename}
    

    O script de inicialização usa a CLI do Azure para executar os comandos que descobrem o endereço IP da instância de contêiner e criam um registro A na zona DNS privada. O script autentica usando o ID do aplicativo principal de serviço e o segredo que ele espera encontrar como variáveis de ambiente.

Implantar o grupo de contêineres com o contêiner init

Agora você pode criar um arquivo YAML que se baseia nos arquivos usados em unidades anteriores. Observe esses itens no seguinte código YAML:

  • Agora há uma initContainers seção.
  • O initContainer usa a imagem, que já tem a microsoft/azure-cli:latest CLI do Azure instalada.
  • O contêiner init executa um script armazenado na pasta /mnt/init/, que é montada a partir de um volume de Arquivos do Azure.
  • O grupo de recursos, o nome da instância do contêiner e as credenciais da entidade de serviço são passados como variáveis de ambiente.
  • O segredo da entidade de serviço é passado como uma variável de ambiente seguro.
  • O resto das definições de YAML do recipiente permanecem inalteradas em relação às unidades anteriores.
  1. Crie o arquivo YAML executando o seguinte código:

    # Create YAML
    aci_subnet_id=$(az network vnet subnet show -n $aci_subnet_name --vnet-name $vnet_name -g $rg --query id -o tsv)
    aci_yaml_file=/tmp/acilab.yaml
    cat <<EOF > $aci_yaml_file
    apiVersion: '2023-05-01'
    location: $location
    name: $aci_name
    properties:
      subnetIds:
      - id: $aci_subnet_id
      initContainers:
      - name: azcli
        properties:
          image: mcr.microsoft.com/azure-cli:latest
          command:
          - "/bin/sh"
          - "-c"
          - "/mnt/init/$init_script_filename"
          environmentVariables:
          - name: RG
            value: $rg
          - name: SP_APPID
            value: $sp_appid
          - name: SP_PASSWORD
            secureValue: $sp_password
          - name: SP_TENANT
            value: $sp_tenant
          - name: DNS_ZONE_NAME
            value: $dns_zone_name
          - name: HOSTNAME
            value: $aci_name
          - name: ACI_NAME
            value: $aci_name
          volumeMounts:
          - name: initscript
            mountPath: /mnt/init/
      containers:
      - name: nginx
        properties:
          image: nginx
          ports:
          - port: 443
            protocol: TCP
          resources:
            requests:
              cpu: 1.0
              memoryInGB: 1.5
          volumeMounts:
          - name: nginx-config
            mountPath: /etc/nginx
      - name: sqlapi
        properties:
          image: erjosito/yadaapi:1.0
          environmentVariables:
          - name: SQL_SERVER_FQDN
            value: $sql_server_fqdn
          - name: SQL_SERVER_USERNAME
            value: $sql_username
          - name: SQL_SERVER_PASSWORD
            secureValue: $sql_password
          ports:
          - port: 8080
            protocol: TCP
          resources:
            requests:
              cpu: 1.0
              memoryInGB: 1
          volumeMounts:
      volumes:
      - secret:
          ssl.crt: "$ssl_crt"
          ssl.key: "$ssl_key"
          nginx.conf: "$nginx_conf"
        name: nginx-config
      - name: initscript
        azureFile:
          readOnly: false
          shareName: initscript
          storageAccountName: $storage_account_name
          storageAccountKey: $storage_account_key
      ipAddress:
        ports:
        - port: 443
          protocol: TCP
        type: Private
      osType: Linux
    tags: null
    type: Microsoft.ContainerInstance/containerGroups
    EOF
    
  2. Verifique o arquivo YAML gerado para verificar se a substituição de variáveis funcionou corretamente.

    # Check YAML file
    more $aci_yaml_file
    
  3. Crie as instâncias de contêiner. Essas instâncias demoram um pouco mais para serem criadas porque o contêiner init é executado antes do aplicativo e dos contêineres NGINX serem iniciados.

    # Deploy ACI
    az container create -g $rg --file $aci_yaml_file
    
  4. Verifique se o contêiner init criou um registro A na zona DNS Privado do Azure:

    # Verify A records in the Azure Private DNS zone
    az network private-dns record-set a list -z $dns_zone_name -g $rg --query '[].{RecordName:name,IPv4Address:aRecords[0].ipv4Address}' -o table
    
  5. Use os pontos de extremidade da API SQL para testar se o contêiner está acessível. Você usa o nome de domínio para acessar o contêiner, não seu endereço IP.

    # Test
    aci_fqdn=${aci_name}.${dns_zone_name} && echo $aci_fqdn
    ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $vm_pip "nslookup $aci_fqdn"
    ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $vm_pip "curl -ks https://$aci_fqdn/api/healthcheck"
    ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $vm_pip "curl -ks https://$aci_fqdn/api/sqlversion"
    ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no $vm_pip "curl -ks https://$aci_fqdn/api/sqlsrcip"
    
  6. Você pode inspecionar os logs de instância de contêiner individuais para cada contêiner. Por exemplo, execute o seguinte código para acessar os logs de contêiner init:

    # Init container logs
    az container logs -n $aci_name -g $rg --container-name azcli
    
  7. Para evitar cobranças contínuas, exclua o grupo de recursos do Azure para limpar todos os recursos criados para esta unidade e módulo.

    # Clean up module
    az group delete -n $rg -y --no-wait