让我们尝试在scratch容器中运行通过Rust创建的二进制文件

我想在scratch容器中运行由Rust编写的二进制文件。

你肯定希望能够在scratch容器中运行由Rust编写的二进制文件吧。

另外,在撰写这篇文章时,我参考了O’Reilly Docker和O’Reilly编程Rust第2版。

我试试看

让我们试着准备以下3个文件,来创建一个Hello World程序。

FROM rust:latest as build

WORKDIR /helloworld
COPY ./helloworld /helloworld
RUN cargo build --release

FROM scratch
COPY --from=build /helloworld/target/release/helloworld /helloworld
CMD [ "/helloworld" ]
fn main() {
    println!("Hello, World!");
}
[package]
name = "helloworld"
version = "0.1.0"
edition = "2021"

[profile.release]
codegen-units = 1
opt-level = 3 
debug = false
strip = "symbols"
debug-assertions = false
lto = true

[dependencies]

然后按照以下步骤启动。

$ docker build -t helloworld .
$ docker run --rm helloworld

然后,会发生以下错误。

exec /helloworld: no such file or directory

为什么呢?

调查

那么我们来用ldd命令查看生成的二进制文件。

# ldd /helloworld/target/release/helloworld
        linux-vdso.so.1 (0x00007fffcc59a000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fd22875e000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd22857d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fd2287e3000)

看起来似乎在glibc上进行了动态连接。
因为scratch容器不包含glibc,所以似乎出现了错误。

Option 1: 运用cc-distroless来解决

让我们尝试在包含glibc的轻量级容器distroless/cc-debian12中运行。我将修改Dockerfile。

FROM rust:latest as build

WORKDIR /helloworld
COPY ./helloworld /helloworld
RUN cargo build --release

FROM gcr.io/distroless/cc-debian12:latest
COPY --from=build /helloworld/target/release/helloworld /helloworld
CMD [ "/helloworld" ]

让我们启动一下。

$ docker build -t cc-helloworld .
$ docker run --rm cc-helloworld
Hello, World!

我成功地把它移动了,毫无问题。

補足: base-debian12 不包含 glibc
顺便提一下,distroless/base-debian12 也不包含 glibc,Dockerfile
FROM rust:latest as build

WORKDIR /helloworld
COPY ./helloworld /helloworld
RUN cargo build –release

FROM gcr.io/distroless/base-debian12:latest
COPY –from=build /helloworld/target/release/helloworld /helloworld
CMD [ “/helloworld” ]

当使用 base-debian12 时,
/helloworld: error while loading shared libraries: libgcc_s.so.1: cannot open shared object file: No such file or directory

会提示缺少 glibc。

解决方案2:使用musl进行构建

如果您决意要在容器中运行,请将其静态链接到单个二进制文件上。如果使用Rust,构建时使用musl非常方便。此外,使用rust:alpine容器进行构建也很方便。

FROM rust:alpine as build

WORKDIR /helloworld
COPY ./helloworld /helloworld
RUN cargo build --release --target=x86_64-unknown-linux-musl

FROM scratch
COPY --from=build /helloworld/target/x86_64-unknown-linux-musl/release/helloworld /helloworld
CMD [ "/helloworld" ]

随后,

$ docker build -t musl-helloworld .
$ docker run --rm musl-helloworld
Hello, World!

当看了今次生成的二进制文件使用ldd时,没有任何问题,在scratch容器上运行正常。

$ ldd /helloworld/target/x86_64-unknown-linux-musl/release/helloworld
        /lib/ld-musl-x86_64.so.1 (0x7f3b10e8e000)

我正在生成一个静态链接的二进制文件,使用musl libc进行链接。也就是说,库被嵌入到二进制文件中。因此可以在scratch容器中运行。

印象

这次生成的cc-helloworld容器大小是27.98MB,musl-helloworld容器大小是440.44KB。
它们都非常轻量。
大家也可以使用distroless或scratch来享受愉快的Docker生活!