部署包含初始化容器的 Azure 容器实例
有时,你需要在应用程序启动之前执行某些任务。 例如,可能需要将某些服务配置为接受来自容器的入站连接,或将 Azure 密钥保管库中的机密注入卷。 可以使用初始化 (init) 容器实现这些先决条件验证或初始化任务。
Init 容器是挎斗模式的示例,但 init 容器在容器组中任何其他容器启动之前运行。 只有在任何定义的初始化容器成功完成其任务后,容器组中的应用程序容器才会启动。 Azure 容器实例 init 容器的概念与 Kubernetes init 容器相同。
你的客户希望通过使用完全限定的域名 (FQDN) 而不是 IP 地址来访问其 API。 他们还希望确保 FQDN 在重新创建容器组时不会更改。 在本单元中,你将使用 init 容器更新域名系统 (DNS),以便客户始终可以使用域名而不是 IP 地址来访问 API。
下图显示了容器实例 init 容器的拓扑:
Init 容器会检索分配给应用程序容器的 IP 地址,并更新 API 客户端用于访问容器的 DNS 条目。 Init 容器和应用程序容器共享同一网络堆栈,因此对 Init 容器可见的 IP 地址与应用程序容器将使用的 IP 地址相同。
创建初始化脚本和 DNS 区域
首先,创建 Init 容器用于检索应用程序 IP 地址和更新 DNS 的 Azure 服务主体,因为你不能在 init 容器中使用托管标识。 在此示例中,为简单起见,你将分配服务主体参与者角色。 在生产环境中,你可能希望更严格。
在 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')为应用程序客户端创建 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 数据库。
可通过多种方法将脚本注入 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 定义的其余部分没有改变。
通过运行以下代码创建 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验证已生成的 YAML 文件,以检查是否正常替换了变量。
# Check YAML file more $aci_yaml_file创建容器实例。 由于 init 容器在应用程序和 NGINX 容器启动之前运行,因此创建这些实例需要更长的时间。
# Deploy ACI az container create -g $rg --file $aci_yaml_file验证 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使用 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"可以检查每个容器的单个容器实例日志。 例如,运行以下代码以访问 init 容器日志:
# Init container logs az container logs -n $aci_name -g $rg --container-name azcli要避免持续收费,请删除 Azure 资源组以清理为此单元和模块创建的所有资源。
# Clean up module az group delete -n $rg -y --no-wait