镜像瘦身10斤:Rust优化攻略

2023年 7月 25日 86.0k 0

我们团队使用K8S来编排Docker,而小的镜像的体积的好处不言而喻,它可以有更快的构建和部署速度、更少的存储空间、更快的镜像传输和下载速度以及更好的可移植性。

但在实际工作中,团队伊始使用的Rust运行时镜像体积非常大(114M),令我感觉不可思议(我之前做过一个Go项目,总共也就30来M),于是下决心优化:
企业微信截图_be2ad7f2-bb4d-48f0-87d0-817b92493a1f.png

运行时镜像

在编写Dockerfile时,选择合适的基础镜像非常重要。常用的Linux系统镜像包括Ubuntu、CentOS和Alpine。其中,Alpine是一个高度精简的轻量级Linux发行版,包含了基本工具,基础镜像只有4.41M。此外,各开发语言和框架都有基于Alpine制作的基础镜像。因此,强烈推荐使用Alpine作为基础镜像。

当然,还有更小的镜像,例如scratch和busybox,读者如果想做极致的优化,可以考虑使用它们。

制作镜像本身并不复杂,对于Rust项目而言,基本上只需要安装libgcc就可以了,镜像体积缩减到5.96M:
企业微信截图_402c9457-3ef3-455a-a153-33ee337d2746.png

后来我为了修改时区,我又额外安装了tzdata,一个时区数据库包,体积稍微大了点,为9.46M:
image.png

镜像代码

以下是最终的Dockerfile代码:

FROM alpine:3.17.3

ENV TZ=Asia/Shanghai

RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories 
    && apk update 
    && apk add --no-cache libgcc tzdata 
    && echo "${TZ}" > /etc/timezone 
    && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime 
    && rm /var/cache/apk/*

镜像使用

这是一个构建Rust镜像的Dockerfile,它分成2部分,上半部分构建出产物二进制文件,下半部分将之前的文件copy到运行时镜像里。

FROM rust:alpine3.16
# This is important, see https://github.com/rust-lang/docker-rust/issues/85
ENV RUSTFLAGS="-C target-feature=-crt-static"
RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
# if needed, add additional dependencies here
RUN apk add --no-cache musl-dev pkgconfig openssl-dev
# set the workdir and copy the source into it
WORKDIR /app
COPY ./ /app
# do a release build
RUN cargo build --release
# RUN strip target/release/search

# use a plain alpine image, the alpine version needs to match the builder
FROM alpine
# if needed, install additional dependencies here
RUN apk add --no-cache libgcc
# copy the binary into the final image
WORKDIR /app
COPY --from=0 /app/target/release/search .
# set the binary as entrypoint
ENTRYPOINT ["./search"]

我们有了上面的镜像以后,后面部分修改为:

FROM dk.uino.cn/runner/rust-runtime-alpine:0.0.3

COPY --from=0 /app/target/release/search .

# set the binary as entrypoint
ENTRYPOINT ["./search"]

但我们不建议在GitLab流水线中这样使用,为什么呢?

上篇文章《提高GitLab CICD效率:Rust编译速度飙升秘籍》提到,Rust编译速度非常缓慢,为解决这个问题,我们在GitLab流水线中加入sccache、mold以及调整CPU来加速。而在流水线中构建镜像(我们用的Buildah,详见这篇探索Buildah:简化Docker镜像构建),并不能充分利用这个优势。

所以,最佳做法是将流水线任务拆分成两步,一步是构建产物,一步是构建镜像,后面这步只将上一步产物复制。比如.gitlab-ci.yml文件:

build:
	stage: build
  script:
    - cargo build --release
  artifacts:
    paths:
      - target/release/$RUST_BIN
    expire_in: 1 day  

docker:
  stage: docker
	variables:
    CI_DOCKERFILE: Dockerfile
    CI_DOCKER_PROJECT: xx # 你有权限推送的方件夹
    CI_DOCKER_REPO: xx-xx # 仓库名
  script:
    - docker_build --build-arg GIT_REVISION=${CI_COMMIT_SHA}

Rust减少二进制文件体积

在 Cargo.toml 文件中添加以下配置:

[profile.release]
codegen-units = 1
strip = true
lto = true
opt-level = "z"

这些配置是用于Rust的发布模式(release mode)的:

  • codegen-units = 1 :这个配置指定在编译期间生成代码的单元数量。它的值为1,表示只生成一个代码单元。通过减少代码单元的数量,可以提高编译速度和减小最终生成的可执行文件的大小。然而,这可能会导致一些性能损失。
  • strip = true :这个配置用于指定是否在编译完成后剥离(strip)可执行文件中的调试符号和其他不必要的信息。剥离可执行文件可以减小其大小,并且可以防止他人通过分析可执行文件来获取敏感信息。设置它,效果与在cargo build之后再显式执行strip -s是一样的。
  • lto = true :这个配置用于启用链接时优化(Link-Time Optimization,简称LTO)。链接时优化是在链接阶段对整个程序进行优化,而不仅仅是单个源文件。通过LTO,编译器可以更好地优化代码,提高最终可执行文件的性能。然而,它会显著增加编译时间和内存占用,有时候对程序性能没有正面影响,所以默认是没有激活的。
  • opt-level = "z":这个配置用于指定优化级别,通常有0、1、2、3、s、z几种。在这里,"z"表示最小化优化,这意味着编译器将尽可能地减小体积,但可能会降低性能。Debug模式,缺省使用0,Release模式缺省是3。
  • 这些配置可以根据你的需求进行调整,以平衡编译速度、可执行文件大小和性能。

    以下是我验证过的一个项目的结果:

    大小 耗时
    原始 15_581_576 3m
    strip 8_796_320 2m53s
    strip+codegen 7_506_032 2m57s
    strip+codegen+lto 6_797_320 3m52s
    strip+codegen+lto+opt-level 5_548_040 2m59s

    使用strip后,体积减少的最多。而后面几个虽然也有效果,但各有缺点,codegen可能增加编译时间(本例中并没有,应该与机器配置有关,Mac上某项目正常编译是1分30秒,加了codegen后是2分钟),lto明显地增加了编译时间,对性能的影响不确定。奇怪的是最后4个一起时间居然又减少了。这个样本可能不太正常。

    综上,我觉得开启strip的最简单有效,对性能没有影响,又减少了相当的体积,再往下优化的几M意义不大。
    由于我们老的项目基本都没有开启这几项优化,所以我在.gitlab-ci.yml文件里build这一步添加strip:

    build
      stage: build
      script:
        - cargo build --release
        - strip -s target/release/$RUST_BIN
    

    总结

    我们通过使用alpine镜像作为基础镜像,减少了Rust的运行时镜像体积,又使用strip命令,减少了Rust构建产物的体积,将最终镜像体积从130M减少到18M左右,基本满足了我们的需求。
    image.png
    image.png

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论