前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >构建最精简的 Rust Docker 镜像

构建最精简的 Rust Docker 镜像

作者头像
niqin.com
发布2022-06-30 16:52:53
4.2K0
发布2022-06-30 16:52:53
举报
文章被收录于专栏:Rust 生态与实践

本文摘选自 Sylvain Kerkour(Bloom.sh 站点的创建者和《黑帽 Rust(Black Hat Rust)》一书作者)的文章 How to create small Docker images for Rust。

构建最精简的 Docker 映像,以用来部署 Rust,将会带来很多益处:不仅有利于安全(减少攻击面),而且还可以缩短部署时间、降低成本(减少带宽和存储),并降低依赖项冲突的风险。

Rust 代码

我们的“应用”相当简单:将构建一个简单的命令行实用程序,用来调用 https://api.myip.com,并打印响应结果。

进行 HTTPS 调用很有趣,因为它需要一个库来与 TLS(通常使用 openssl)交互。但是,为了构建尽可能精简的 Docker 映像,我们需要对我们的程序做静态链接,而 openssl 的静态链接并不是那么容易实现。所以,本文我们将避免使用 openssl,而改用 Rust 生态库 rustls

让我们暂时忽略 Jemalloc

代码语言:javascript
复制
cargo new myip

Cargo.toml

代码语言:javascript
复制
[package]
name = "myip"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1", features = ["derive"] }
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls", "blocking"] }


[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
version = "0.3"

main.rs

代码语言:javascript
复制
use serde::Deserialize;
use std::error::Error;

// Use Jemalloc only for musl-64 bits platforms
#[cfg(all(target_env = "musl", target_pointer_width = "64"))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

#[derive(Deserialize, Debug)]
struct ApiRes {
    ip: String,
}

fn main() -> Result<(), Box<dyn Error>> {
    let res = reqwest::blocking::get("https://api.myip.com")?.json::<ApiRes>()?;

    println!("{}", res.ip);

    Ok(())
}

我们执行 cargo run,看看是否正常运行:

代码语言:javascript
复制
cargo run
     Running `target/debug/myip`
127.0.0.1

使用空镜像 scratch

大小:15.9 MB

为了将 docker 空镜像 scratch 作为基础镜像,我们必须静态地将程序链接到 musl libc,因为 glibcscratch 中不可用。链接 musl libc,可以通过增加编译目标 x86_64-unknown-linux-musl 来实现。

这样做有一个问题,musl 的内存分配器没有进行速度优化,可能会降低应用程序的性能,尤其是在处理高吞吐量的应用程序时。

这就是为什么我们要使用 jemalloc,一个为高并发应用程序设计的内存分配器。

请注意,在使用 jemalloc 时可能会产生错误,因此请注意查看日志 ;)

作为一个数据节点,我已经使用它为数百万个 HTTP 请求提供了服务,没有任何问题。

Dockerfile.scratch

代码语言:javascript
复制
####################################################################################################
## Builder
####################################################################################################
FROM rust:latest AS builder

RUN rustup target add x86_64-unknown-linux-musl
RUN apt update && apt install -y musl-tools musl-dev
RUN update-ca-certificates

# Create appuser
ENV USER=myip
ENV UID=10001

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    "${USER}"


WORKDIR /myip

COPY ./ .

RUN cargo build --target x86_64-unknown-linux-musl --release

####################################################################################################
## Final image
####################################################################################################
FROM scratch

# Import from builder.
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group

WORKDIR /myip

# Copy our build
COPY --from=builder /myip/target/x86_64-unknown-linux-musl/release/myip ./

# Use an unprivileged user.
USER myip:myip

CMD ["/myip/myip"]

让我们构建,以及运行镜像:

代码语言:javascript
复制
docker build -t myip:scratch -f Dockerfile.scratch .
# 省略构建时输出
# ……

docker run -ti --rm myip:scratch
127.0.0.1

使用基础镜像 alpine

大小:21.6MB

Alpine Linux 是以安全为理念的轻量级 Linux 发行版,基于 musl libcbusybox

如果使用 scratch 空镜像不满足需求,并且需要包管理器来安装依赖项,如 chromium 或者 ssh,那么应当使用 alpine 基础镜像。

由于基础镜像 alpine 基于 musl libc,因此它的约束条件与空镜像 scratch 相同,我们需要使用编译目标 x86_64-unknown-linux-musl,以静态链接我们的 Rust 程序。

Dockerfile.alpine

代码语言:javascript
复制
####################################################################################################
## Builder
####################################################################################################
FROM rust:latest AS builder

RUN rustup target add x86_64-unknown-linux-musl
RUN apt update && apt install -y musl-tools musl-dev
RUN update-ca-certificates

# Create appuser
ENV USER=myip
ENV UID=10001

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    "${USER}"


WORKDIR /myip

COPY ./ .

RUN cargo build --target x86_64-unknown-linux-musl --release

####################################################################################################
## Final image
####################################################################################################
FROM alpine

# Import from builder.
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group

WORKDIR /myip

# Copy our build
COPY --from=builder /myip/target/x86_64-unknown-linux-musl/release/myip ./

# Use an unprivileged user.
USER myip:myip

CMD ["/myip/myip"]

让我们构建,以及运行镜像:

代码语言:javascript
复制
docker build -t myip:alpine -f Dockerfile.alpine .
# 省略构建时输出
# ……

docker run -ti --rm myip:alpine
127.0.0.1

使用基础镜像 buster-slim

大小:79.4MB

最后一个例子,我们将使用基础镜像 debian:buster-slim 作为基本。由于 Debian 基于 glibc,我们不再需要使用编译目标 x86_64-unknown-linux-musl

Dockerfile.debian

代码语言:javascript
复制
####################################################################################################
## Builder
####################################################################################################
FROM rust:latest AS builder

RUN update-ca-certificates

# Create appuser
ENV USER=myip
ENV UID=10001

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    "${USER}"


WORKDIR /myip

COPY ./ .

# We no longer need to use the x86_64-unknown-linux-musl target
RUN cargo build --release

####################################################################################################
## Final image
####################################################################################################
FROM debian:buster-slim

# Import from builder.
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group

WORKDIR /myip

# Copy our build
COPY --from=builder /myip/target/release/myip ./

# Use an unprivileged user.
USER myip:myip

CMD ["/myip/myip"]

让我们构建,以及运行镜像:

代码语言:javascript
复制
docker build -t myip:debian -f Dockerfile.debian .
# 省略构建时输出
# ……

docker run -ti --rm myip:debian
127.0.0.1

结论

代码语言:javascript
复制
docker images
REPOSITORY    TAG           IMAGE ID       CREATED          SIZE
myip          scratch       795604e74501   9 minutes ago    15.9MB
myip          alpine        9a26400587a2   2 minutes ago    21.6MB
myip          debian        c388547b9486   12 seconds ago   79.4MB

虽然本文我们聚焦于 Docker,但是如果镜像对您来说仍然太大,并且您知道自己在做什么,那么请参阅这篇文章,还有一些技巧可以将 Rust 可执行文件的大小进一步精简。

例如,在 Cargo.toml 文件中:

代码语言:javascript
复制
[profile.release]
lto = true
codegen-units = 1

然后,在执行 cargo build 命令后,在 Dockerfile 文件中增加:

代码语言:javascript
复制
RUN strip -s /myip/target/release/myip

现在,大小如下:

代码语言:javascript
复制
docker images
REPOSITORY    TAG           IMAGE ID       CREATED          SIZE
myip          scratch       de26b0460262   17 minutes ago   4.2MB
myip          alpine        4188ccc82662   6 minutes ago    9.81MB
myip          debian        0eefb58278a8   4 seconds ago    72.8MB

谢谢您的阅读!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust 生态与实践 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Rust 代码
    • Cargo.toml
      • main.rs
      • 使用空镜像 scratch
        • Dockerfile.scratch
          • 让我们构建,以及运行镜像:
          • 使用基础镜像 alpine
            • Dockerfile.alpine
            • 使用基础镜像 buster-slim
              • Dockerfile.debian
              • 结论
              相关产品与服务
              容器镜像服务
              容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档