最小かつ効率的なRust WebアプリケーションDockerイメージの構築
Lukas Schneider
DevOps Engineer · Leapcell

クラウドネイティブ開発の急速に進化する状況において、アプリケーションを効率的にパッケージ化してデプロイする能力は最重要です。Dockerは、ポータビリティと一貫した実行環境を提供するコンテナ化の事実上の標準として登場しました。パフォーマンスと安全性で知られるRust Webアプリケーションにとって、最小かつ効率的なDockerイメージを作成することは、単なる最適化ではありません。その本質的な利点を最大限に引き出すための基本的なステップです。イメージサイズが小さいということは、ダウンロードが速く、ストレージコストが削減され、起動時間が短縮され、攻撃対象領域が小さくなることを意味します。これらはすべて、最新のマイクロサービスアーキテクチャとサーバーレスデプロイメントにとって非常に重要です。この記事では、Rust Webアプリケーションの合理化されたDockerイメージの構築プロセスをステップバイステップで説明し、可能な限りリーンでパフォーマンスが高くなるようにします。
効率的なコンテナ化のコアコンセプト
実践的な側面に入る前に、Rust Dockerイメージの効率的な戦略の基盤となるいくつかのコアコンセプトについて共通の理解を確立しましょう。
- マルチステージビルド: このDocker機能により、Dockerfileで
FROM
命令を複数使用できます。各FROM
ディレクティブは新しいビルドステージを開始します。次に、アーティファクトをステージ間で選択的にコピーして、ビルド時の依存関係と実行時の要件を効果的に分離できます。これは、最終的なイメージサイズを小さく保つために不可欠です。 - Musl libc(静的リンク):
musl
は、静的リンク用に設計された軽量、高速、安全なC標準ライブラリです。Rustアプリケーションをmusl
でコンパイルすることにより、システム動的ライブラリ(glibc
など)に依存しない完全に静的にリンクされたバイナリを作成できます。これにより、最終的なイメージはバイナリ自体のみを必要とするため、非常に小さくポータブルになります。 FROM scratch
: これはDockerで可能な限り最小のベースイメージです。オペレーティングシステムでさえ、絶対に含まれていません。静的にリンクされたバイナリと組み合わせて使用すると、可能な限り最小の最終イメージが得られます。- ビルドキャッシュ: Dockerレイヤーとビルドキャッシュは基本的です。Dockerがレイヤーをキャッシュする方法(
Dockerfile
命令に基づく)を理解することで、頻繁に変更される命令を遅延させることでビルドプロセスを最適化できます。 - リリースビルドとデバッグビルド: Rustには異なるコンパイルプロファイルがあります。「cargo build --release」は、デバッグシンボルを削除し、さまざまな最適化を適用して、パフォーマンスとサイズのためにバイナリを最適化します。これは本番デプロイメントに不可欠です。
Minimal Rust Web Application Docker Imagesの構築
Rustの最小Dockerイメージの構築の本質は、マルチステージビルドと静的リンクを活用することにあります。簡単な actix-web
アプリケーションの実際的な例をステップバイステップで説明します。
基本的な actix-web
アプリケーションから始めましょう。
// src/main.rs use actix_web::{ get, App, HttpResponse, HttpServer, Responder, }; #[get("/")] async fn hello() -> impl Responder { HttpResponse::Ok().body("Hello from Rust on Docker!") } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new().service(hello)) .bind(("0.0.0.0", 8080))? .run() .await }
そして、その Cargo.toml
:
# Cargo.toml [package] name = "my-rust-app" version = "0.1.0" edition = "2021" [dependencies] actix-web = "4" tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
次に、最適化された Dockerfile
を作成しましょう。
# Stage 1: Builder # Use a specific Rust version with Debian slim for a stable build environment FROM rust:1.76-slim-bookworm AS builder # Set the working directory inside the container WORKDIR /app # Install musl-tools for static compilation RUN apt-get update && apt-get install -y musl-tools && rm -rf /var/lib/apt/lists/* # Add the musl target for static linking RUN rustup target add x86_64-unknown-linux-musl # Copy only Cargo.toml and Cargo.lock first to leverage Docker cache # This layer changes less often than source code COPY Cargo.toml Cargo.lock ./ # Build dependencies only. This layer is highly cacheable. # If Cargo.toml and Cargo.lock haven't changed, this step will be skipped. RUN cargo fetch --locked --target x86_64-unknown-linux-musl # Copy all source code COPY src ./src # Build the release binary with musl target # --release for optimizations and smaller size # --locked to ensure reproducible builds based on Cargo.lock # --target for static linking with musl libc RUN CARGO_INCREMENTAL=0 \ RUSTFLAGS="-C strip=debuginfo -C target-feature=+aes,+sse2,+ssse3" \ cargo build --release --locked --target x86_64-unknown-linux-musl # Stage 2: Runner # Start from scratch for the smallest possible final image FROM scratch # Copy only the compiled binary from the builder stage COPY /app/target/x86_64-unknown-linux-musl/release/my-rust-app . # Expose the port your application listens on EXPOSE 8080 # Define the command to run your application CMD ["./my-rust-app"]
このDockerfileを詳しく見てみましょう。
-
ビルダー(Stage 1) (
FROM rust:1.76-slim-bookworm AS builder
):/code>
debian-slimに基づいた
rust` イメージから開始します。これは、不要なバルクなしで完全なRustツールチェーンと必要なビルド依存関係を提供します。WORKDIR /app
次のコマンドの作業ディレクトリを設定します。RUN apt-get update && apt-get install -y musl-tools
:musl
コンパイルに必要な静的リンカーとヘッダーを提供するmusl-tools
パッケージをインストールします。RUN rustup target add x86_64-unknown-linux-musl
:x86_64-unknown-linux-musl
ターゲットをRustツールチェーンに追加し、静的リンクを可能にします。COPY Cargo.toml Cargo.lock ./
: これは重要なキャッシュ最適化です。マニフェストファイルのみを最初にコピーして依存関係(cargo fetch
)をビルドすることにより、Dockerはこのレイヤーをキャッシュできます。ソースコードのみが変更された場合、この重い依存関係コンパイルステップはスキップされます。RUN cargo fetch --locked --target x86_64-unknown-linux-musl
: プロジェクトのすべての依存関係を取得します。COPY src ./src
: 実際のソースコードをコピーします。ソースコードが変更された場合、このレイヤーは後続のステップのキャッシュを無効にします。RUN CARGO_INCREMENTAL=0 RUSTFLAGS="-C strip=debuginfo -C target-feature=+aes,+sse2,+ssse3" cargo build --release --locked --target x86_64-unknown-linux-musl
: アプリケーションをコンパイルします。CARGO_INCREMENTAL=0
:Dockerでのリリースビルドには有益ではなく、イメージサイズを増加させる可能性があるため、増分コンパイルを無効にします。RUSTFLAGS="-C strip=debuginfo -C target-feature=+aes,+sse2,+ssse3"
: 最終バイナリからデバッグ情報を削除し、潜在的なパフォーマンス向上のために特定のCPU機能を有効にします。--release
: パフォーマンス最適化とバイナリサイズの削減を保証します。--locked
: 再現可能なビルドを保証するためにCargo.lock
を使用します。--target x86_64-unknown-linux-musl
:musl
での静的リンクのターゲットを指定します。
-
ランナー(Stage 2) (
FROM scratch
):FROM scratch
: ここで最小イメージの魔法が起こります。空のベースから始めます。COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my-rust-app .
: 静的にリンクされた最終バイナリのみをbuilder
ステージからscratch
イメージにコピーします。バイナリは自己完結型(静的にリンクされている)ため、scratch
イメージ内の他のファイルや依存関係は必要ありません。EXPOSE 8080
: コンテナがポート8080をリッスンしていることをDockerに通知しますが、実際にはポートを発行しません。- `CMD [