Heresy 這邊自從自己架設 GitLab 來作為開發平台後,就有持續在研究 GitLab CI/CD 這方面的東西。基本上 GitLab CI/CD 在 Linux 環境上搭配 Docker 使用「docker」這個 executor(官方文件)可以跑得不錯,問題也相對少、使用上也相對方便。
但是在 Windows 上,其實要跑 Windows 的容器的話,其實和 Linux 環境比起來,算是相對麻煩不少、問題也相對多的。GitLab Runner 雖然特別設計一個「docker-windows」的 executor(官方文件)來作為執行 Windows 容器的設定,但是實際上使用上也相當麻煩。
也因為各式各樣的問題,所以 Heresy 後來好一段時間其實都是把 Windows Runner 的 executor 設定為「shell」、然後自己透過腳本去呼叫 docker 的指令。
而最近 Heresy 才終於把 Windows Runner 全部由「shell」轉換成「docker-windows」,方便後續的腳本維護。這邊就來稍微紀錄一下吧。
Runner 基本設定
這邊在註冊 runner 的時候,選擇的 executor 就是「docker-windows」(官方文件),代表它是用來執行 Windows Docker 容器的 runner。
至於預設使用的 docker image 基本上就看個人的需求了。
而在註冊過後,個人是建議要去修改 gitlab-runner 的 config.toml
這個設定檔(官方文件),把裡面的「shell
」由預設的「pwsh
」改成「cmd
」或「powershell
」會比較好。
會需要這樣修改的原因,是因為 Windows 提供的基礎 Docker image 裡面預設其實都還沒有安裝 pwsh(PowerShell Core)、所以如果預設 shell 是 pwsh 的話,那可能會遇到不少 docker image 會因為沒有 pwsh 這個指令而無法正確運作。
Heresy 這邊碰到的錯誤訊息如下:
ERROR: Job failed (system failure): Error response from daemon: container XXXX encountered an error during hcs::System::CreateProcess: pwsh -NoProfile -NoLogo -InputFormat text -OutputFormat text -NonInteractive -ExecutionPolicy Bypass -EncodedCommand XXXXX failure in a Windows system call: The system cannot find the file specified. (0x2) (exec.go:72:2s)
而在把 runner 的 shell 設定改為 powershell 後就沒問題了。
不過考慮到微軟在 Windows 11 Insider 27891 中似乎已經把 PowerShell 移除了,所以以後等微軟在基礎影像中內建 pwsh 後,大概又得再修改了。
另外,目前使用 docker-windows 的狀況下,感覺上似乎沒辦法自己設定 builds_dir
、來控制要把專案程式碼放在哪裡?不管是設定成 [[runners]]
下的 builds_dir
參數(參考)、或是透過 [runners.docker]
下的 volumes
來掛載、在執行工作時都會出錯…
所以現階段,大概就只能先透過 Docker 本身的 volume 的機制來做專案資料夾的管理了。而由於 Docker 的這些資料預設都會在系統磁碟機(Windows 預設路徑是 C:\ProgramData\docker
),所以如果怕這些資料大量占用系統磁碟機的話,會建議把 Docker 的資料夾設定到別的磁碟機去。
這部分可以參考《修改、刪除 Windows 版 Docker 的資料夾》。
拿 docker-windows 的 runner 來建置 Docker image
這邊可以參考《Use the Docker executor with Docker socket binding》,使用 docker:windowsservercore-ltsc2022
這個 docker image 來在 docker-windows 的 executor 中執行 docker 指令。(也已經有 ltsc2025 了)
不過要能執行的話,必須要修改 gitlab-runner 的 config.toml
檔案,在 [runners.docker]
下的 volumes
中,加入「"//./pipe/docker_engine://./pipe/docker_engine"
」、讓跑起來的 Docker 容器裡面可以控制 Docker Engine 才行。
另外一提,他給 [[runners]]
設定在 Windows 的夏應該是有問題的…因為如果 volumes
裡面真的寫「"/cache"
」應該會出問題?所以 Heresy 這邊的設定是:
volumes = ["//./pipe/docker_engine://./pipe/docker_engine", "c:\\cache"]
理論上 Runner 的設定只要加上這個就可以了。
之後如果要建置 Windows 的 Docker image 的話,.gitlab-ci.yml
就可以寫成:
build:
image: docker:windowsservercore-ltsc2022
script:
- docker build -t my-docker-image .
沒有其他問題的話,這樣就可以在 Windows Runner 上建置 Windows 的 Docker image 了。
如果要使用 GitLab 內建的 container registry 中的 docker image 的話,則就和 Linux 環境一樣、需要手動執行 docker login
來登入。
這種情況下,.gitlab-ci.yml
可能會寫成:
build:
image: docker:windowsservercore-ltsc2022
variables:
IMAGE_NAME: my_image
CI_IMAGE: $CI_REGISTRY_IMAGE/$IMAGE_NAME:$CI_COMMIT_SHORT_SHA
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
script:
- docker build -t $CI_IMAGE .
- docker push $CI_IMAGE
這邊的登入指令是寫在 before_script
裡面、使用的是官方文件說較安全的寫法(參考):
echo "$CI_REGISTRY_PASSWORD" |
docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
這樣基本上是把 GitLab 產生的臨時性密碼 $CI_REGISTRY_PASSWORD
透過 echo
這個指令餵進 docker login
的標準輸入來完成輸入。
但是這個寫法在 Heresy 這邊 docker-windows 搭配 powershell 的狀況下,是會出現「unauthorized: HTTP Basic: Access denied」的錯誤。
根據 ChatGPT 的說法,這樣的問題可能是 PowerShell 的 echo
輸出格式(例如 BOM)造成的問題。
解決方法是改成:
docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
修改後的 .gitlab-ci.yml
會是:
build:
image: docker:windowsservercore-ltsc2022
variables:
IMAGE_NAME: my_image
CI_IMAGE: $CI_REGISTRY_IMAGE/$IMAGE_NAME:$CI_COMMIT_SHORT_SHA
before_script:
- docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
script:
- docker build -t $CI_IMAGE .
- docker push $CI_IMAGE
這樣基本上應該就沒問題了。
無法使用 Gitlab 的 Container Registry 上的映象檔
前面都是理想狀態,實際要能真正使用,Heresy 還碰到不少問題…
第一個,就是如果要使用的是自己架設的 GitLab 上的 Container Registry 裡面的 docker image 的時候,由於需要認證才能存取,結果就出錯了。
這邊的錯誤還算滿明確的:
- Job failed: failed to pull image
- unauthorized: HTTP Basic: Access denied.
基本上就明確地看的出來,是他沒有辦法正確完成 GitLab 的 Container Registry 的認證。
而參考《Windows Docker runner doesn’t have permissons to pull image from GitLab container registry》這個 issue 的話,可以發現在紀錄中,其實有一行是:
Authenticating with credentials from C:\WINDOWS\system32\config\systemprofile\.docker\config.json
這代表他是根據「C:\WINDOWS\system32\config\systemprofile\.docker\config.json
」這個檔案來做 Container Registry 的認證的。
而實際上如果要是使用 GitLab CI/CD 的工作執行時產生的臨時性 token 來登入的話,他的紀錄應該要顯示:
Authenticating with credentials from job payload (GitLab Registry)
才對。
而解決方法呢,就是把「C:\WINDOWS\system32\config\systemprofile\.docker\config.json
」這個檔案刪除、或是改名,讓他找不到這個檔案就可以了;當它找不到這個檔案的時候,他就會透過工作的 payload 來登入了。(額外參考)
另外一提,這邊的路徑會是這樣包含「systemprofile」是因為 gitlab-runner 這個服務是用系統帳號在跑的關係;如果 gitlab-runner 是用使用者帳號跑的話,則會是「C:\Users\USERNAME\.docker\config.json
」這樣的路徑。
結論
目前大概就是這樣了?
基本上,把 gitlab-runner 的 executor 從「shell」改成「docker-windows」其實應該算是各有優缺點?
好處的話,主要是在很多 CI/CD 的腳本上可以更單純。除了可以讓腳本和 Linux 的一致性更高之外,要執行大量指令的時候,其實也會更方便。
此外,如果有大量變數要傳遞到 docker 容器裡面的話,使用 docker-windows 的時候可以完全不用管、gitlab runner 會自己處理好。
不過相對地,或許是因為「docker-windows」還會先透過「gitlab-runner-helper」這樣的容器來跑前置作業,所以總覺得他的工作初始化階段其實是更久的;所以如果小工作很多的話,花的時間可能會明顯增加。
再來,如果是使用 shell 的形式、自己執行 docker 指令的話,還可以透過角本來設定 docker 的參數;但是透過 docker-windows 的模式的話,基本上就沒有這種彈性了。
所以到底要怎麼玩比較好?基本上應該還是見仁見智、根據不同的需求會有不同的選擇吧。
附註:之前的問題
之前為什麼都用 shell 做 executor?最大的一個原因,就是如果要看執行效能的話,Windows Docker 如果用 process isolation 跑會比較好。
但是早期 Windows Docker 的容性實在很糟,常常 OS 一有大型就沒辦法繼續用 process isolation 來執行舊有映像檔、而舊的 runner 也沒辦法執行新的 image…這個問題是直到 Windows 11 和 Windows Server 2022 推出後,才終於大幅度改善,讓 process isolation 的運作更為簡單(參考)。
否則在映像檔相容性極差的時代,常常會有連 gitlab-runner 提供的 helper 因為更新太慢而都沒辦法跑的狀況…像現在 gitlab-runner-helper 在 Windows 用的還是 x86_64-v18.2.0-servercore21H2,如果是還在 Windows 10 的年代的話,那已經更新到 24H2 的電腦大概就都沒辦法跑了。
另外一點,則是早期 Gitlab 官方文件雖然有說在 Linux 環境可以用 docker:dind
這種 Docker-in-Docker 的容器來在容器內執行 docker 的指令來建立 docker image,但是在 Windows 這種不支援 dind 的狀況下,其實當時 Heresy 是沒看到相關文件說該怎麼做的…不過現在由於 GitLab 官方就有提供相關說明了,所以問題也就算解決了~(實際上應該早就可以這樣做、只是 Heresy 自己不知道而已)