部署包含初始化容器的 Azure 容器实例

已完成

有时,你需要在应用程序启动之前执行某些任务。 例如,可能需要将某些服务配置为接受来自容器的入站连接,或将 Azure 密钥保管库中的机密注入卷。 可以使用初始化 (init) 容器实现这些先决条件验证或初始化任务。

Init 容器是挎斗模式的示例,但 init 容器在容器组中任何其他容器启动之前运行。 只有在任何定义的初始化容器成功完成其任务后,容器组中的应用程序容器才会启动。 Azure 容器实例 init 容器的概念与 Kubernetes init 容器相同。

你的客户希望通过使用完全限定的域名 (FQDN) 而不是 IP 地址来访问其 API。 他们还希望确保 FQDN 在重新创建容器组时不会更改。 在本单元中,你将使用 init 容器更新域名系统 (DNS),以便客户始终可以使用域名而不是 IP 地址来访问 API。

下图显示了容器实例 init 容器的拓扑:

展示了 ACI Init 容器拓扑的图。

Init 容器会检索分配给应用程序容器的 IP 地址,并更新 API 客户端用于访问容器的 DNS 条目。 Init 容器和应用程序容器共享同一网络堆栈,因此对 Init 容器可见的 IP 地址与应用程序容器将使用的 IP 地址相同。

创建初始化脚本和 DNS 区域

首先,创建 Init 容器用于检索应用程序 IP 地址和更新 DNS 的 Azure 服务主体,因为你不能在 init 容器中使用托管标识。 在此示例中,为简单起见,你将分配服务主体参与者角色。 在生产环境中,你可能希望更严格。

  1. 在 Azure 门户中的 Azure Cloud Shell 中,运行以下代码来创建服务主体:

    # 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. 为应用程序客户端创建 Azure 专用 DNS 区域以访问容器实例,并将该区域关联到虚拟网络。

    # 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
    

    注意

    此 DNS 区域不同于在上一单元中创建的 DNS 区域,该区域由容器实例用于访问 Azure SQL 数据库。

  3. 可通过多种方法将脚本注入 Init 容器。 在本例中,请使用 Azure 文件共享来存储脚本。 运行以下代码以创建初始化脚本、创建 Azure 文件共享并将脚本上传到共享中。

    # 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}
    

    初始化脚本使用 Azure CLI 来运行命令,以查找容器实例的 IP 地址,并在专用 DNS 区域中创建一条 A 记录。 该脚本将使用应作为环境变量的服务主体应用程序 ID 和机密进行身份验证。

部署包含 Init 容器的容器组

现在,可以基于之前单元中使用的文件创建一个 YAML 文件。 请注意以下 YAML 代码中的以下项:

  • 现在有一个 initContainers 部分。
  • initContainer 使用已安装 Azure CLI 的 microsoft/azure-cli:latest 映像。
  • Init 容器会运行存储在 /mnt/init/ 文件夹中的脚本,它是从 Azure 文件存储卷装载的。
  • 资源组、容器实例名称和服务主体凭据作为环境变量传递。
  • 服务主体机密作为安全环境变量传递。
  • 与上一个单元相比,容器 YAML 定义的其余部分没有改变。
  1. 通过运行以下代码创建 YAML 文件:

    # 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. 验证已生成的 YAML 文件,以检查是否正常替换了变量。

    # Check YAML file
    more $aci_yaml_file
    
  3. 创建容器实例。 由于 init 容器在应用程序和 NGINX 容器启动之前运行,因此创建这些实例需要更长的时间。

    # Deploy ACI
    az container create -g $rg --file $aci_yaml_file
    
  4. 验证 init 容器是否在 Azure 专用 DNS 区域中创建了 A 记录:

    # 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. 使用 SQL API 终结点来测试容器是否可以访问。 你将使用域名访问容器,而不是其 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. 可以检查每个容器的单个容器实例日志。 例如,运行以下代码以访问 init 容器日志:

    # Init container logs
    az container logs -n $aci_name -g $rg --container-name azcli
    
  7. 要避免持续收费,请删除 Azure 资源组以清理为此单元和模块创建的所有资源。

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