59
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RustAdvent Calendar 2020

Day 22

RustのさまざまなDockerfileのビルド時間を比較してみた

Last updated at Posted at 2020-12-21

はじめに

Rustで書かれたアプリケーションをコンテナイメージとしてビルドする際にDockerfileの記述方法にいくつか書き方があったので、それぞれについてイメージのビルド時間の比較してみました。

背景

というのも、Rustはアプリケーションのビルド時間が長いためDockerfileの書き方によって開発効率を悪くしてしまう場合があります。

例えば、Rustで作ったアプリケーションをKubernetesで動かす場合を想定します。ローカルでのアプリケーション開発にskaffoldを利用するとします。
skaffoldはコードの変更を検知し、イメージの再ビルドを行い、Kubernetes上にアプリケーションを再デプロイします。
このとき、Rust特有のビルド時間が長い問題にぶち当たります。ビルド時間がながいことによって、コードの変更に対してイメージの再ビルドが追いつかないのです。

上記は一例ですが、どうせならビルドの速いDockerfileを書きたいよねっということで、いくつか比較してみました。

方法

今回は、コードの変更によって生じるイメージのビルド時間を測定し、それらを比較します。ビルドイメージサイズに関しては特に気にしてません。またcargo workspaceでの開発も想定していません。

serderocketを使ったアプリを想定します。Cargo.tomlmain.rsは下記のようにしています。

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rocket = { git = "https://github.com/SergioBenitez/Rocket" }
#[macro_use] extern crate rocket;

#[get("/")]
fn hello() -> &'static str {
    "Hello, before build!"
}

#[launch]
fn rocket() -> rocket::Rocket {
    rocket::ignite().mount("/", routes![hello])
}

一度イメージのビルドを実行した後、コードを変更後イメージの再ビルドを行います。イメージとしては、下記のようなスクリプトになります。変更前と変更後のコードは上記のhello()関数の返り値が異なります。

rm -rf src && cp -r testsrc/before src
docker build -q -f Dockerfile.base -t rust-docker-base .
rm -rf src && cp -r testsrc/after src
time docker build -q -f Dockerfile.base -t rust-docker-base .

Github上に各Dockerfileとスクリプトを置いています。

対象

1. base

最も基本的なDockerfileです。

FROM rust:1.48.0

WORKDIR /app

COPY . .
RUN cargo build --release

ENTRYPOINT ["/app/target/release/app"]

2. echo

Fast + Small Docker Image Builds for Rust Appsで紹介されているイメージのビルド方法です。
アプリケーションのビルドキャッシュをつくるために一度テンポラリなmain.rsを作成してます。

# https://shaneutt.com/blog/rust-fast-small-docker-image-builds/
FROM rust:1.48.0

WORKDIR /app
COPY Cargo.toml Cargo.toml
RUN mkdir src/
RUN echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs
RUN cargo build --release
RUN rm -f target/release/deps/app*

COPY . .
RUN cargo build --release

ENTRYPOINT ["/app/target/release/app"]

3. cargo-chef

cargo-chefを使ったイメージのビルド方法です。

# https://github.com/LukeMathWalker/cargo-chef
FROM rust as planner
WORKDIR app
# We only pay the installation cost once,
# it will be cached from the second build onwards
RUN cargo install cargo-chef
COPY . .
RUN cargo chef prepare  --recipe-path recipe.json

FROM rust as cacher
WORKDIR app
RUN cargo install cargo-chef
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json

FROM rust as builder
WORKDIR app
COPY . .
# Copy over the cached dependencies
COPY --from=cacher /app/target target
COPY --from=cacher $CARGO_HOME $CARGO_HOME
RUN cargo build --release

ENTRYPOINT ["/app/target/release/app"]

4. BuildKit + base

対象1のDockerfileに加えて、BuildKitを使用します。

FROM rust:1.48.0

WORKDIR /app

COPY . .

RUN --mount=type=cache,target=/usr/local/cargo/registry \
	--mount=type=cache,target=/app/target \
	cargo build --release

ENTRYPOINT ["/app/target/release/app"]

5. BuildKit + sccache

BuildKitを使用していますが、あくまでキャッシュするのはsccacheで保存しているものに限定しています。

FROM rust:1.47.0

RUN cargo install sccache

ENV HOME=/app
ENV SCCACHE_CACHE_SIZE="1G"
ENV SCCACHE_DIR=$HOME/.cache/sccache
ENV RUSTC_WRAPPER="/usr/local/cargo/bin/sccache"

WORKDIR $HOME

COPY . .

RUN --mount=type=cache,target=/app/.cache/sccache cargo build --release

ENTRYPOINT ["/app/target/release/app"]

結果

以下のPCで計測しました。念の為、ビルド実行する前にdocker system prune -f -aを実行しました。

macOS Catalina v10.15.7
MacBook Pro (16-inch, 2019)
Processor 2.4GHz 8-Core Intel Core i9
Memory 64GB 2667 MHz DDR4

結果は下記のようなものになりました。

Time of Dockerfile.base
       87.27 real         2.86 user         0.86 sys
Time of Dockerfile.echo
       21.49 real         2.86 user         0.85 sys
Time of Dockerfile.cargochef
       18.40 real         2.86 user         0.84 sys
Time of Dockerfile.buildkit-base
       14.09 real         0.12 user         0.06 sys
Time of Dockerfile.buildkit-sccache
       34.50 real         0.16 user         0.08 sys

順番を入れ替えてもだいたい同じ結果になりました。

Time of Dockerfile.buildkit-base
       15.63 real         0.15 user         0.08 sys
Time of Dockerfile.cargochef
       18.81 real         3.05 user         0.92 sys
Time of Dockerfile.echo
       22.82 real         3.00 user         0.92 sys
Time of Dockerfile.buildkit-sccache
       35.12 real         0.16 user         0.09 sys
Time of Dockerfile.base
       90.60 real         2.93 user         0.89 sys

感想

今回の実験だと、BuildKitを使うと速いですね。プライベートだと、echoのDockerfileを使っていたのでちょっと置き換えて本当に速いか試してみたいと思います(置き換えれるか)。
emk/rust-musl-builderを使った場合どうなるのかとかもで試したいですね。

他にもこういうのもあるよっていうのがありましたら、ぜひコメントかGithubのIssueかPRください!
今回のコード類は、mkazutaka/rust-dockerfile-comparisonにあります。

ちなみに

私が使用しているRocketフレームワークを使っている最終的なアプリケーションのDockerfileです。
skaffoldのv1.17.2はBuildkit動かないので(skaffold Issue #5178)、bleeding edge buildをつかってください。
musl遅いよって記事(Why does musl make my Rust code so slow?)を見てからベースイメージにdebianを使うようにしています

FROM debian:buster-slim as runner

RUN apt update; apt install -y libssl1.1

FROM rust:1.48.0 as builder

WORKDIR /usr/src

RUN rustup target add x86_64-unknown-linux-musl

COPY Cargo.toml Cargo.lock ./
COPY src ./src

RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/usr/src/target \
    cargo install --path .

FROM runner

COPY --from=builder /usr/local/cargo/bin/myapp .
COPY Rocket.toml .

USER 1000

CMD ["./myapp"]

参考

59
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?