在Azure DevOps中建立共用的Yaml Pipeline Template

建立共用的 ADO yaml pipeline Template

為何有這種需求

最近在上 Azure DevOps 課程,下課後,一群學員突然跑來問了個問題。

「老師,我們公司有三十幾個 Azure DevOps 專案,每個專案都有自己的 repo,每個 repo 都有自己的 pipeline yaml 檔…」他頓了一下,臉上帶著某種「歷盡滄桑」的表情,「有沒有辦法讓這些 pipeline 共用同一份 template?改一次就全部生效的那種?」

我其實急著下課,並沒有聽得很仔細,腦袋裡還在糾結:「為何要讓所有的 repo 都共用同一份 yaml template,每個開發專案不是應該有自己的 pipeline design 嗎?為何要為了統一控管搞得這麼複雜?」

於是我追問:「為什麼會有這種需求呢?」

他苦笑著說「每當公司想要在 pipeline 裡加入一些共用的步驟,比如說安全掃描、程式碼品質檢查、部署前的驗證等等,我們就得一個一個 repo 去改 pipeline yaml 檔。三十幾個專案,每個專案又可能會有好幾個 repo,改起來超級累人。」

教室裡其他學員也紛紛露出「我懂我懂」的表情。這種痛,做 DevOps 的人應該都瞭。

更慘的是,當你好不容易改完二十幾份檔案,過兩個月主管又說:「欸,我們現在還要加入程式碼品質檢查。」然後你又要再改一輪。如果哪天發現其中一個步驟寫錯了,想改?好啊,二十幾個 repo 再巡一次。

這就像是複製貼上了二十幾份同樣的食譜,結果發現鹽放太多了,然後你得把二十幾本筆記本都翻出來,一本一本改。真的累。

「所以…」學員問,「有沒有辦法讓這些 pipeline 共用同一份 template?改一次就全部生效?」

我當下趕著回家,沒多想,也來不及具體回答。

但回家後仔細思量:「這個問題其實挺實際的,應該是有解決方案的吧?」稍微翻了一下,果然發現 Azure DevOps pipeline 原本就支援引用外部的 yaml template。

如何實現?

Azure DevOps 其實早就想到這個問題了。它提供了一個機制,讓你可以把 pipeline 的某些步驟抽出來,放到另一個 repo 裡當作「模板」,然後其他專案的 pipeline 就可以「引用」這個模板。

聽起來很簡單對吧?但實際上第一次設定的時候,總會有那種「欸這樣寫對嗎?」的不確定感。

我也是這樣,看著官方文件,然後照著做,結果 pipeline 一開始是連儲存都失敗,後來是跑起來一直紅燈。改了兩三次才發現是路徑寫法錯了。

所以在好不容易把範例做完之後,順手寫了這篇文章,一步一步紀錄一下整個流程。
不廢話,直接做。

具體實作

我建了兩個 Azure DevOps 專案來示範,分別叫 TestShardYmlATestShardYmlB

想像一下,TestShardYmlA 是「模板庫」,專門用來放那些可以共用的 pipeline 步驟。而 TestShardYmlB 是「實際專案」,它的 pipeline 會去引用模板庫裡的東西。

這種設計的好處是,以後如果你的企業有更多專案,它們都可以指向同一個模板。改一次模板庫裡的模板,所有專案全部生效。

第一步:建立模板 Repo

TestShardYmlA 專案裡,我建了一個 repo,然後在裡面放了一個共用的 pipeline template,位置是這樣的:

/SharedTemplates/templates/dotnet-unit-test.yml

圖片

看到這個資料夾結構了嗎?這就是模板庫的樣子。
假設,我將片段的 yml 內容撰寫如下:

# 檔案:ProjectA / SharedTemplates / templates/dotnet-unit-test.yml
# 功能:共用的 .NET Unit Test 步驟

steps:
- task: DotNetCoreCLI@2
  displayName: Test
  inputs:
    command: test
    projects: $(BuildParameters.TestProjects)
    arguments: --configuration $(buildConfiguration)

就這樣。

這個 template 做的事情很單純:跑 .NET 的 unit test。
就這麼幾行,但它未來會做為所有專案共用的標準測試步驟。(假設你的專案是用 .NET Core 開發的)

如果哪天你想在測試後加點什麼,比如說檢查測試覆蓋率,或是輸出測試報告,改這裡就好。所有套用到這個 template 的專案,全部會自動更新。

第二步:在實際專案中引用 Template

接下來,我們看 TestShardYmlB,這是實際在跑的專案。

在它的 repo 裡,我建了一個 azure-pipelines.yml,這是該專案主要的 pipeline 定義檔。內容如下:

# 檔案:ProjectB / <你的 WebApp Repo> / azure-pipelines.yml
# ASP.NET Core
# Build and test ASP.NET Core projects targeting .NET Core.

trigger:
- main

pool:
  vmImage: ubuntu-latest

# 👉 引用 ProjectA 的共用 template Repo 👈
resources:
  repositories:
  - repository: sharedTemplates                  # 在這個 yml 裡的暱稱
    type: git
    name: TestShardYmlA/TestShardYmlA            # 格式:<ProjectA 名稱>/<Repo 名稱>
    #ref: refs/heads/main                        # 或 main,看你那邊的 branch 名稱

variables:
- name: buildConfiguration
  value: 'Release'
- name: BuildParameters.RestoreBuildProjects
  value: '**/*.csproj'
- name: BuildParameters.TestProjects
  value: '**/*[Tt]ests/*.csproj'

name: $(date:yyyyMMdd)$(rev:.r)

jobs:
- job: Job_1
  displayName: Agent job 1
  pool:
    vmImage: ubuntu-latest
  steps:
  - checkout: self

  - task: DotNetCoreCLI@2
    displayName: Restore
    inputs:
      command: restore
      projects: $(BuildParameters.RestoreBuildProjects)

  - task: DotNetCoreCLI@2
    displayName: Build
    inputs:
      projects: $(BuildParameters.RestoreBuildProjects)
      arguments: --configuration $(buildConfiguration)

  # 👉  這裡改成使用 ProjectA 的共用 Unit Test template 👈
  - template: SharedTemplates/templates/dotnet-unit-test.yml@sharedTemplates

  - task: DotNetCoreCLI@2
    displayName: Publish
    inputs:
      command: publish
      publishWebProjects: true
      projects: $(BuildParameters.RestoreBuildProjects)
      arguments: --configuration $(buildConfiguration) --output $(build.artifactstagingdirectory)
      zipAfterPublish: true

  - task: PublishBuildArtifacts@1
    displayName: Publish Artifact
    condition: succeededOrFailed()
    inputs:
      PathtoPublish: $(build.artifactstagingdirectory)
      TargetPath: '\\my\share\$(Build.DefinitionName)\$(Build.BuildNumber)'

關鍵在這兩個地方

第一個是 resources: 區段:

resources:
  repositories:
  - repository: sharedTemplates
    type: git
    name: TestShardYmlA/TestShardYmlA

這段是在告訴 Azure DevOps:「嘿,我要用另一個專案裡的 repo,它叫 TestShardYmlA/TestShardYmlA,在這個 pipeline 裡我給它取個暱稱叫 sharedTemplates。」

格式很重要:<專案名稱>/<Repo 名稱>。如果寫錯了,pipeline 會找不到,然後就報錯了。

第二個是引用 template 的那一行:

- template: SharedTemplates/templates/dotnet-unit-test.yml@sharedTemplates

這行的意思是:「去 sharedTemplates 這個 repo(就是我們剛剛取的暱稱),找到 SharedTemplates/templates/dotnet-unit-test.yml 這個檔案,然後把裡面的 steps 插到這裡。」

就是這麼簡單。
但關鍵就藏在這麼簡單的兩個步驟裡。

現在,如果你有十個、二十個、甚至五十個專案,它們都可以用同樣的方式引用這個 template。以後要改測試步驟?改一個地方就好。不用再一個 repo 一個 repo 巡了。

結論

自動化的本質,說穿了就是「減少重複勞動」。

你寫的每一行 code、每一個 pipeline、每一個設定檔,都應該問自己:「這東西會不會重複用到?」如果答案是「會」,那就該想辦法抽出來、共用它。

Azure DevOps 的 pipeline template 就是這種思維的呈現。它並非什麼高深的技術,就是把「可以重複用的東西」拉到一個地方,然後讓所有需要它的人都能輕鬆取用。

但這背後的價值,可不只是「少打幾行 code」這麼簡單。

當你的團隊有了共用的 template,代表著:

  • 標準化:所有專案的 pipeline 都遵循同樣的規範,不會有人自己亂搞。
  • 可追溯性:template 放在 git 裡,每次改動都有紀錄,出事了可以回溯。
  • 快速迭代:要推廣新的實踐(比如安全掃描、測試覆蓋率檢查),改一次就全部生效。

更重要的是,當有一天你的主管又跑來說「我們要在所有 pipeline 加入 XXX 步驟」的時候,你可以淡定地回答:「好,我五分鐘改給你。」

然後看著他驚訝的表情,內心暗爽。

這才是 DevOps 該有的樣子。😁


參考資料:
https://learn.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops&utm_source=chatgpt.com&pivots=templates-includes

相關課程:
https://www.studyhost.tw/NewCourses/ALM

留言

這個網誌中的熱門文章

為何想做CI的你,根本不該使用 Gitflow?

開啟 teams 中的『會議轉錄(謄寫)』與Copilot會議記錄、摘要功能

使用LM Studio輕鬆在本地端以API呼叫大語言模型(LLM)

VS Code的字體大小

原來使用 .net 寫個 MCP Server 如此簡單