
Demo 資料說明
這是一份可以單獨閱讀的教學用範例。所有專案名稱、公司名稱、GitHub repository、Azure 資源名稱、網域、帳號與 tenant 名稱都是虛構資料,不能直接拿來連線或部署。請依照實際環境替換成自己的設定。
這份文件要解決什麼問題
Example Company 的開發團隊希望使用 GitHub Actions 做 Azure CI/CD,流程如下:
- GitHub Actions 將程式編譯成 container image。
- 將 image push 到 Azure Container Registry(ACR)。
- 更新 Azure Container App,讓 App 使用新的 image。
- Backend 啟動後,透過 Microsoft Entra Managed Identity 連線到 Azure SQL Database。
目前遇到的問題通常會長這樣:
- GitHub Actions 可以登入 Azure,但 Container App 啟動時無法從 ACR 拉 image。
- Container App 的部署指令顯示成功,但新的 revision 變成
ActivationFailed。 - Backend image 可以啟動,卻因為沒有 SQL contained user 或 database role 而無法登入資料庫。
- App 位於沒有 VNet 路徑的 Container Apps Environment,因此連不到 SQL Private Endpoint。
- Frontend 還在使用舊的 Harbor image 或舊的 Backend hostname。
- 舊的部署系統與 Azure GitHub Actions 同時執行,造成兩邊互相覆蓋部署結果。
這些現象看起來都像「權限問題」,但實際上分屬四個不同層次:
- GitHub Actions 的 CI 身分能不能登入 Azure、push image、更新 App。
- Container App 執行時的 Managed Identity 能不能從 ACR pull image。
- Backend 的 Managed Identity 能不能登入 SQL Database。
- Container Apps Environment 是否有通往 SQL Private Endpoint 的網路路徑。
Demo 情境與資源
以下名稱全部是示意資料:
- 公司:Example Company
- GitHub organization/repository:
example-company/demo-backend - Subscription:
Demo-Subscription - App/ACR Resource Group:
rg-demo-stg-001 - 共用 Container Apps Environment:
cae-demo-stg-001 - 共用 CAE 所在 Resource Group:
rg-cae-demo-stg-001 - ACR:
acrdemostg001.azurecr.io - SQL Server:
sql-demo-stg-001.database.windows.net - SQL Database:
demoproject-dev - SQL Private Endpoint:
pe-sql-demo-stg-001 - VNet:
vnet-demo-stg-001
預期使用的兩個 Container App 是:
| App | 用途 | Environment |
|---|---|---|
ca-demo-backend-dev-001 | Backend API | cae-demo-stg-001 |
ca-demo-frontend-dev-001 | Frontend | cae-demo-stg-001 |
另外,情境中有一組不建議繼續使用的舊 App:
| App | 舊 Environment | 舊 image registry | 問題 |
|---|---|---|---|
ca-example-backend-dev-001 | cae-example-dev-001 | harbor.example.invalid | 不在共用 CAE,也沒有正確接上 Azure ACR |
| 舊 Frontend App | cae-example-dev-001 | 舊 Harbor | image 或 Nginx 設定可能仍指向舊 Backend |
正確的目標架構
完整的部署與連線路徑應該是:
GitHub Actions
│ OIDC,取得短期 token
▼
GitHub CI Service Principal
│ AcrPush + Container App update
▼
Azure Container Registry
│ image push
▼
Container App(共用 cae-demo-stg-001)
│ App 自己的 system-assigned Managed Identity
├─ ACR AcrPull
├─ Key Vault secret get/list(如果 App 使用 Key Vault)
└─ Azure SQL Entra token
│
▼
VNet + Private DNS + SQL Private Endpoint
│
▼
demoproject-dev
最重要的觀念是:
GitHub Actions 的 Service Principal 負責部署,不是執行中的 App。
真正需要 pull image 和連 SQL 的,是各個 Container App 自己的 Managed Identity。
CI/CD 的正確流程
1. GitHub Actions 使用 OIDC 登入 Azure
GitHub Actions 不應把長期的 Azure client secret 放在 repository。建議使用 OIDC:
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
示範用的 Federated Credential subject 是:
repo:example-company/demo-backend:ref:refs/heads/main
如果 workflow 使用 GitHub Environment,例如 staging,subject 和 variables 的 scope 都要跟 GitHub Environment 的設定一致,不能只改其中一邊。
2. CI 身分將 image push 到 ACR
示範用的 image repository 是:
acrdemostg001.azurecr.io/demo-backend:<github.sha>
GitHub CI Service Principal 需要 AcrPush,但不需要 runtime AcrPull。
AcrPush = GitHub Actions 將 image 推進 ACR
AcrPull = Container App 啟動時從 ACR 拉出 image
demo-backend 在第一次成功 docker push 前可能還不會出現在 ACR repository list 中。ACR 通常會在第一次 push 時建立 repository,這不是權限錯誤。
3. CI 身分更新正確的 Container App
部署 Backend 時,target 應該是位於共用 CAE 的 App:
az containerapp update \
--resource-group rg-demo-stg-001 \
--name ca-demo-backend-dev-001 \
--image acrdemostg001.azurecr.io/demo-backend:${GITHUB_SHA}
這個指令成功只代表 Azure 接受了 App 設定更新,不代表新的 revision 一定能啟動。接下來還要檢查 runtime identity 是否能 pull image。
4. Container App 使用自己的 Managed Identity pull image
Backend 和 Frontend 都要各自完成以下設定:
- 啟用 system-assigned Managed Identity。
- 將該 App 的 identity 在 ACR scope 授予
AcrPull。 - 將 Container App registry authentication 設為
identity: system。
可用以下指令設定 registry identity:
az containerapp registry set \
--resource-group rg-demo-stg-001 \
--name ca-demo-backend-dev-001 \
--server acrdemostg001.azurecr.io \
--identity system
Frontend App 也要使用相同方式設定,只是 --name 改為 ca-demo-frontend-dev-001。
檢查 App identity、Environment、registry 與 image:
az containerapp show \
--resource-group rg-demo-stg-001 \
--name ca-demo-backend-dev-001 \
--query "{identity:identity,environment:properties.managedEnvironmentId,registries:properties.configuration.registries[].{server:server,identity:identity},images:properties.template.containers[].image}" \
-o json
檢查 runtime identity 是否有 ACR AcrPull:
az role assignment list \
--assignee-object-id <BACKEND_APP_PRINCIPAL_ID> \
--scope "$(az acr show \
--resource-group rg-demo-stg-001 \
--name acrdemostg001 \
--query id -o tsv)" \
--query "[].{role:roleDefinitionName,scope:scope}" \
-o table
Frontend 使用自己的 <FRONTEND_APP_PRINCIPAL_ID> 查詢,不要共用 Backend 的 identity。
如果 App 沒有 AcrPull,常見結果是:
az containerapp update指令本身成功。- revision 建立了,但狀態是
ActivationFailed。 - logs 或 revision events 出現 image pull、registry authentication 或 unauthorized 錯誤。
這時應該補在 Container App 的 Managed Identity,不能把 AcrPull 加到 GitHub OIDC Service Principal 代替。
Backend 存取 SQL Database
SQL 身分的分工
Backend 不應把 SQL username/password 寫進 GitHub Secrets 或 container environment。建議使用:
- Backend App 的 system-assigned Managed Identity 取得 Microsoft Entra token。
- SQL Entra administrator 在
demoproject-dev建立對應的 contained user。 - 只授予 Backend 實際需要的 database roles。
示範用的 database URL 概念如下:
sqlserver://sql-demo-stg-001.database.windows.net:1433
?database=demoproject-dev
&encrypt=true
&trustservercertificate=false
&fedauth=ActiveDirectoryManagedIdentity
&useMsi=true
建立 contained user 與 database roles
以下 SQL 必須由 SQL Microsoft Entra administrator 或有足夠 database 權限的管理者,在 demoproject-dev database 執行:
CREATE USER [ca-demo-backend-dev-001] FROM EXTERNAL PROVIDER;
再依照應用程式需要授予角色:
ALTER ROLE [db_datareader]
ADD MEMBER [ca-demo-backend-dev-001];
ALTER ROLE [db_datawriter]
ADD MEMBER [ca-demo-backend-dev-001];
-- 只有需要由 App 執行 schema migration 時才授予
ALTER ROLE [db_ddladmin]
ADD MEMBER [ca-demo-backend-dev-001];
這些是 SQL Database 裡的 data-plane permissions,不是 Azure Resource Group RBAC。因此 az role assignment list 看不到 db_datareader、db_datawriter 或 db_ddladmin membership。
可以在 SQL 中確認:
SELECT
dp.name AS database_principal,
rp.name AS database_role
FROM sys.database_role_members AS drm
JOIN sys.database_principals AS rp
ON rp.principal_id = drm.role_principal_id
JOIN sys.database_principals AS dp
ON dp.principal_id = drm.member_principal_id
WHERE dp.name = N'ca-demo-backend-dev-001';
Frontend 不需要 SQL 權限,也不應為 Frontend 建立 SQL contained user。
SQL 網路路徑
即使 SQL user/roles 都正確,網路不通時仍然無法連線。
Demo 情境中的網路設計是:
pe-sql-demo-stg-001的狀態為 Approved。- Private DNS zone
privatelink.database.windows.net連結到vnet-demo-stg-001。 - 共用
cae-demo-stg-001有正確的 VNet/infrastructure subnet 路徑。 - App 放在共用 CAE,才能透過 Private Endpoint 解析並連線到 SQL。
舊的 cae-example-dev-001 沒有這條 infrastructure subnet 路徑,因此不適合作為需要存取 Private SQL 的 target。
看到下面的錯誤時,優先檢查 CAE、VNet、Private DNS 和 Private Endpoint:
Client with IP address ... is not allowed to access the server
不要為了繞過問題,直接把 SQL firewall 開成 0.0.0.0。這會把網路問題變成不必要的公開暴露。
看到下面的錯誤時,優先檢查 Managed Identity、contained user 和 database roles:
Login failed
開發者帳號能做什麼
示範開發者帳號是 [email protected],在 demo tenant 中的 UPN 示意如下:
demo_user_example.com#EXT#@exampletenant.onmicrosoft.com
示範直接授予的 Azure roles:
| Scope | Role | 可以做什麼 |
|---|---|---|
rg-demo-stg-001 | Contributor | 在 App RG 建立、更新、刪除 Container App |
rg-demo-stg-001 | Reader | 讀取 RG 資源 |
acrdemostg001 | AcrPush | Push image 到 ACR |
cae-demo-stg-001 | Reader | 讀取共用 CAE 設定 |
cae-demo-stg-001 | Container Apps Environment Joiner | 建立 App 時加入共用 CAE |
因此,這個帳號可以:
- 建立或更新 App。
- 將 App 放進共用
cae-demo-stg-001。 - Push image 到 ACR。
但這個帳號不能:
- 建立 Azure RBAC role assignment。
- 替新的 App 授予 ACR
AcrPull。 - 修改 SQL Private Endpoint、VNet 或 SQL firewall。
- 在
demoproject-dev建立 Entra contained user。 - 授予或修改 SQL database roles。
這是刻意的權限邊界:開發者可以部署應用程式,但不能自行擴大 App 的 Azure 權限或資料庫權限。需要新增 AcrPull 或 SQL user/roles 時,應請平台管理者或 SQL Entra administrator 處理。
舊 App 如何切換到共用 CAE
Azure Container Apps 沒有提供用 az containerapp update 直接切換 Environment 的參數。既有 App 不能像更新 image 一樣,直接從:
cae-example-dev-001
搬到:
cae-demo-stg-001
較安全的切換方式是:
- 在共用 CAE 建立新的 Container App,或使用已準備好的
ca-demo-backend-dev-001/ca-demo-frontend-dev-001。 - 逐項複製必要的 environment variables、secrets、ingress、domain、scale 和 traffic 設定。
- 啟用新 App 的 system-assigned Managed Identity。
- 由平台管理者授予新 identity ACR
AcrPull。 - 由 SQL Entra administrator 替新的 Backend identity 建立 SQL user/roles。
- 將 CI/CD 的 target App name 改成新的 App。
- 驗證 revision、logs、API、Frontend URL、ACR pull 與 SQL 連線。
- 新 App 穩定後,才考慮移除舊 App。
不要先刪除舊 App 再重建,因為可能遺失 secrets、domain、ingress、traffic 和原本的部署設定。
Frontend 的特別注意事項
Frontend 只需要:
- 從 ACR pull image。
- 連到正確的 Backend URL。
- 必要時讀取自己的 Key Vault secrets。
Frontend 不需要 demoproject-dev 的 SQL roles。
如果 Frontend revision 是 ActivationFailed,但 AcrPull 已經正確,請檢查:
- image 內的 Nginx upstream 設定。
BACKEND_URL或其他 Backend endpoint environment variable。- image 是否仍然包含舊的
backend.legacy.example.invalidhostname。 - 新 image 是否真的已 push 到 ACR,且 App 使用了正確 tag。
這類問題通常是 image/configuration 問題,不是 SQL 權限問題。
建議的執行順序
平台管理者與開發團隊可以依照以下順序處理:
- 確認 GitHub OIDC Federated Credential 的 repository、branch 和 Environment subject 正確。
- 確認 CI Service Principal 只有部署所需的
Contributor、AcrPush等角色。 - 確認 Backend 和 Frontend 都位於
cae-demo-stg-001。 - 啟用兩個 App 的 system-assigned identity。
- 對兩個 App identity 各自授予 ACR
AcrPull。 - 執行
az containerapp registry set --identity system。 - 由 SQL Entra administrator 建立 Backend contained user 和必要 roles。
- 確認 CAE、VNet、Private DNS、Private Endpoint 的網路路徑。
- 只啟用一條正式部署路徑,避免舊 pipeline 和 Azure CI/CD 同時更新同一個 App。
- 做一次受控部署,逐項檢查 image、revision、logs、API、Frontend 和 SQL。
部署後驗證指令
確認 ACR image tag
az acr repository show-tags \
--name acrdemostg001 \
--repository demo-backend \
--orderby time_desc \
--top 5 \
-o table
確認 Backend revision
az containerapp revision list \
--resource-group rg-demo-stg-001 \
--name ca-demo-backend-dev-001 \
--query "[].{name:name,health:properties.healthState,running:properties.runningState,traffic:properties.trafficWeight}" \
-o table
正常情況應看到 revision 為 Healthy,且 running state 正常。若 revision 建立成功但沒有 running,請查 revision events 和 container logs。
查看 Backend logs
az containerapp logs show \
--resource-group rg-demo-stg-001 \
--name ca-demo-backend-dev-001 \
--type console \
--tail 100
最後確認的成功條件
- GitHub Actions OIDC login 成功。
- image 成功 push 到
acrdemostg001.azurecr.io。 - Backend 和 Frontend revision 能從 ACR pull image。
- Backend revision 為
Healthy / Running。 - Backend 能以 Managed Identity 取得 SQL token。
- SQL contained user 與必要 roles 存在。
- Backend 能連線到
demoproject-dev。 - Frontend 使用新的 Backend URL。
- 舊的部署系統不會再覆蓋 Azure CI/CD 的結果。
