Rustで作ったバイナリをscratchコンテナで動かしたい
Rustで作ったバイナリ、scratchコンテナで動かしたいですよね。
なお、この記事を書くにあたって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を含んでいないので、エラーが発生しているようです。
解決策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を含んでいないため、
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でビルドする
意地でもscratchコンテナで動かしたい場合、単一バイナリに静的リンクさせます。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!
無事、scratchコンテナで動きました。
今回生成されたバイナリをlddで見てみると、
$ 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ライフを!