製作一個能構建 dotnet AOT 的 gitlab runner 的 Debian docker 鏡像

我的需求是需要有一個能夠構建出 dotnet 的 AOT 包的環境,要求這個環境能解決 glibc 兼容依賴的問題,能打出來 x64 和 arm64 的 AOT 的包,且能夠運行 gitlab runner 對接自動構建

需求

以下是我列舉的需求

  • 支持製作能在 UOS 系統和麒麟系統上運行的包
  • 支持製作出來的包是 AOT 版本的
  • 可以使用 gitlab runner 對接自動構建

開始之前必須說明的是,對於 dotnet 應用來說,如果不需要 AOT 的話,完全可以在 Windows 上構建出其他 Linux 系統和其他平臺適用的應用。僅僅只是在 AOT 下,強依賴平臺構建時,纔有需要在對應的系統平臺構建

製作方法

我製作的 docker 的 Dockerfile 是基於 debian:buster-slim 打上負載的

FROM debian:buster-slim

爲了提升一點拉取速度,我換成國內的源,使用的是阿里的源

RUN rm /etc/apt/sources.list
COPY sources.list /etc/apt/sources.list
RUN apt-get update

這裏的 sources.list 的代碼是從 debian鏡像_debian下載地址_debian安裝教程-阿里巴巴開源鏡像站 抄的,代碼如下

deb http://mirrors.aliyun.com/debian/ buster main non-free contrib
deb-src http://mirrors.aliyun.com/debian/ buster main non-free contrib
deb http://mirrors.aliyun.com/debian-security buster/updates main
deb-src http://mirrors.aliyun.com/debian-security buster/updates main
deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib
deb-src http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib

爲了交叉構建,同時構建出 ARM64 的 AOT 的 dotnet 應用,我根據 Cross-compilation - .NET - Microsoft Learn 的文檔安裝上必要的負載

RUN dpkg --add-architecture arm64
RUN apt update

RUN apt-get install libicu-dev -y
RUN apt-get install libssl-dev -y
RUN apt-get install wget -y
RUN apt-get install clang llvm -y
RUN apt-get install gcc-aarch64-linux-gnu -y
RUN apt-get install binutils-aarch64-linux-gnu -y
RUN apt-get install zlib1g-dev -y
RUN apt-get install zlib1g-dev:arm64 -y

爲了方便調試和對接 gitlab runner 我還加上了 git 和 vim 工具

RUN apt-get install vim -y
RUN apt-get install git -y

RUN apt-get clean

到這一步,就完成了 docker image 裏面的基礎部分了,現在的 Dockerfile 的代碼如下

FROM debian:buster-slim
WORKDIR /root

RUN rm /etc/apt/sources.list
COPY sources.list /etc/apt/sources.list
RUN apt-get update

RUN dpkg --add-architecture arm64
RUN apt update

RUN apt-get install libicu-dev -y
RUN apt-get install libssl-dev -y
RUN apt-get install wget -y
RUN apt-get install clang llvm -y
RUN apt-get install gcc-aarch64-linux-gnu -y
RUN apt-get install binutils-aarch64-linux-gnu -y
RUN apt-get install zlib1g-dev -y
RUN apt-get install zlib1g-dev:arm64 -y

RUN apt-get install vim -y
RUN apt-get install git -y

RUN apt-get clean

接着到 dotnet 官網 下載 dotnet 8 和 dotnet 6 的 sdk 壓縮包,本文這裏使用的是自己解壓縮的方式。換成命令方式安裝也可以,只是命令方式拉取的速度可能不如先下載壓縮包的方式,且下載壓縮包可以方便多次重新構建,在 Dockerfile 不斷需要修改時,使用壓縮包可以省去多次修改之後的重新構建時的拉取時間

本文這裏採用的是下載壓縮包的方式,下載到 dotnet-sdk-6.0.421-linux-x64.tar.gz 和 dotnet-sdk-8.0.204-linux-x64.tar.gz 這兩個壓縮包。如果大家下載失敗,或者沒有網速的話,可以郵件給我,讓我用網盤發給你。一般情況下在國內都能拉取成功,因爲微軟幫忙提供了全球 CDN 了,下載速度在我這裏還是很快的。下載 dotnet 6 版本僅僅只是爲了讓我的構建工具正常工作而已,屬於可選項

下載完成 dotnet 的壓縮包,即可使用 Dockerfile 的 ADD 命令將壓縮包解壓縮到 docker image 裏的某個文件夾裏面,如下面代碼

WORKDIR /root
ADD dotnet-sdk-6.0.421-linux-x64.tar.gz ./dotnet
ADD dotnet-sdk-8.0.204-linux-x64.tar.gz ./dotnet

解壓縮完成之後,配置環境變量等,讓全局可以使用 dotnet 命令

ENV DOTNET_ROOT="/root/dotnet"
ENV PATH="${PATH}:${DOTNET_ROOT}:${DOTNET_ROOT}/tools"
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN ln -s /root/dotnet/dotnet /usr/bin/dotnet

完成以上步驟之後,一個特別簡單的 dotnet 構建 Dockerfile 已經完成了,接下來一步則是配置 gitlab runner 的步驟。我將參考 gitlab runner 官方安裝文檔 進行配置,只是過程稍微取巧

先根據 Install GitLab Runner manually on GNU/Linux - GitLab 提供的方法,拉取 GitLab Runner 的二進制壓縮包,本文這裏是需要下載 Linux x86-64 版本,當前的下載鏈接是 https://s3.dualstack.us-east-1.amazonaws.com/gitlab-runner-downloads/latest/binaries/gitlab-runner-linux-amd64

下載鏈接可能會有變更,還請大家重新參考 Install GitLab Runner manually on GNU/Linux - GitLab 文檔,找到更新的下載 Linux x86-64 版本的下載地址

完成下載之後,通過 COPY 命令拷貝到 docker image 裏

COPY gitlab-runner-linux-amd64 /usr/share/gitlab/gitlab-runner

RUN chmod +x /usr/share/gitlab/gitlab-runner

完成以上步驟之後需要對 GitLab Runner 進行配置。本文這裏採用取巧的方式,即先將 GitLab Runner 運行起來,配置完成之後,存放配置文件,再將配置文件打入到 docker image 裏面,後續就只需啓動 docker image 即可

具體的步驟是先將當前的 Dockerfile 構建且運行。我這裏使用的是 podman 工具,如果大家使用的是 docker desktop 的話,只需將 podman 命令換成 docker 命令即可,其他參數相同

// 先 cd 到 Dockerfile 所在的文件夾,再執行以下命令
podman build -t t1 .

當前的 Dockerfile 文件的代碼如下

FROM debian:buster-slim
WORKDIR /root

RUN rm /etc/apt/sources.list
COPY sources.list /etc/apt/sources.list
RUN apt-get update

RUN dpkg --add-architecture arm64
RUN apt update

RUN apt-get install libicu-dev -y
RUN apt-get install libssl-dev -y
RUN apt-get install wget -y
RUN apt-get install clang llvm -y
RUN apt-get install gcc-aarch64-linux-gnu -y
RUN apt-get install binutils-aarch64-linux-gnu -y
RUN apt-get install zlib1g-dev -y
RUN apt-get install zlib1g-dev:arm64 -y

RUN apt-get install vim -y
RUN apt-get install git -y

RUN apt-get clean

ADD dotnet-sdk-6.0.421-linux-x64.tar.gz ./dotnet
ADD dotnet-sdk-8.0.204-linux-x64.tar.gz ./dotnet
ENV DOTNET_ROOT="/root/dotnet"
ENV PATH="${PATH}:${DOTNET_ROOT}:${DOTNET_ROOT}/tools"
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN ln -s /root/dotnet/dotnet /usr/bin/dotnet

COPY gitlab-runner-linux-amd64 /usr/share/gitlab/gitlab-runner

RUN chmod +x /usr/share/gitlab/gitlab-runner

再將打包好的 docker image 運行,運行時記得掛載上文件夾,用於將 docker 裏面的文件傳輸到主機

// 提前創建好 C 盤的 lindexi 的 wsl 文件夾,你換成自己的文件夾也可以
podman run -i -t -v /mnt/c/lindexi/wsl:/etc/gitlab-runner t1

以上代碼的 /mnt/c/lindexi/wsl 是我自己的 C:\lindexi\wsl 文件夾,這是我提前建立好的空文件夾。大家換成自己的文件夾也可以,如果用 docker desktop 的話,需要看一下是否運行在 wsl 上,如果不在的話,也許需要換成 Windows 下的路徑表示方法,相信這一步難不倒大家的

進入之後,即可使用 /usr/share/gitlab/gitlab-runner 命令進行註冊,具體的註冊步驟如下

本文使用註冊 GitLab 組 作爲例子,註冊單個項目的步驟也類似,詳細請參閱 https://docs.gitlab.com/runner/configuration/ 文檔。先進入到 GitLab 的 組 的 Runner 配置界面裏面,點擊 New group runner 按鈕

點擊之後,進入配置界面。由於這是一個特殊的構建方式,我推薦寫上 gitlab 的 runner tag 項,我這裏寫的是 debian-dotnet-docker 標記。這裏的標記需要和 git 的 tag 區分哦,這是兩個完全不相同的東西

點擊 Create runner 按鈕,即可進入到創建配置命令界面,拷貝其配置命令參數,如我這裏的是

gitlab-runner register
  --url https://gitlab.lindexi.com
  --token glrt-HbCpfssbPSFqR_xVtxLX

於是我在運行起來的 docker 命令行裏面輸入以下命令用於註冊

/usr/share/gitlab/gitlab-runner register --url https://gitlab.lindexi.com --token glrt-HbCpfssbPSFqR_xVtxLX

輸入之後,一路都是回車下一步,除了執行命令部分可選使用 shell 之外。完成之後再使用 /usr/share/gitlab/gitlab-runner run 命令運行起來試試,如果能夠運行成功,且在 gitlab 的 runner 頁面裏面能夠看到運行起來的 runner 則證明成功。否則還請自行調試哈,我也不熟悉

完成之後即可愉快退出 docker 環境,此時即可在掛載到 /etc/gitlab-runner 的文件夾裏面,即本文的 C:\lindexi\wsl 文件夾裏面看到配置文件,一般是 config.toml 文件

在上述步驟完成之後,咱可以取出來掛載的文件夾,如我這裏的 C:\lindexi\wsl 文件夾,將其拷貝到 Dockerfile 文件所在的文件夾裏面,用於編寫 Dockerfile 拷貝到 /etc/gitlab-runner 文件夾裏面,如此製作出來的 docker image 將會帶上已經註冊的 gitlab runner 信息

COPY wsl /etc/gitlab-runner

接着再執行安裝命令,以及設置入口爲 gitlab-runner run 即可

RUN /usr/share/gitlab/gitlab-runner install --user=root --working-directory=/root/.local/share/gitlab

ENTRYPOINT ["/usr/share/gitlab/gitlab-runner", "run"]

完成以後的 Dockerfile 文件如下

FROM debian:buster-slim
WORKDIR /root

RUN rm /etc/apt/sources.list
COPY sources.list /etc/apt/sources.list
RUN apt-get update

RUN dpkg --add-architecture arm64
RUN apt update

RUN apt-get install libicu-dev -y
RUN apt-get install libssl-dev -y
RUN apt-get install wget -y
RUN apt-get install clang llvm -y
RUN apt-get install gcc-aarch64-linux-gnu -y
RUN apt-get install binutils-aarch64-linux-gnu -y
RUN apt-get install zlib1g-dev -y
RUN apt-get install zlib1g-dev:arm64 -y

RUN apt-get install vim -y
RUN apt-get install git -y

RUN apt-get clean

ADD dotnet-sdk-6.0.421-linux-x64.tar.gz ./dotnet
ADD dotnet-sdk-8.0.204-linux-x64.tar.gz ./dotnet
ENV DOTNET_ROOT="/root/dotnet"
ENV PATH="${PATH}:${DOTNET_ROOT}:${DOTNET_ROOT}/tools"
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN ln -s /root/dotnet/dotnet /usr/bin/dotnet

COPY gitlab-runner-linux-amd64 /usr/share/gitlab/gitlab-runner

RUN chmod +x /usr/share/gitlab/gitlab-runner

COPY wsl /etc/gitlab-runner

RUN /usr/share/gitlab/gitlab-runner install --user=root --working-directory=/root/.local/share/gitlab

ENTRYPOINT ["/usr/share/gitlab/gitlab-runner", "run"]

RUN mkdir /root/build
WORKDIR /root/build

嘗試構建此 Dockerfile 文件

// 先 cd 到 Dockerfile 所在的文件夾,再執行以下命令
podman build -t t1 .

接着掛載到後臺運行

podman container run -v nuget_global:/root/.nuget/packages -v nuget_cache:/root/.local/share/NuGet -v gitlabrunner:/root/.local/share/gitlab -d t1

以上命令的 -v nuget_global:/root/.nuget/packages -v nuget_cache:/root/.local/share/NuGet -v gitlabrunner:/root/.local/share/gitlab 屬於可選的參數,用來掛載 nuget 緩存等內容,解決 docker 每次重啓都會丟失緩存文件,提升重啓 docker 之後的構建速度,減少重複拉取 nuget 包

完成以上步驟之後,就已經完成了製作一個能構建 dotnet AOT 的 gitlab ruuner 的 Debian docker 鏡像

可以嘗試在自己的項目裏面,編寫 .gitlab-ci.yml 文件,指定到這個運行起來的 docker image 上運行,以下是我的測試使用的 .gitlab-ci.yml 文件代碼

stages:
  - build

BuildLinuxX64InDocker:
  stage: build
  script:
    - 'dotnet run publish -p:PublishAot=true -c Release -r linux-x64'
  tags:
    - docker-uos

BuildLinuxArm64InDocker:
  stage: build
  script:
    - 'dotnet run publish -p:PublishAot=true -c Release -r linux-arm64'
  tags:
    - docker-uos 

如果能夠構建成功,且構建出 linux-x64 和 linux-arm64 的 dotnet 可執行文件,則表示成功。否則還請自行根據輸出的錯誤信息修復

踩坑記錄

爲什麼不在 WSL 裏面構建

核心原因是 WSL 裏面的 glibc 版本過於新,使用 ldd --version 命令可以看到的輸出如下

ldd (GNU libc) 2.36

而麒麟的 Desktop-V10-SP1 版本的 glibc 是 2.31 版本,更慘的 UOS 20.1050.11068.102 版本的 glibc 是 2.28 版本,都低於 WSL 裏面的版本

這就意味着在 WSL 裏面構建出來的應用將無法在以上的兩個系統上運行

這就是爲什麼使用 debian:buster-slim 的原因。當前我拉取的 debian:buster-slim 的 docker image id 是 6d0d34a48ee1 的版本。通過 cat /etc/debian_version 可以看到在此版本里面帶的是 debian 10.13 版本

再通過 ldd --version 命令行獲取的 glibc 版本信息,可以看到帶的是 2.28 版本,剛好與 UOS 20.1050.11068.102 版本的 glibc 版本相同,低於麒麟的 Desktop-V10-SP1 的 glibc 版本

因此在此 debian:buster-slim 裏面 AOT 構建出來的包可以同時在 UOS 20.1050.11068.102 和麒麟的 Desktop-V10-SP1 版本運行

debian buster-backports Release does not have a Release file

開始國內源使用了阿里的,結果遇到以下錯誤內容

E: The repository 'http://mirrors.aliyun.com/debian buster-backports Release' does not have a Release file.
Error: building at STEP "RUN apt update": while running runtime: exit status 100

重新參考了 替換docker容器默認的debian鏡像 - OrcHome 博客,結果依然配置失敗。核心原因是配置的版本不正確

我當前使用的是 debian 是 10.13 版本,需要根據 debian鏡像_debian下載地址_debian安裝教程-阿里巴巴開源鏡像站 教程文檔,更新對應的 debian 10.x (buster) 的配置

我是如何知道 debian 版本的,我通過運行鏡像,輸入 cat /etc/debian_version 命令獲取到版本

No system certificates available

完成配置阿里的源,遇到以下的錯誤內容

W: https://mirrors.aliyun.com/debian/dists/buster/InRelease: No system certificates available. Try installing ca-certificates.

原因是 ca-certificates 沒有提前安裝,可以在切換爲國內源之前,安裝好。安裝方法可參閱 修復 Debian 安裝 dotnet 失敗 depends on ca-certificates

由於我這裏不需要關注安全性問題,更簡單的方法是將 https 全部更換爲 http 即可

安裝 dotnet tool 失敗

執行任何的 dotnet tool install 都會提示如下錯誤

Unhandled exception: System.IO.FileNotFoundException: Unable to find the specified file.
   at Interop.Sys.GetCwdHelper(Byte* ptr, Int32 bufferSize)
   at Interop.Sys.GetCwd()
   at Microsoft.DotNet.Cli.ToolPackage.ToolPackageDownloader..ctor(IToolPackageStore store, String runtimeJsonPathForTests)
   at Microsoft.DotNet.ToolPackage.ToolPackageFactory.CreateToolPackageStoresAndDownloader(Nullable`1 nonGlobalLocation, IEnumerable`1 additionalRestoreArguments)
   at Microsoft.DotNet.Tools.Tool.Update.ToolUpdateLocalCommand..ctor(ParseResult parseResult, IToolPackageDownloader toolPackageDownloader, IToolManifestFinder toolManifestFinder, IToolManifestEditor toolManifestEditor, ILocalToolsResolverCache localToolsResolverCache, IReporter reporter)
   at Microsoft.DotNet.Tools.Tool.Update.ToolUpdateCommand..ctor(ParseResult result, IReporter reporter, ToolUpdateGlobalOrToolPathCommand toolUpdateGlobalOrToolPathCommand, ToolUpdateLocalCommand toolUpdateLocalCommand)
   at Microsoft.DotNet.Cli.ToolUpdateCommandParser.<>c.<ConstructCommand>b__14_0(ParseResult parseResult)
   at System.CommandLine.Invocation.InvocationPipeline.Invoke(ParseResult parseResult)
   at Microsoft.DotNet.Cli.Program.ProcessArgs(String[] args, TimeSpan startupTime, ITelemetry telemetryClient)

暫時沒有找到可用方法,只能繞路

我在 windows 下將所需工具下載下來,然後通過拷貝進入的方式即可完全安裝

當然,在本文例子裏面,我沒有加上我所使用的工具

在 gitlab 構建腳本找不到 dotnet 命令

在命令行裏面,可以使用 dotnet 命令,但是在 .gitlab-ci.yml 文件裏面編寫的腳本找不到 dotnet 命令

加上如下配置到 Dockerfile 即可

RUN ln -s /root/dotnet/dotnet /usr/bin/dotnet

以上命令是對 dotnet 建立鏈接,如此即可讓全局可以使用 dotnet 命令

爲什麼使用 podman 工具

原因是在 windows 下的 docker desktop 是收費的,於是我用平替的 podman 工具

還原速度過慢

由於 docker 本身是不帶持久化存儲文件,只有通過掛載本機存儲的方式,才能讓 docker 裏面的文件持久化存放

還原速度過慢的問題,是因爲初始化時沒有任何的 NuGet 緩存,導致需要大量拉取,從而導致拉取過慢

根據 How to manage the global packages, cache, temp folders in NuGet - Microsoft Learn 官方文檔說明,獲取到默認的緩存路徑,使用如下命令將緩存路徑掛載到本機

-v nuget_global:/root/.nuget/packages -v nuget_cache:/root/.local/share/NuGet

我這裏掛載寫的是相對路徑,如 nuget_global 等路徑,相對路徑在 podman 下將會存放到 wsl 裏面,詳細請看 在 windows 上運行的 podman 默認的掛載相對路徑是什麼

爲什麼代碼倉庫路徑不掛載

如上述還原速度過慢原因,由於 docker 本身是不帶持久化存儲文件,只有通過掛載本機存儲的方式,才能讓 docker 裏面的文件持久化存放。有些夥伴認爲將代碼倉庫路徑也進行本機掛載,可以減少拉取代碼倉庫的時間。實際上這麼做可能帶來的後果是開啓多 docker 容器時,出現構建過程中的相互影響問題

拉取代碼倉庫時,大部分時間都是拉取內網的,且隻影響容器的重啓後的首次拉取。因此掛在代碼倉庫不是必要的

掛載代碼倉庫可能受到 Windows 自帶殺毒影響,導致 llvm-objcopy 這一步失敗,大概的錯誤信息如下

llvm-objcopy: failed to open xx.dbg Input/output error.

/root/.nuget/packages/microsoft.dotnet.ilcompiler/8.0.4/build/Microsoft.NETCore.Native.targets(379,5): error MSB3073: The command ""llvm-objcopy" --only-keep-debug xx xx exited with code 1.

解決方法是要麼不掛載,要麼在 Windows 自帶殺毒加白名單

如何使用交叉編譯

由於我缺少 ARM64 的機器,或者準確來說我缺少一臺可以撐住構建的有性能的 ARM64 的機器,我期望能夠在原有的 linux-x64 機器上構建出 ARM64 的應用。於是我就需要使用到交叉編譯技術,通過此技術我就可以在 linux-x64 的機器上構建出 linux-arm64 的應用

參考 Cross-compilation - .NET - Microsoft Learn 的文檔安裝上必要的負載,如下面的 docker 代碼,即可在 debian 的 x64 系統上構建出 ARM64 的 dotnet 的 AOT 應用

RUN dpkg --add-architecture arm64
RUN apt update

RUN apt-get install libicu-dev -y
RUN apt-get install libssl-dev -y
RUN apt-get install wget -y
RUN apt-get install clang llvm -y
RUN apt-get install gcc-aarch64-linux-gnu -y
RUN apt-get install binutils-aarch64-linux-gnu -y
RUN apt-get install zlib1g-dev -y
RUN apt-get install zlib1g-dev:arm64 -y

在進行 dotnet 發佈時,將在 dotnet 裏面自動根據 -r 參數自動執行交叉編譯,如下面命令

dotnet publish -p:PublishAot=true -c Release -r linux-arm64

相關文檔請看 runtime/docs/workflow/building/libraries/cross-building.md at main · dotnet/runtimeruntime/docs/workflow/building/coreclr/cross-building.md at main · dotnet/runtime

爲什麼不使用 gitlab-runner start 命令

因爲實際測試 gitlab-runner start 之後沒有真的執行,如下面代碼

RUN /usr/share/gitlab/gitlab-runner start

添加之後運行 docker image 也不會有 gitlab runner 上線

如果換成下面的代碼,則啓動 docker image 之後立刻退出

ENTRYPOINT ["/usr/share/gitlab/gitlab-runner", "start"]

實際測試只有以下代碼符合預期

ENTRYPOINT ["/usr/share/gitlab/gitlab-runner", "run"]

找不到 runner 機器或找錯

先調查是否 dotnet 配置 Gitlab 的 CI 找不到 Runner 或找錯的可能原因 提及的問題

排除之後,記得查看是否帶上了 tags 和 runner 在 gitlab 上配置正確且相同的

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章