【2022鐵人賽】基本版-建立CI Pipeline(1)

昨天建立完PR Pipeline之後,接下來就需要建立CI Pipeline來編譯.Net程式了,不過CI Pipeline要做的事情比較多,包含Build Code、Build Image、Deploy Dev to CloudRun,所以這些部份會分幾篇文章來完成。

首先是Build Code的部份,雖然去年的文章已經有建立YAML的Pipeline來編譯.Net程式的範例,但是今年還是要再做一次。

只是如果我只是照著去年的內容搬過來改一小部份就交差,那肯定會被噓爆的吧?所以這次我們改用.net sdk的docker image執行container來編譯.net的程式,這樣就是新鮮貨了,對吧!

因為用container執行sdk是透過script的方式執行,所以就沒辦法使用去年文章的方式把小助手叫出來設定幾個選項搞定,能透過小幫手的部份也只是加一個Bash的Task,差異不大。不囉嗦,還是直接先上code吧!

trigger:
- none

pool:
  vmImage: ubuntu-latest

resources:
  repositories:
  - repository: sources
    type: git
    name: ironman2022/NetApp
    ref: Develop
    trigger:
      branches:
        include:
          - Develop

jobs:
  - job: BuildCode
    steps:
      - checkout: sources
        clean: true
      - script: |
          export UID=$(id -u)
          export GID=$(id -g)
          docker run --user $UID:$GID --rm \
          -v $(Build.SourcesDirectory):/tmp/source \
          -v $(Build.BinariesDirectory):/tmp/publish \
          -e DOTNET_CLI_HOME=/tmp/.dotnet \
          mcr.microsoft.com/dotnet/sdk:6.0-alpine \
          dotnet publish /tmp/source/IronmanWeb.sln \
          -c release \
          -o /tmp/publish
        displayName: Dotnet Build
      - task: ArchiveFiles@2
        displayName: 壓縮成zip
        inputs:
          rootFolderOrFile: $(Build.BinariesDirectory)
          includeRootFolder: false
          archiveType: 'zip'
          archiveFile: '$(Build.ArtifactStagingDirectory)/zipFiles/buildResult.zip'
          replaceExistingArchive: true
      - task: PublishBuildArtifacts@1
        displayName: 上傳到Pipeline Artifact
        inputs:
          PathtoPublish: '$(Build.ArtifactStagingDirectory)/zipFiles/buildResult.zip'
          ArtifactName: 'output'
          publishLocation: 'Container'

上面的YAML內容前半段是借用前一篇文章的設定,從sources這個repository的ref分支參考的下一行開始才是今天新增的內容。可以發現多增加了trigger的部份,而且是設定在resources底下的repository設定,同時設定了以Develop分支為篩選條件(branches下面的include)。

    trigger:
      branches:
        include:
          - Develop

接著下面有一個名為BuildCode的Job,這個名稱不是放在displayName屬性裡面,它是屬於ID(也就是識別)的一種,只能包含英數字和底線,不能有空格也不能以數字為開頭。簡單來說就是程式的變數命名的概念。

jobs:
  - job: BuildCode

接著在BuildCode Job底下的steps加入了許多不同的task,先checkout程式碼出來,沒有特別設定path的話會放在Build.SourcesDirectory這個系統預先定義的變數中。

接著我們使用script(等同Bash task)寫了一段Linux的script,利用dotnet 6.0的sdk docker image(使用alpine的版本,image size比較小)建立container來編譯.net程式(使用publish命令)。

在script中每一行最後的空白加上「\」符號是命令區塊換行的用法,是為了可讀性,實際執行的時候會是與同一行指令相同效果。

      - script: |
          export UID=$(id -u)
          export GID=$(id -g)
          docker run --user $UID:$GID --rm \
          -v $(Build.SourcesDirectory):/tmp/source \
          -v $(Build.BinariesDirectory):/tmp/publish \
          -e DOTNET_CLI_HOME=/tmp/.dotnet \
          mcr.microsoft.com/dotnet/sdk:6.0-alpine \
          dotnet publish /tmp/source/IronmanWeb.sln \
          -c release \
          -o /tmp/publish
        displayName: Dotnet Build

前面兩行的export是為了將host的uid和gid帶到container裡面(–user $UID:$GID部份),避免檔案建立之後的問題。

–rm是告訴docker當container使用完就可以刪掉了。

-v的部份則是掛volume的設定,也就是將host的路徑與container內的路徑關聯起來,script中的例子就是把$(Build.SourcesDirectory)所代表的位置和$(Build.BinariesDirectory)所代表的位置分別和container內的/tmp底下的source、publish目錄關聯起來,這樣就可以在source目錄中找到程式碼,編譯產生的檔案放在container內的publish目錄則是相當於放在host。

-e DOTNET_CLI_HOME=/tmp/.dotnet這部份也是把sdk的Home目錄設在/tmp底下的.dotnet目錄,會發現這裡都是使用/tmp開頭,因為在這個tmp目錄下才不會碰到存取權限的問題。

在mcr.microsoft.com/dotnet/sdk:6.0-alpine這個image repository之後就是單純的dotnet sdk的指令,和docker container就沒什麼關係了,就是指定使用publish指令與其它設定來執行編譯的動作。

接下來後面兩個task就是把編譯完的檔案壓縮成zip檔案之後上傳到Pipeline Run的Artifacts空間中,這部份和去年的這篇文章內容做的事是一樣的。

因此這篇內容的關鍵在於resources的repository設定與利用docker container來編譯程式而不是使用內建的dotnet task。

到這邊就完成了Build Code的設計部份,後續步驟請待下一篇文章分享。

發佈留言