前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >构建多平台的 AOT 容器镜像

构建多平台的 AOT 容器镜像

作者头像
郑子铭
发布2025-01-07 10:55:17
发布2025-01-07 10:55:17
860
举报
文章被收录于专栏:DotNet NB && CloudNative

构建多平台的 AOT 容器镜像

Intro

最近把 dotnet-httpie 做了一些升级改造,移除了 dotnet 6.0/7.0 的支持,只保留 8.0 和 9.0 的支持,于是可以更好地去做 AOT 的支持并且将容器镜像也基于 AOT 来打包,进一步减小了 docker 镜像的大小

Code Changes

因为项目没有那么复杂,代码上的变化比较简单

先来看下项目文件的变化

移除了 net6.0/7.0 之后就可以直接使用 PublishAot 了,另外发现 nuget 包里的内容会有很多其他语言的语言包,所以配置了一下 <SatelliteResourceLanguages>en</SatelliteResourceLanguages> 避免太多的语言包来减小 nuget 包的大小

除了 PublishAot 之外还额外配置了一些 AOT publish 时候的一些配置来进一步减少 publish 之后的文件大小,具体可以参考

代码语言:javascript
复制
  <PropertyGroup Condition="'$(PublishAot)'=='true'">
    <!-- https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/optimizing?WT.mc_id=DT-MVP-5004222 -->
    <OptimizationPreference>Size</OptimizationPreference>
    <!-- https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options?WT.mc_id=DT-MVP-5004222#trimming-framework-library-features -->
    <DebuggerSupport>false</DebuggerSupport>
    <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
    <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
    <EventSourceSupport>false</EventSourceSupport>
    <InvariantGlobalization>true</InvariantGlobalization>
    <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
    <MetadataUpdaterSupport>false</MetadataUpdaterSupport>
    <StackTraceSupport>false</StackTraceSupport>
    <UseSystemResourceKeys>true</UseSystemResourceKeys>
    <TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
    <XmlResolverIsNetworkingEnabledByDefault>false</XmlResolverIsNetworkingEnabledByDefault>
    <MetadataUpdaterSupport>false</MetadataUpdaterSupport>
    <MetricsSupport>false</MetricsSupport>
    <InvariantGlobalization>true</InvariantGlobalization>
    <StripSymbols>true</StripSymbols>
    <IlcGenerateDgmlFile Condition="'$(OS)' == 'Windows_NT'">true</IlcGenerateDgmlFile>
</PropertyGroup>

再来看几个具体的代码变化

有一些使用了条件编译的代码可能可以去掉了

condition compilation

Json 序列化需要使用 Source Generator 的模式来代替原来的写法

http-json

json generator

依赖注入有些方法也需要添加一些 DynamicallyAccessedMembers attribute 来告知编译器需要保留的一些动态依赖

除了上述变更之外还有一个小的改动,这里的改动是使用 primary constructor 的特性,移除了私有字段,直接使用 primary constructor 上的字段,由于在 dotnet 8 中 logging source generator 还不支持引用 primary constructor 的字段,所以这里有一个 NET8_0 的条件编译, dotnet 8 的时候声明一个私有字段以支持 logging source generator

Container Image Changes

看完代码变化我们再来看看 docker 镜像相关的一些变化

原来的 Dockerfile 如下,原来的 docker image 会做一个 self-contained 的 单文件的 publish 并且会做单文件的压缩

代码语言:javascript
复制
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine AS base
LABEL Maintainer="WeihanLi"

FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build-env

WORKDIR /app
COPY ./src/ ./src/
COPY ./build/ ./build/
COPY ./Directory.Build.props ./
COPY ./Directory.Build.targets ./
COPY ./Directory.Packages.props ./
WORKDIR /app/src/HTTPie/
RUN dotnet publish -f net8.0 -c Release --self-contained --use-current-runtime -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true -p:AssemblyName=http -p:TargetFrameworks=net8.0 -o /app/artifacts

FROM base AS final
COPY --from=build-env /app/artifacts/http /root/.dotnet/tools/http
RUN ln -s /root/.dotnet/tools/http /root/.dotnet/tools/dotnet-http
ENV PATH="/root/.dotnet/tools:${PATH}"
ENTRYPOINT ["http"]
CMS ["--help"]

原来的 docker 镜像大小大概是 36+ MB

更新一个版本之后的 docker image

代码语言:javascript
复制
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build-env

# Configure NativeAOT Build Prerequisites 
# https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=linux-alpine%2Cnet8
# for alpine
RUN apk update && apk add clang build-base zlib-dev

WORKDIR /app
COPY ./src/ ./src/
COPY ./build/ ./build/
COPY ./Directory.Build.props ./
COPY ./Directory.Build.targets ./
COPY ./Directory.Packages.props ./
WORKDIR /app/src/HTTPie/
RUN dotnet publish -f net9.0 --use-current-runtime -p:AssemblyName=http -p:TargetFrameworks=net9.0 -o /app/artifacts

FROM alpine
COPY --from=build-env /app/artifacts/http /root/.dotnet/tools/http
RUN ln -s /root/.dotnet/tools/http /root/.dotnet/tools/dotnet-http
ENV PATH="/root/.dotnet/tools:${PATH}"
ENTRYPOINT ["http"]
CMS ["--help"]

更新之后,我们的 docker image 的可执行文件已经是 AOT publish 的产物了,所以 runtime 的镜像可以直接使用 alpine 而无需安装其他的 dotnet runtime 依赖

上面的 dockerfile 是单个 platform 的镜像,如果要在不同的 platform 上使用,比如在苹果的 ARM 电脑上是不能运行 linux/amd64 的,所以接着尝试增加多个平台的支持,目前支持 linux/amd64, linux/arm64 两种架构

要支持交叉编译需要配置 docker driver,要配置 QEMU 模拟多个架构,对于 dockerfile 也需要一些改动,和之前分享的多平台容器镜像相比会更加复杂一些,因为 AOT 发布需要使用到一些平台相关的依赖,微软提供了一些可以帮助交叉编译的一些容器镜像可以简化这一过程,感谢大佬的帮助,感兴趣的朋友也可以查看这个问题和大佬的改造 https://github.com/dotnet/runtime/discussions/110288 因为开始的时候遇到一些错误,错误的以为 alpine 不能 build arm 的支持,中间改成了基于 debian 的 image,所以大佬的改造也是基于 debian image 的,后面经过一些摸索改成了基于 alpine 的版本,因为 alpine 的镜像会更小一些

最终版本的 dockerfile 如下:

代码语言:javascript
复制
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net9.0-cross-arm64-musl AS cross-build-env

FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build-env

COPY --from=cross-build-env /crossrootfs /crossrootfs

ARG TARGETARCH
ARG BUILDARCH

# Configure NativeAOT Build Prerequisites 
# https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=linux-alpine%2Cnet8
# for alpine
RUN apk update && apk add clang build-base zlib-dev

WORKDIR /app

COPY ./src/ ./src/
COPY ./build/ ./build/
COPY ./Directory.Build.props ./
COPY ./Directory.Build.targets ./
COPY ./Directory.Packages.props ./
COPY ./.editorconfig ./

WORKDIR /app/src/HTTPie/

RUN if [ "${TARGETARCH}" = "${BUILDARCH}" ]; then \
      dotnet publish -f net9.0 --use-current-runtime -p:AssemblyName=http -p:TargetFrameworks=net9.0 -o /app/artifacts; \
    else \      
      apk add binutils-aarch64 --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community; \
      dotnet publish -f net9.0 -r linux-musl-arm64 -p:AssemblyName=http -p:TargetFrameworks=net9.0 -p:SysRoot=/crossrootfs/arm64 -p:ObjCopyName=aarch64-alpine-linux-musl-objcopy -o /app/artifacts; \
    fi

FROM alpine

# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.authors="WeihanLi"
LABEL org.opencontainers.image.source="https://github.com/WeihanLi/dotnet-httpie"

COPY --from=build-env /app/artifacts/http /usr/bin/http
RUN chmod +x /usr/bin/http
ENTRYPOINT ["/usr/bin/http"]
CMD ["--help"]

最终 build 出来的镜像大概 12 MB+

第一行代码从微软的交叉编译帮助镜像中 copy 其他架构编译可能用到的文件,并针对 arm 架构安装编译必要的文件

代码语言:javascript
复制
apk add binutils-aarch64 --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community

由于这个 package 还没合并到 main repo 里,所以这里手动指定了 repository 地址

之后我们需要稍微调整下 publish 的命令,需要指定 SysRootObjCopyName 用来帮助找到正确的系统依赖,另外需要注意下基于 alpine 和 debian 的 rid 不同,alpine arm 需要使用 linux-musl-arm64

代码语言:javascript
复制
dotnet publish -f net9.0 -r linux-musl-arm64 -p:AssemblyName=http -p:TargetFrameworks=net9.0 -p:SysRoot=/crossrootfs/arm64 -p:ObjCopyName=aarch64-alpine-linux-musl-objcopy -o /app/artifacts

dockerfile 搞定之后需要配置 QEMU 和 docker driver,之前的介绍是通过 Github Actions 的,这次基于 Azure DevOps 直接使用命令脚本来配置了,可以通过下面的命令来配置

代码语言:javascript
复制
docker run --privileged --rm multiarch/qemu-user-static --reset -p yes
docker buildx create --name container-builder --driver docker-container --driver-opt default-load=true --bootstrap --use

之后 build 并 push 镜像,需要使用 docker buildx build --push 命令并通过 --platform 指定要构建的 platform,这里我们用到的是 linux/amd64/linux/arm64

代码语言:javascript
复制
docker buildx build --push -f Dockerfile --platform="linux/amd64,linux/arm64" --output="type=image" -t weihanli/dotnet-httpie:latest .

完整 build yaml 可以参考:https://github.com/WeihanLi/dotnet-httpie/blob/dev/.azure/pipelines/docker.yml

More

项目比较简单所以改造比较简单,大部分时间花在了研究 AOT 的多平台容器镜像的构建推送上了,希望对构建基于 AOT 的多平台容器镜像有所帮助

AOT 之后 docker 镜像的大小减少了差不多 2/3

Dockerfile: https://github.com/WeihanLi/dotnet-httpie/blob/dev/Dockerfile

build pipeline yaml: https://github.com/WeihanLi/dotnet-httpie/blob/dev/.azure/pipelines/docker.yml

微软 dotnet buildtools docker image:https://github.com/dotnet/dotnet-buildtools-prereqs-docker

References

  • https://github.com/WeihanLi/dotnet-httpie
  • https://hub.docker.com/r/weihanli/dotnet-httpie/tags
  • https://github.com/WeihanLi/dotnet-httpie/compare/0.8.2...0.9.0
  • https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?WT.mc_id=DT-MVP-5004222
  • https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/optimizing?WT.mc_id=DT-MVP-5004222
  • https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options?WT.mc_id=DT-MVP-5004222#trimming-framework-library-features
  • https://github.com/dotnet/runtime/discussions/110288
  • https://github.com/dotnet/dotnet-buildtools-prereqs-docker
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 构建多平台的 AOT 容器镜像
    • Intro
    • Code Changes
    • Container Image Changes
    • More
    • References
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档