Rustで作ったバイナリをscratchコンテナで動かそう

# Rustで作ったバイナリをscratchコンテナで動かしたい
Rustで作ったバイナリ、scratchコンテナで動かしたいですよね。

なお、この記事を書くにあたって<a target="_blank" href="https://www.amazon.co.jp/dp/4873117763?&linkCode=ll1&tag=nishidemasami-22&linkId=7b0a0e16eab9786a7b21bcf0d7c4e768&language=ja_JP&ref_=as_li_ss_tl" rel="noreferrer" >O'Reilly Docker</a>と<a target="_blank" href="https://www.amazon.co.jp/dp/4873119782?&linkCode=ll1&tag=nishidemasami-22&linkId=e2ff2c051967cc737eca98a12c4de66d&language=ja_JP&ref_=as_li_ss_tl" rel="noreferrer" >O'Reilly プログラミングRust 第2版</a>を参考にしました。

# やってみる
試しに以下の3ファイルを用意してHello Worldを作ってみましょう。
```dockerfile:Dockerfile
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" ]
```
```rust:helloworld/src/main.rs
fn main() {
    println!("Hello, World!");
}
```
```toml:helloworld/Cargo.toml
[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]
```
そして以下のように起動します。
```bash
$ docker build -t helloworld .
$ docker run --rm helloworld
```
すると、以下のようなエラーが発生します。
```
exec /helloworld: no such file or directory
```
なぜなのでしょうか？

# 調査
では今回生成されたバイナリをlddで見てみます。
```bash
# 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を修正します。
```dockerfile: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" ]
```
起動してみましょう。
```bash
$ docker build -t cc-helloworld .
$ docker run --rm cc-helloworld
Hello, World!
```
無事に動かせました。

:::note warn 
### 補足: base-debian12はglibcを含んでいない
ちなみに`distroless/base-debian12`はglibcを含んでいないため、
```dockerfile: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でビルドする
意地でもscratchコンテナで動かしたい場合、単一バイナリに静的リンクさせます。Rustならmuslでビルドすると便利です。また、ビルドには`rust:alpine`コンテナを使用すると便利です。
```dockerfile:Dockerfile
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" ]
```
すると、
```bash
$ docker build -t musl-helloworld .
$ docker run --rm musl-helloworld
Hello, World!
```
無事、scratchコンテナで動きました。
今回生成されたバイナリをlddで見てみると、
```bash
$ 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ライフを！