The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在 Azure DevOps Service 建立 Docker Registry 的 Service Connection 並成功推送 Image 到 Azure Container Registry (ACR)

上個月我花了許多時間在幫一家知名企業導入 Azure DevOps Services 服務,過程中遇到了一些關於 Azure Container Registry (ACR) 的授權問題,因為企業要求「最小權限原則」的關係,很多時候並不能用 UI 介面上的「預設值」來設定,尤其是要盡可能使用 Azure IAM 授權這件事,就很考驗使用者對 Entra ID ( Azure AD ) 的理解。今天這篇文章我就來分享幾個在 Azure DevOps Service 建立 Docker Registry 的 Service Connection 並成功推送 Image 到 Azure Container Registry (ACR) 的經驗。

以終為始:要知道怎樣 Push 之前,要先知道怎樣 Pull

要知道怎樣推送 image 到 ACR,那就先來看看怎樣把 image 拉下來,並且先掌握一些重要的專有名詞!

假設我們想要推送 Docker Image 到 Azure Container Registry (ACR) 上,而且我們會這樣下載 Image:

docker pull azuredevopstests.azurecr.io/samples/hello-world:78883

我們就要來先拆解一下上述 Image 位址的每個片段在 ACR 所代表的名詞:

  • Login server

    對 ACR 來說,這個名字通常是一個 DNS 名稱,例如:

    azuredevopstests.azurecr.io
    

    有時候也會稱為 Docker Registry 這個名字,如果是這個名字,通常代表著一個 https 的網址:

    https://azuredevopstests.azurecr.io
    
  • Repository

    這個名字通常是一個專案名稱,又或是 image 名稱,可能包含路徑部分,例如:

    samples/hello-world
    
  • Tag

    這裡就是 image 的 tag (標籤) 部分:

    78883
    

在認識了這幾個重要的名詞後,我們才能比較順利的在 Azure Pipelines 裡面使用 Docker v2 task (Docker@2) 來設定 Docker 作業。

建立服務連線 (Service Connection)

在建立 Pipelines 的時候,首先要先加入一個 Docker v2 task,請且要記得選擇 Docker@2 版本。然後在 Container registry 欄位按下 New 建立一個新的服務連線 (Service Connection):

Docker@2 - Docker v2 task

在開啟 New service Connection 設定畫面後,第一個選擇就是 Registry type 欄位,這裡有三個選項:

  1. Docker Hub

    只能發佈到 Docker Hub 上的公開儲存庫(Public Registry),不能發佈到 Azure Container Registry (ACR)。

  2. Others

    可以發佈到任何 Docker Registry v2 API 相容的儲存庫,其中當然也包含 Azure Container Registry (ACR)。

  3. Azure Container Registry

    只能發佈到 Azure Container Registry (ACR),並且可以透過 Service Principal 來通過 ACR 的 IAM 驗證。

因為這三種 Registry type 選項的差異,除了第一種不適用 ACR 之外,另外兩種都可以用來發佈到 ACR 上,但是在設定上有些不容易釐清的概念,所以我打算分開來說明。

當 Registry type 等於 Others 時該如何設定

當 Registry type 等於 Others 時該如何設定

這裡主要有 4 個欄位可以設定:

  1. Docker Registry

    你可以把 https://index.docker.io/v1/ 變更成 ACR 的 Login server 名稱,但必須加上 https:// 在域名前面,例如:

    https://azuredevopstests.azurecr.io
    
  2. Docker ID

  3. Docker Password

    這兩個欄位就是 ACR 在啟用 Admin user 時的 Username 與 Password,如下圖示:

    Container registry > Access keys

    請注意,在建立 ACR 的時候預設 Admin user 模式是關閉的,而且管理 ACR 的 Access keys 常會有安全性的風險,所以若要在 Azure Pipelines 裡面設定 ACR 認證的話,我個人不建議啟用 Admin user 模式,而是建議改用 Service Principal 搭配 ACR 的 IAM 來進行驗證。

    那要怎麼做呢?其實你可以先自行建立 Service principal 並設定好 ACR 的 IAM 之後,請記得替這個 Service principal 授予 AcrPush 角色。然後,你的 Docker ID 可以設定成 Service principal 的 Application (Client) ID,而 Docker Password 可以設定成 Service principal 的 Service principal key,這樣就可以順利使用「最小權限原則」來設定該 Azure Pipelines 與 ACR 之間的驗證與授權!

    Docker Registry:    Your container registry URL (eg. https://myacr.azurecr.io)
    Docker ID:          Service principal client ID
    Password:           Service principal key
    

    詳見 DockerV2 only supports Docker registry service connection and not support ARM service connection. How can I use an existing Azure service principal (SPN) for authentication in Docker task? 說明。

  4. Email (optional)

    這個欄位可以不用填寫。

當 Registry type 等於 Azure Container Registry 時該如何設定

當 Registry type 等於 Azure Container Registry 時該如何設定

這裡的 Authentication Type 其實有兩種可能的值:

  1. Service Principal

    顧名思義,就是使用 Service Principal 來進行驗證與授權。

    在選取 Service Principal 時,Azure Pipelines 會自動列出你目前登入身分所有可以看到的 Subscriptions (訂用帳戶) 讓你選擇 (包含所有的 Tenants 下的 Subscriptions 都會列出來),選取訂用帳戶後,就會顯示該訂用帳戶下所有可用的 ACR 讓你選擇,因此在選取時,你目前的身分必須擁有完整的 Subscription 權限才能看到並選擇所有可能的 ACR,因此設定時你將會需要擁有 SubscriptionOwner 權限才能透過這個管道設定。因此這種設定方法並不適用於「最小權限原則」的情況,像我們客戶就沒辦法這樣設定!

  2. Managed Service Identity (MSI)

    這個選項是使用 Azure VM 的 Managed Identity 來進行驗證與授權,而 Managed Service Identity 其實就是 Managed Identity 的早期名稱,微軟已經改名了,但是在 Azure Pipelines 這邊還沒改到,未來應該會修改吧。

    簡單來說,這個選項只有在你使用 Self-hosted Agent 且部署在 Azure VM 的時候才有意義,因為只有這樣才能讓你使用 System-assigned managed identity 或 User-assigned managed identity 來讓 ACR 指派權限給這個 Managed Identiy 使用。

若你可以順利透過 Service Principal 設定完所需的 Service Connection 的話,事實上他會做出以下設定:

  1. 在 Entra ID 中建立一個 App (Service Principal)

    其命名規則通常是這樣的:組織名稱-專案名稱-Subscription ID

    orgname-projectName-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    

    這裡的 orgname 是你的 Azure DevOps Service 的組織名稱

    這裡的 projectName 是你的 Azure DevOps Service 的專案名稱

    這裡的 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 是你選中的 Subscription ID

    這樣的組織名稱有個問題,就是如果你在同一個 Azure DevOps Service 組織底下的某個專案中有兩個不同的 Service Connection 的話,建立的過程會在 Entra ID 中建立起兩個完全同名的 Service Principal,這個重複的名稱會讓你無法用肉眼辨識出這到底是哪個 Service Connection 在用的,因為你從 Azure Pipelines 是完全看不出來的,沒有 UI 可以讓你查出你在 Service Connection 中到底使用了哪個 Service Principal ID,這樣在管理上會有點小困擾。

    我的小小建議就是,只要你在 Azure DevOps Service 建立 Docker Registry 的 Service Connection 並使用 Azure Container Registry (ACR) 搭配 Service Principal 來進行驗證與授權的話,在建立 Service Connection 之後,就立刻到 Entra ID 中查看一下 App registrations,並將 Service Principal 變更名稱 (例如可以加上 Service Connection 的名稱),避免日後管理上的混淆。

  2. 然後在 Azure Container Registry (ACR) 的 IAM 中加入這個 Service Principal,並賦予 AcrPush 角色

總結

上述就是我特別針對在 Azure DevOps Service 建立 Docker Registry 這個類型的 Service Connection 並要推送 Image 到 Azure Container Registry (ACR) 的所有技術細節,這麼細的權限設定知識不深入研究還真的不太可能好好的理解!

設定 Docker@2 task 的重點,就在 Service Connection 的部份而已,其他都很簡單,不外乎是設定 repositorytags 參數而已。

以下是一份 Docker@2 的 YAML 範例:

steps:
- task: Docker@2
  displayName: 'Build and Push an image'
  inputs:
    containerRegistry: AzureDevOpsTestsACR
    repository: 'hello-world'
    tags: '$(Build.BuildId)-new'

以下列出我在研究時搞不清楚但很想知道的細節,所有疑惑也都已經獲得解答:

  1. 透過建立 Docker Registry 這個類型的 Service Connection 並要推送 Image 到 Azure Container Registry (ACR) 的最小 IAM 權限為何?

    到 ACR 授權 Service Principal 給予 AcrPush 角色即可。

  2. 當你在 Authentication Type 欄位選取 Service Principal 的時候,他會自動載入哪些 Subscriptions (訂用帳戶)?為什麼有些訂用帳戶我沒看過?

    你可以透過以下 Azure Az PowerShell 模組的 Cmdlets 可以取得完全相同的訂用帳戶清單:

    Get-AzTenant | foreach {
        Get-AzSubscription -TenantId $_.TenantId | select Name,State,IsDefault,Id | ft
    }
    
  3. 由於建立完成的 Service connection 服務連線,在 Edit 的時候並不能修改 Service Principal 的參數,甚至連 Application (Client) ID 都看不到。我如果在一同一個 Project 底下建立了兩次 Service connection 的話,我在 Microsoft Entra ID 裡面的 App registrations 會看到完全同名的兩個 Service Principal,我目前完全無法區分到底是哪個 Service Connection 在使用。那這樣我要怎樣區別與管理 Microsoft Entra ID 之中多個完全同名的 Service Principal 呢?

    目前無法從 Azure DevOps Service 的 Web 介面看到這些資訊,目前唯一的方法就是透過 Azure DevOps Services REST API 呼叫 Service Endpoint 底下的 Endpoints API 才能獲取這個重要資訊!

    以下是透過 az devops invoke 命令取得 Service Connection 詳細資訊的範例 (PowerShell):

    $orgName = "willh"
    $projectName = "AzureDevOpsTests"
    $serviceConnectionName = "aaaaa"
    $serviceConnection = az devops invoke --org "https://dev.azure.com/$orgName" -o json `
      --route-parameters 'project=AzureDevOpsTests' `
      --area 'serviceendpoint' --resource 'endpoints' `
      --query-parameters "endpointNames=$serviceConnectionName" | ConvertFrom-Json | select -ExpandProperty value
    
    # 取得 authorization 資訊 (裡面有 servicePrincipalId 資訊)
    $serviceConnection.authorization.parameters
    

相關連結

留言評論