想不到有什麼簡短的標題,姑且就順著上次的《GitLab CI + Windows Docker 的一些紀錄》,把這篇當成 part 2 吧。
Heresy 這邊在 2019 年建立了工作環境的 GitLab server 後,也花了好一段時間把 GitLab CI/CD 的架構弄了一個雛型出來。中間有搞錯一些東西、也拿來玩了一些其它自己的東西。
而老實說,在 Heresy 這邊碰到最多問題的,基本上應該還是 Windows Docker 上的問題了…
Heresy 這邊的 GitLab CI/CD,其實目前主要還是用來做整個專案的跨平台建置測試,有部分新開發的專案也有透過 Boost Test(官方文件)來同時針對 Visual Studio 和 GitLab 做整合。
在最初寫的《GitLab 簡單的 C++ 專案腳本範例》中,基本上並沒有使用 Docker 來做建置,而是直接使用 shell 來跑。
這樣的方案,好處是單一 Runner 的架設相對簡單、也容易找到錯誤;而由於基本上就是直接在電腦上操作,所以不太容易會有額外的問題。
但是相對地,缺點就是當 Runner 變多後,要管理建置環境就會變得困難,要確保每台 Runner 都是相同版本也有可能很頭大。不過更大的問題,是如果不同的專案需要不同的建置環境的時候,也有可能會碰到因為版本不相容、而無法共用 Runner 的問題了。
為了解決這些問題,Heresy 後來也開始把 GitLab CI/CD 裡的建置方法,都轉移到 Docker 上了。
在 Linux 環境下,要使用 GitLab Ci/CD + Docker 基本上問題很少,可以很輕鬆地完成。
但是在 Windows 環境下,碰到的問題可就多了…姑且不論之前搞了很久的開發軟體安裝問題,光是的版本相容性問題,就可以把人搞到吐血…
GitLab-Runner 支援性有限
由於在 GitLab-Runner 中要使用 Docker executor(官方文件)的話,會被綁死在他有支援的特定版本作業系統,而且更新得很慢…以已經出了超過半年的 Windows 10 20H2 來說,GitLab-Runner 卻到 14.1.0 才支援…
所以除非拿來當 Runner 的電腦都為了符合 GitLab-Runner 支援的版本不升級,否則就只好放棄使用比較方便的 Docker executor,而是回到 Shell executor(官方文件)、自己來下 Docker 命令了。
Windows Docker 支援性極差
就算扣掉 GitLab-Runner 支援性的問題,Windows Docker 本身的相容性問題也很大…
如果要使用效能較好的 process 隔離模式(參考),那 Windows 的版本必須要和 Windows Docker Image 的版本完全相同才能跑,也就是說如果 Host 已經升級到 Windows 10 21H1 的話,那就不能以 process 模式來跑任何基於 Windows 20H2 的 Docker image 了…
就算自己願意積極一點去用新版的 Windows 基礎映像來重建,微軟本身釋出新版本 Docker Image 的速度也不快,像以目前 21H1 出了兩個月了,官方卻始終沒有釋出對應的基礎映像檔…
所以如果想用 process 隔離模式來跑的話,勢必也得把 Runner 綁在較舊的作業系統版本、直到微軟釋出新版的映像檔上。
如果切換到 Hyper-V 隔離的話,基本上就比較不會有這樣的相容性問題了。但是相對地,Hyper-V 模式下預設只有兩顆 CPU、2GB 記憶體,效能很糟…
想要調製的話,雖然根據官方說明(連結),在設定的介面中應該是可以透過圖形介面調整 Hyper-V 的可用資源的;但是在 Heresy 這邊切換到 Windows container 後就看不到設定的介面了… orz
而同樣的設定雖然可以透過在執行 docker run 的時候,加上「–cpus」和「–memory」(官方文件)來調整,但是由於每台 runner 的配置又不相同,變得不適合寫死在腳本…
Heresy 這邊目前的解決方法,是決定在執行的時候,透過 PowerShell 來抓系統的 CPU 與記憶體的量,然後再來設定 Docker 要使用多少資源。
取得系統 CPU thread 數量的指令是:
(Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
以 GB 為單位、取得記憶體量的指令則是:
(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum /1gb
為了跨以後可以重複使用腳本,這邊是把它寫成一個 GitLab CI 的範本,內容大致如下:
.windows_setting:
variables:
ErrorActionPreference: STOP # make PowerShell can make job fail MaxCPU: 16
MaxMem: 32
before_script:
- "[System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8" # Set as UTF-8
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- $ThreadNum = ( $MaxCPU, ((Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors - 4) | Measure -Min).Minimum
- $MemSize = ( $MaxMem, ([int]((Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum /1gb) - 4) | Measure -Min).Minimum
- $DOCK_CONF = ("--isolation hyperv --cpus=$ThreadNum --memory=" + $MemSize + "g --rm -v " + (Get-Location).path + ":c:code").Split(" ")
- echo "Use $ThreadNum cores and $MemSize GB memory"
可以看到,這裡是透過「before_script」 ,讓 runner 在執行工作前,先跑這些指令。
為了不要讓 runner 占用過多資源,所以這邊事先設定最多使用 16 個 thread、32GB 記憶體。
而為了留一些資源給 host 所實際上,最後使用的 CPU 數量($ThreadNum)是 min( TOTAL_THREAD_NUM – 4, $MaxCPU),算是至少留了 4 個 thread 給 host;記憶體的部分($MemSize),則是 min( MAX_MEMORY – 4, $MaxMem)、至少保留 4GB 給 host。
之後則是把這些數值、包含一般的 Docker 執行參數,都定義成 $DOCKER_CONF 這個變數,方便之後使用。(包括統一把目前的路徑 mount 成 c:code)
不過這邊要注意的是,在 PowerShell 裡面如果是直接用一個字串包含多個用空白區隔的參數,那在給其他程式當參數的時候,是會有問題的;所以這邊需要先透過 Split()、根據空白來拆開來才能用。
而本來的 .gitalb-ci.yml 則可以在 include 前面定義的範本檔案後(參考),把 job 寫成:
build-windows:
extends: .windows_setting
variables:
DOCKER_IMAGE: $DOCKER_IMAGE_PATH/windows/builder:$DOCKER_TAG_POSTFIX
script:
- docker pull $env:DOCKER_IMAGE
- docker run $DOCK_CONF $env:DOCKER_IMAGE msbuild /m /p:Configuration=Release c:code
基本上,就是透過 extends(官方文件)繼承前面定義的「.windows_setting」這個範本後,直接拿 $DOCKER_CONF 當作主要的 docker 執行參數來用了~
如此一來,之後如果有更好的 Docker 參數設定方法,也可以統一修改就好了。
目前這樣的方案算是可以正常運作,不過偶爾會出現「The requested resource is in use
」的錯誤、導致 Docker 無法啟動,就不知道該怎麼辦了…