百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 博客教程 > 正文

构建多平台的 AOT 容器镜像

connygpt 2024-12-13 12:40 18 浏览

构建多平台的 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 之后的文件大小,具体可以参考

 <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 并且会做单文件的压缩

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

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 如下:

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 架构安装编译必要的文件

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

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 直接使用命令脚本来配置了,可以通过下面的命令来配置

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

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


相关推荐

自学Python,写一个挨打的游戏代码来初识While循环

自学Python的第11天。旋转~跳跃~,我~闭着眼!学完循环,沐浴着while的光芒,闲来无事和同事一起扯皮,我说:“编程语言好神奇,一个小小的循环,竟然在生活中也可以找到原理和例子”,同事也...

常用的 Python 工具与资源,你知道几个?

最近几年你会发现,越来越多的人开始学习Python,工欲善其事必先利其器,今天纬软小编就跟大家分享一些常用的Python工具与资源,记得收藏哦!不然下次就找不到我了。1、PycharmPychar...

一张思维导图概括Python的基本语法, 一周的学习成果都在里面了

一周总结不知不觉已经自学Python一周的时间了,这一周,从认识Python到安装Python,再到基本语法和基本数据类型,对于小白的我来说无比艰辛的,充满坎坷。最主要的是每天学习时间有限。只...

三日速成python?打工人,小心钱包,别当韭菜

随着人工智能的热度越来越高,许多非计算机专业的同学们也都纷纷投入到学习编程的道路上来。而Python,作为一种相对比较容易上手的语言,也越来越受欢迎。网络上各类网课层出不穷,各式广告令人眼花缭乱。某些...

Python自动化软件测试怎么学?路线和方法都在这里了

Python自动化测试是指使用Python编程语言和相关工具,对软件系统进行自动化测试的过程。学习Python自动化测试需要掌握以下技术:Python编程语言:学习Python自动化测试需要先掌握Py...

Python从放弃到入门:公众号历史文章爬取为例谈快速学习技能

这篇文章不谈江流所专研的营销与运营,而聊一聊技能学习之路,聊一聊Python这门最简单的编程语言该如何学习,我完成的第一个Python项目,将任意公众号的所有历史文章导出成PDF电子书。或许我这个Py...

【黑客必会】python学习计划

阅读Python文档从Python官方网站上下载并阅读Python最新版本的文档(中文版),这是学习Python的最好方式。对于每个新概念和想法,请尝试运行一些代码片段,并检查生成的输出。这将帮助您更...

公布了!2025CDA考试安排

CDA数据分析师报考流程数据分析师是指在不同行业中专门从事行业数据搜集、整理、分析依据数据作出行业研究评估的专业人员CDA证书分为1-3级,中英文双证就业面广,含金量高!!?报考条件:满18...

一文搞懂全排列、组合、子集问题(经典回溯递归)

原创公众号:【bigsai】头条号:程序员bigsai前言Hello,大家好,我是bigsai,longtimenosee!在刷题和面试过程中,我们经常遇到一些排列组合类的问题,而全排列、组合...

「西法带你学算法」一次搞定前缀和

我花了几天时间,从力扣中精选了五道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。467.环绕字符串中唯一的子字符串[1](中等)795.区...

平均数的5种方法,你用过几种方法?

平均数,看似很简单的东西,其实里面包含着很多学问。今天,分享5种经常会用到的平均数方法。1.算术平均法用到最多的莫过于算术平均法,考试平均分、平均工资等等,都是用到这个。=AVERAGE(B2:B11...

【干货收藏】如何最简单、通俗地理解决策树分类算法?

决策树(Decisiontree)是基于已知各种情况(特征取值)的基础上,通过构建树型决策结构来进行分析的一种方式,是常用的有监督的分类算法。决策树算法是机器学习中的一种经典算法,它通过一系列的规则...

面试必备:回溯算法详解

我们刷leetcode的时候,经常会遇到回溯算法类型题目。回溯算法是五大基本算法之一,一般大厂也喜欢问。今天跟大家一起来学习回溯算法的套路,文章如果有不正确的地方,欢迎大家指出哈,感谢感谢~什么是回溯...

「机器学习」决策树——ID3、C4.5、CART(非常详细)

决策树是一个非常常见并且优秀的机器学习算法,它易于理解、可解释性强,其可作为分类算法,也可用于回归模型。本文将分三篇介绍决策树,第一篇介绍基本树(包括ID3、C4.5、CART),第二篇介绍Ran...

大话AI算法: 决策树

所谓的决策树算法,通俗的说就是建立一个树形的结构,通过这个结构去一层一层的筛选判断问题是否好坏的算法。比如判断一个西瓜是否好瓜,有20条西瓜的样本提供给你,让你根据这20条(通过机器学习)建立起...