社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  docker

.NET 6 的 docker 镜像可以有多小

dotNET跨平台 • 2 年前 • 237 次点击  

.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-env

WORKDIR /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 ./artifacts

FROM 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.pdbpdb 文件其实是不需要的,所以我们在拷贝的时候可以只拷贝 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-env

WORKDIR /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 ./artifacts

FROM 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


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/119649
 
237 次点击