Azure Container Apps 多網域部署指南

如何在同一個 Azure Container Apps Environment(CAE)中,
透過 Application Gateway(AGW)將不同網域路由到不同的 Container App
達到單一基礎設施服務多個應用程式的效果。


架構圖

                        ┌─────────────────────────────────────────────┐
外部使用者                │  Virtual Network (vnet-ag-example-001)        │
  │                     │  ┌──────────────────────────────────────┐   │
  │ HTTPS               │  │  Application Gateway (agw-example)   │   │
  ▼                     │  │                                      │   │
[app-a.example.com]─────┼──►  Listener: app-a.example.com        │   │
[app-b.example.com]─────┼──►  Listener: app-b.example.com        │   │
                        │  └───────────┬──────────────┬───────────┘   │
                        └─────────────┼──────────────┼───────────────┘
                                      │ VNet Peering │
                        ┌─────────────┼──────────────┼───────────────┐
                        │  Virtual Network (vnet-example-001)         │
                        │             │              │                │
                        │  ┌──────────▼──────────────▼───────────┐   │
                        │  │  Container Apps Environment (CAE)   │   │
                        │  │  (Internal, Consumption)            │   │
                        │  │                                      │   │
                        │  │  ca-app-a  (port 80,  nginx)        │   │
                        │  │  ca-app-b  (port 8080, api)         │   │
                        │  └─────────────────────────────────────┘   │
                        │                                             │
                        └─────────────────────────────────────────────┘

前置條件

資源說明
Container Apps Environment建立為 Internal(Workload Profile: Consumption)
VNet for CAE例如 vnet-example-001,CAE 使用其中一個子網路
VNet for AGW例如 vnet-ag-example-001,AGW 使用其中一個子網路
VNet Peering上述兩個 VNet 互相 Peered
Private DNS Zone由 CAE 自動建立,需手動連結至 AGW 所在的 VNet
SSL 憑證每個自訂網域需有對應的憑證(PFX 格式上傳至 AGW)

一、Private DNS Zone 設定

CAE 建立後,Azure 會自動在同一資源群組建立一個 Private DNS Zone,
格式為:<隨機字串>.<region>.azurecontainerapps.io
例如:abcd1234efgh.japaneast.azurecontainerapps.io

此 DNS Zone 需與 AGW 所在的 VNet 連結,AGW 才能解析 CA 的 FQDN。

az network private-dns link vnet create \
  --resource-group <cae-resource-group> \
  --zone-name "abcd1234efgh.japaneast.azurecontainerapps.io" \
  --name "link-to-agw-vnet" \
  --virtual-network <agw-vnet-id> \
  --registration-enabled false

Wildcard DNS 記錄

Private DNS Zone 中有一筆 Wildcard A 記錄:

名稱類型
*A<CAE 內部 Load Balancer IP>

此 Wildcard 只能匹配單層子網域(如 ca-app-a.abcd1234efgh...),
無法匹配 ca-app-a.internal.abcd1234efgh...(雙層)。

⚠️ 注意:若使用 --ingress internal 建立 CA,其 FQDN 會包含 .internal.
不會被 Wildcard 記錄匹配,AGW 將無法解析。
請一律使用 --ingress external(在 Internal CAE 內,external 仍為 VNet 私有,並非真正對外)。


二、建立 Container App

每個應用程式建立時指定 --ingress external,對應的 FQDN 格式為:
<app-name>.<env-domain>

# 建立 App A(前端,nginx on port 80)
az containerapp create \
  --name ca-app-a \
  --resource-group <resource-group> \
  --environment <cae-name> \
  --image <registry>.azurecr.io/app-a:latest \
  --target-port 80 \
  --ingress external \
  --registry-server <registry>.azurecr.io

# 建立 App B(後端 API,on port 8080)
az containerapp create \
  --name ca-app-b \
  --resource-group <resource-group> \
  --environment <cae-name> \
  --image <registry>.azurecr.io/app-b:latest \
  --target-port 8080 \
  --ingress external \
  --registry-server <registry>.azurecr.io

建立後,各 CA 的 FQDN 例如:

  • ca-app-a.abcd1234efgh.japaneast.azurecontainerapps.io
  • ca-app-b.abcd1234efgh.japaneast.azurecontainerapps.io

允許 HTTP(不強制重新導向 HTTPS)

ACA 預設會將 HTTP 請求 301 重新導向至 HTTPS。
若 AGW 以 HTTP 協定連至 CA(通常如此,因為 TLS 已在 AGW 終止),
需對每個 CA 啟用 --allow-insecure

az containerapp ingress update \
  --name ca-app-a \
  --resource-group <resource-group> \
  --allow-insecure

三、Application Gateway 設定

同一個 AGW 可以透過多組 Listener + Rule + Backend Pool 路由不同網域。

3-1 Backend Pool

每個應用程式建立一個 Backend Pool,目標為 CA 的 FQDN:

Pool 名稱FQDN
pool-app-aca-app-a.abcd1234efgh.japaneast.azurecontainerapps.io
pool-app-bca-app-b.abcd1234efgh.japaneast.azurecontainerapps.io
az network application-gateway address-pool create \
  --gateway-name <agw-name> \
  --resource-group <agw-resource-group> \
  --name pool-app-a \
  --servers ca-app-a.abcd1234efgh.japaneast.azurecontainerapps.io

3-2 Backend HTTP Settings

指定協定為 HTTP(因為 TLS 在 AGW 終止)、Port 80,
固定 Host Header 為 CA 的 FQDN,使 ACA Ingress 能正確路由:

az network application-gateway http-settings create \
  --gateway-name <agw-name> \
  --resource-group <agw-resource-group> \
  --name set-app-a \
  --protocol Http \
  --port 80 \
  --host-name "ca-app-a.abcd1234efgh.japaneast.azurecontainerapps.io"

--host-name 設定會讓 AGW 傳送正確的 Host Header 給 ACA,
若不設定,ACA Ingress 收到的 Host 可能是原始網域(如 app-a.example.com),
導致路由失敗(HTTP 404)。

3-3 HTTPS Listener

每個自訂網域需要一個 Listener:

# 先上傳 SSL 憑證
az network application-gateway ssl-cert create \
  --gateway-name <agw-name> \
  --resource-group <agw-resource-group> \
  --name cert-app-a \
  --cert-file app-a.pfx \
  --cert-password <password>

# 建立 Listener
az network application-gateway http-listener create \
  --gateway-name <agw-name> \
  --resource-group <agw-resource-group> \
  --name lsn-app-a-https \
  --frontend-port <https-port-name> \
  --frontend-ip <frontend-ip-config-name> \
  --host-name "app-a.example.com" \
  --ssl-cert cert-app-a

3-4 Routing Rule

將 Listener 連接至 Backend Pool + HTTP Settings:

az network application-gateway rule create \
  --gateway-name <agw-name> \
  --resource-group <agw-resource-group> \
  --name rule-app-a \
  --http-listener lsn-app-a-https \
  --address-pool pool-app-a \
  --http-settings set-app-a \
  --rule-type Basic \
  --priority 200

重複 3-1 至 3-4 步驟,為 app-b.example.com 建立對應的資源。


四、新增應用程式到現有環境

只需重複以下步驟,無需修改 DNS Zone 或 CAE 設定

  1. 建立 Container App(--ingress external
  2. 啟用 --allow-insecure
  3. AGW 新增 Backend Pool(目標為新 CA 的 FQDN)
  4. AGW 新增 HTTP Settings(Host Header 設為新 CA 的 FQDN)
  5. AGW 新增 Listener(綁定新網域的 SSL 憑證)
  6. AGW 新增 Routing Rule(連接上述三者)

五、CA 間互相呼叫

在同一個 Internal CAE 中,Container App 可透過 FQDN 直接呼叫彼此,
不需經過 AGW:

http://ca-app-b.abcd1234efgh.japaneast.azurecontainerapps.io/api/endpoint

例如前端 nginx 的 proxy_pass 設定:

location /api/ {
    proxy_pass http://ca-app-b.abcd1234efgh.japaneast.azurecontainerapps.io/api/;
}

建議將後端 FQDN 透過環境變數注入(例如 BACKEND_URL),
搭配 nginx 的 envsubst 機制於容器啟動時替換:

# nginx.conf.template
location /api/ {
    proxy_pass ${BACKEND_URL}/api/;
}
# Dockerfile:放在 /etc/nginx/templates/ 會在啟動時自動替換
COPY .deploy/nginx.conf.template /etc/nginx/templates/default.conf.template

六、常見問題

AGW Backend Health 顯示 Unknown 或 502

  1. DNS 無法解析 CA FQDN:確認 Private DNS Zone 已連結至 AGW 所在的 VNet。
  2. 使用了 .internal. FQDN:CA 建立時使用了 --ingress internal,改為 --ingress external
  3. HTTP 重新導向:CA 收到 HTTP 請求回傳 301,對該 CA 執行 az containerapp ingress update --allow-insecure
  4. Host Header 不符:AGW 的 HTTP Settings 未設定 --host-name,導致 ACA Ingress 無法識別目標應用程式。

CA 啟動失敗:open .: no such file or directory

應用程式嘗試讀取相對路徑的檔案(如資料庫 Migration 檔),
但工作目錄與預期不符。
解法:透過環境變數指定絕對路徑,例如:

az containerapp update \
  --name ca-app-b \
  --resource-group <resource-group> \
  --set-env-vars "MIGRATION_SOURCE=file:///app/migrations"

CA 中的環境變數包含 & 字元

在 Windows 環境下使用 az CLI(.cmd 包裝器)時,& 會被 cmd.exe 解析為命令分隔符,
導致參數被截斷。解法:改用 az rest --method PATCH 搭配 JSON 檔案傳遞:

{
  "properties": {
    "template": {
      "containers": [{
        "name": "ca-app-b",
        "env": [
          { "name": "DATABASE_URL", "value": "sqlserver://host?db=mydb&param=value" }
        ]
      }]
    }
  }
}
az rest --method PATCH \
  --url "https://management.azure.com/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.App/containerApps/<name>?api-version=2024-03-01" \
  --body @patch.json

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *