Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

デプロイメント

Rustアプリケーションのビルドとデプロイ方法を学びます。

リリースビルド

開発ビルド vs リリースビルド

# 開発ビルド(デバッグ情報あり、最適化なし)
cargo build

# リリースビルド(最適化あり、高速)
cargo build --release
開発ビルドリリースビルド
コマンドcargo buildcargo build --release
出力先target/debug/target/release/
最適化なしあり
ビルド時間短い長い
実行速度遅い速い
バイナリサイズ大きい小さい

実行

# 開発
cargo run

# リリース
cargo run --release

# または直接バイナリを実行
./target/release/my-app

バイナリの最適化

Cargo.toml の設定

[profile.release]
# 最適化レベル(0-3、sはサイズ優先、zは最小サイズ)
opt-level = 3

# リンク時最適化(LTO)
lto = true

# コード生成ユニット(1が最も最適化される)
codegen-units = 1

# パニック時の動作(abort = バイナリ小さくなる)
panic = "abort"

# デバッグシンボルを削除
strip = true

バイナリサイズの確認

# サイズ確認
ls -lh target/release/my-app

# さらに小さくする(stripコマンド)
strip target/release/my-app

環境変数と設定

環境変数の読み込み

#![allow(unused)]
fn main() {
use std::env;

struct Config {
    database_url: String,
    port: u16,
    environment: String,
}

impl Config {
    fn from_env() -> Self {
        Self {
            database_url: env::var("DATABASE_URL")
                .expect("DATABASE_URL must be set"),
            port: env::var("PORT")
                .unwrap_or_else(|_| "3000".to_string())
                .parse()
                .expect("PORT must be a number"),
            environment: env::var("RUST_ENV")
                .unwrap_or_else(|_| "development".to_string()),
        }
    }
}
}

.env ファイル(開発用)

# .env
DATABASE_URL=sqlite:./database.db
PORT=3000
RUST_ENV=development
// dotenvy クレートを使用
fn main() {
    dotenvy::dotenv().ok();  // .envファイルを読み込み
    let config = Config::from_env();
    // ...
}

Docker

Dockerfile(マルチステージビルド)

# ビルドステージ
FROM rust:1.75 AS builder

WORKDIR /app

# 依存関係をキャッシュするため、先にCargo.tomlだけコピー
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release
RUN rm -rf src

# ソースコードをコピーしてビルド
COPY . .
RUN touch src/main.rs  # タイムスタンプ更新
RUN cargo build --release

# 実行ステージ
FROM debian:bookworm-slim

# 必要なライブラリをインストール
RUN apt-get update && apt-get install -y \
    ca-certificates \
    libssl3 \
    && rm -rf /var/lib/apt/lists/*

# 非rootユーザーで実行
RUN useradd -m appuser
USER appuser

WORKDIR /app

# ビルド成果物をコピー
COPY --from=builder /app/target/release/my-app /app/my-app

# 静的ファイルをコピー(必要な場合)
COPY --from=builder /app/static /app/static

ENV PORT=3000
EXPOSE 3000

CMD ["./my-app"]

.dockerignore

target/
.git/
.env
*.md
Dockerfile
.dockerignore

Dockerコマンド

# イメージのビルド
docker build -t my-app .

# コンテナの実行
docker run -p 3000:3000 \
  -e DATABASE_URL=sqlite:/app/data/database.db \
  -v $(pwd)/data:/app/data \
  my-app

# バックグラウンドで実行
docker run -d -p 3000:3000 --name my-app my-app

Docker Compose

docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=sqlite:/app/data/database.db
      - RUST_ENV=production
    volumes:
      - ./data:/app/data
    restart: unless-stopped

  # PostgreSQLを使う場合
  # db:
  #   image: postgres:15
  #   environment:
  #     POSTGRES_USER: user
  #     POSTGRES_PASSWORD: password
  #     POSTGRES_DB: myapp
  #   volumes:
  #     - postgres_data:/var/lib/postgresql/data

# volumes:
#   postgres_data:

Docker Composeコマンド

# 起動
docker-compose up

# バックグラウンドで起動
docker-compose up -d

# 停止
docker-compose down

# ログ確認
docker-compose logs -f app

# 再ビルド
docker-compose up --build

クラウドデプロイ

Railway

# Railway CLIをインストール
npm install -g @railway/cli

# ログイン
railway login

# プロジェクト作成
railway init

# デプロイ
railway up

Fly.io

# Fly CLIをインストール
curl -L https://fly.io/install.sh | sh

# ログイン
fly auth login

# アプリ作成
fly launch

# デプロイ
fly deploy

fly.toml

app = "my-app"
primary_region = "nrt"  # 東京

[build]

[http_service]
  internal_port = 3000
  force_https = true

[env]
  RUST_ENV = "production"

Shuttle(Rust専用)

# Shuttle CLIをインストール
cargo install cargo-shuttle

# プロジェクト作成
cargo shuttle init

# ローカル実行
cargo shuttle run

# デプロイ
cargo shuttle deploy

ヘルスチェック

ヘルスチェックエンドポイント

#![allow(unused)]
fn main() {
async fn health_check() -> &'static str {
    "OK"
}

// より詳細なチェック
async fn health_detailed(State(pool): State<SqlitePool>) -> Json<HealthStatus> {
    let db_ok = sqlx::query("SELECT 1")
        .execute(&pool)
        .await
        .is_ok();

    Json(HealthStatus {
        status: if db_ok { "healthy" } else { "unhealthy" },
        database: db_ok,
        version: env!("CARGO_PKG_VERSION"),
    })
}

#[derive(Serialize)]
struct HealthStatus {
    status: &'static str,
    database: bool,
    version: &'static str,
}
}

ルートに追加

#![allow(unused)]
fn main() {
let app = Router::new()
    .route("/health", get(health_check))
    .route("/health/detailed", get(health_detailed))
    // ... 他のルート
}

ログ設定

tracing クレート

[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
use tracing::{info, warn, error};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

fn init_logging() {
    tracing_subscriber::registry()
        .with(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "my_app=debug,tower_http=debug".into()),
        )
        .with(tracing_subscriber::fmt::layer())
        .init();
}

#[tokio::main]
async fn main() {
    init_logging();

    info!("Starting server...");

    // ...
}

リクエストログ

#![allow(unused)]
fn main() {
use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/", get(handler))
    .layer(TraceLayer::new_for_http());
}

デプロイチェックリスト

セキュリティ

  • 環境変数でシークレットを管理
  • HTTPSを強制(本番)
  • CORSを適切に設定
  • SQLインジェクション対策(パラメータバインド)

パフォーマンス

  • リリースビルドを使用
  • データベース接続プール設定
  • 適切なタイムアウト設定

運用

  • ヘルスチェックエンドポイント
  • ログ出力の設定
  • エラーハンドリング
  • グレースフルシャットダウン

グレースフルシャットダウン

use tokio::signal;

#[tokio::main]
async fn main() {
    let app = Router::new()
        // ... routes ...
        ;

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("Failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("Failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    println!("Shutting down gracefully...");
}

まとめ

コマンド用途
cargo build --releaseリリースビルド
docker buildDockerイメージ作成
docker-compose upDocker Compose起動
デプロイ先特徴
Railwayシンプル、自動デプロイ
Fly.ioグローバル、高速
ShuttleRust専用、簡単

確認テスト

Q1. cargo build --release の特徴として正しいものは?

Q2. Dockerのマルチステージビルドを使う主な理由は?

Q3. マルチステージビルドなしのDockerfileの主な問題点は?

Q4. std::env::var("PORT")の戻り値の型は?

Q5. ヘルスチェックでデータベース接続失敗時に返すべきHTTPステータスコードは?


Phase 5 完了!

おめでとうございます!Phase 5を完了しました。

学んだこと:

  • HTTP/RESTの基礎
  • Axumによるウェブアプリケーション開発
  • SQLxによるデータベース操作
  • 認証の実装
  • フロントエンド連携
  • デプロイメント

次は最終プロジェクトに挑戦しましょう!

次のドキュメント: 最終プロジェクト