.NET 6 的 docker 镜像可以有多小
.NET 6 的 docker 鏡像可以有多小?
Intro
最近寫了一個小玩具,一個命令行調用 HTTP API 的工具,介紹可以參考:動手造輪子 —— dotnet-HTTPie,
最近在使用 System.CommandLine 重構的同時,也在嘗試減少 docker 鏡像的大小,這樣下載鏡像也會比較快一些
Before
之前的做法是在 runtime 容器中安裝一個 dotnet tool,然后鏡像生成出來之后大概是 89.9M,
runtime 的鏡像大小就已經有 87.3M,dockerfile如下:
FROM?mcr.microsoft.com/dotnet/runtime:3.1-alpine?AS?base LABEL?Maintainer="WeihanLi"FROM?mcr.microsoft.com/dotnet/sdk:3.1-alpine?AS?build-env #?dotnet-httpie?version,?docker?build?--build-arg?TOOL_VERSION=0.1.0?-t?weihanli/dotnet-httpie:0.1.0?. ARG?TOOL_VERSION RUN?dotnet?tool?install?--global?dotnet-httpie?--version?${TOOL_VERSION}FROM?base?AS?final COPY?--from=build-env?/root/.dotnet/tools?/root/.dotnet/tools ENV?PATH="/root/.dotnet/tools:${PATH}"最初是基于 dotnetcore 3.1 的,所以用的是 3.1 的鏡像,后面更新到了 6.0,雖然會比 3.1 小一點點,但還是會有 80 多 M,.NET 6.0 runtime 的鏡像有 81.4 M,而一個 nginx 只有 22.8 M,Redis 也只有 32.3M,還是蠻大的
Now
如果有注意 dotnet 鏡像的 tag 的話,你會發現有一類是 runtime-deps,就是運行時必不可少的依賴,但是里面是不包含 sdk 和 runtime 的,這通常用于部署 self-contained 的應用,就是自己包含了運行時所需的所有依賴,拉了一個 .NET 6.0 runtime-deps 的鏡像,大小只有 11.9 M,仿佛看到了希望,也許能夠和 nginx 以及 redis 相媲美了
對于發布 self-contained 應用只需要在發布時指定 --self-contained 并且要指定一個 runtime 信息,官方叫法是 RID(Runtime Identifier),就是要發布平臺的環境信息。
可以使用下面的命令來發布一個 self-contained 應用,因為想作為一個 dotnet-tool 一樣去使用,所以我們指定了 PublishSingleFile 來生成一個單文件應用,另外為了使用指定的 command 我們也指定了 AssemblyName 為我們實際想要使用的 command http
dotnet?publish?./HTTPie/HTTPie.csproj?-c?Release?--self-contained?--use-current-runtime?-p:AssemblyName=http?-p:PublishSingleFile=true來看一下這樣 build 出來的鏡像大小吧,這里我們就不再是安裝 dotnet tool 了,而是直接對源碼進行 build && publish,docker 鏡像如下:
FROM?mcr.microsoft.com/dotnet/runtime-deps:6.0-alpine?AS?base LABEL?Maintainer="WeihanLi"FROM?mcr.microsoft.com/dotnet/sdk:6.0-alpine?AS?build-envWORKDIR?/app COPY?./src?./ COPY?./build/version.props?./Directory.Build.props RUN?dotnet?publish?./HTTPie/HTTPie.csproj?-c?Release?--self-contained?--use-current-runtime?-p:AssemblyName=http?-p:PublishSingleFile=true?-o?./artifactsFROM?base?AS?final COPY?--from=build-env?/app/artifacts?/root/.dotnet/tools ENV?PATH="/root/.dotnet/tools:${PATH}"這里使用發布單文件應用來代替了原來安裝 dotnet-tool 的過程,這樣打包出來的鏡像 77.7 M 比原來小了一些
從 .NET Core 3.0 開始,微軟提供了一個 Trim 選項,可以移除引用的程序集里可能用不到的代碼,我們來嘗試一下,指定 PublishTrimmed 來試一下,使用下面的命令來進行發布
dotnet?publish?./HTTPie/HTTPie.csproj?-c?Release?--self-contained?--use-current-runtime?-p:AssemblyName=http?-p:PublishSingleFile=true?-p:PublishTrimmed=true再來看一下打包出來的鏡像大小,此時已經變成了 33.4 M
已經變之前小了一半,和 redis 已經差不多了,還能不能夠更小呢?
指定 Trim 的時候會有很多警告,這是因為有些方法可能會用反射來使用某些代碼,并沒有直接的依賴關系,此時這種方式就有可能會造成一些問題,所以使用 Trim 的時候如果程序比較復雜需要好好的測試一下以確保沒有問題
.NET 6 在 Preview 4 的時候引入了一個新的功能 .NET 6 Preview 4 Released,針對單文件應用的發布提供了一個壓縮選項,我們可以通過指定 EnableCompressionInSingleFile 來進一步對單文件應用進行壓縮,從而進一步減小文件的大小
dotnet?publish?./HTTPie/HTTPie.csproj?-c?Release?--self-contained?--use-current-runtime?-p:AssemblyName=http?-p:PublishSingleFile=true?-p:PublishTrimmed=true?-p:EnableCompressionInSingleFile=true我們再來看一下現在構建出來的鏡像大小,現在我們的鏡像已經減小到了 26.9M,已經比 redis 小了
這樣基本就達到了我們的預期,是不是還有優化的空間呢
我們通過 dive 來看一下鏡像里的內容,在最后的拷貝的時候,可以看到我們拷貝過去的其實有兩個文件一個是 http,另一個是 http.pdb,pdb 文件其實是不需要的,所以我們在拷貝的時候可以只拷貝 http 就可以了
但是?pdb?文件很小,只有幾十k,所以去掉了以后打包還是有 26.9M,但是鏡像里就只有一個文件了,就很舒適
dive 是一個非常有幫助的工具來查看 docker 鏡像里每一層的內容,在鏡像啟動不起來,鏡像有問題的時候是一個非常好的分析工具
完整的 Dockerfile 如下:
FROM?mcr.microsoft.com/dotnet/runtime-deps:6.0-alpine?AS?base LABEL?Maintainer="WeihanLi"FROM?mcr.microsoft.com/dotnet/sdk:6.0-alpine?AS?build-envWORKDIR?/app COPY?./src?./ COPY?./build/version.props?./Directory.Build.props RUN?dotnet?publish?./HTTPie/HTTPie.csproj?-c?Release?--self-contained?--use-current-runtime?-p:AssemblyName=http?-p:PublishSingleFile=true?-p:PublishTrimmed=true?-p:EnableCompressionInSingleFile=true?-o?./artifactsFROM?base?AS?final COPY?--from=build-env?/app/artifacts/http?/root/.dotnet/tools/http ENV?PATH="/root/.dotnet/tools:${PATH}"最后對我們的 docker 鏡像進行測試
使用類似的方法對一個 hello world 應用測試一下, hello-world?是一個 console,23.4M?,API 是一個?asp.net core web api 應用,34.3M
上傳到?dockerhub 之后,看到的大小會更小一些,docker registry 會對鏡像進行壓縮
More
使用 dotnet publish 而不是使用 dotnet tool 的方式除了大小之外,還有一些別的好處,現在我們發布包到 nuget 的時候往往會有一定的時間才能獲取到這個包,現在更新 docker 鏡像都是手動去做的,因為要等 nuget 上出現這個版本的包以后再進行打包,就不夠自動化,使用 publish 的方式可以更好的自動化地發布
.NET 6 SDK 后續會針對 self-contained 進行一些優化,對于 --self-contained 可以使用 --sc 來代替,一個別名,簡化使用,同時使用 --self-contained 的時候會默認自動使用當前 SDK 的 RID,這樣發布 self-contained 應用就會更加方便了,詳細可以參考 issue:https://github.com/dotnet/sdk/issues/19576
References
https://hub.docker.com/_/microsoft-dotnet-runtime-deps/
https://hub.docker.com/repository/registry-1.docker.io/weihanli/dotnet-httpie/tags?page=1&ordering=last_updated
https://github.com/WeihanLi/dotnet-httpie
https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained?WT.mc_id=DT-MVP-5004222
https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-4/#compression
https://github.com/wagoodman/dive
.NET 6 Preview 4 Released
動手造輪子 —— dotnet-HTTPie
總結
以上是生活随笔為你收集整理的.NET 6 的 docker 镜像可以有多小的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国内最大.NET平台重金招募中 你竟然还
- 下一篇: UOS LoongArch 上成功安装.