Rust 連携(Loco / 素の axum)¶
Rust アプリケーションでは、magic-pocket-rs crate を使って環境変数をセットアップします。
Django の set_envs() に相当する機能を Rust で提供します。
ここで使う仕組み(lambda_http で axum Router を Lambda ハンドラーにする・set_envs()
でシークレット/リソース情報を注入する・pocket.toml の handlers.command でバイナリを
指定する)は Loco 固有ではなく、lambda_http を使う任意の axum アプリで共通です。
Loco を使わない素の axum バイナリでも、同じ pocket.toml と Dockerfile でそのまま
デプロイできます(→Lambda エントリポイントに両方の例)。デプロイは
フレームワークを問わず plain pocket deploy で完結します(Loco 専用のデプロイ
コマンドはありません)。
セットアップ¶
Cargo.toml に依存を追加します。
[dependencies]
magic-pocket-rs = { git = "https://github.com/worgue/magic-pocket.git" }
lambda_http = "0.14"
tokio = { version = "1", features = ["full"] }
# Loco を使う場合のみ:
loco-rs = "0.14"
# 素の axum の場合は axum を直接:
# axum = "0.8"
Django では apig-wsgi が WSGI アプリを Lambda ハンドラーに変換しますが、axum では lambda_http が axum Router を直接 Lambda ハンドラーとして使えます(Loco の Router も素の axum の Router も同じ)。フレームワーク側で完結するため、Lambda Web Adapter のような外部 Extension は不要です。
Lambda エントリポイント¶
src/bin/lambda.rs を作成します。共通するのは「set_envs() で環境変数を注入 →
axum Router を組み立てて lambda_http::run(router) に渡す」という流れで、Router の
作り方だけがフレームワークで変わります。
Loco の場合¶
use loco_rs::boot::{create_app, StartMode};
use loco_rs::config::Config;
use loco_rs::environment::Environment;
use myapp::app::App;
use myapp::migration::Migrator;
#[tokio::main]
async fn main() -> Result<(), lambda_http::Error> {
magic_pocket_rs::set_envs().await.unwrap();
let environment: Environment = "production".parse().unwrap();
let config = Config::new(&environment)?;
let boot = create_app::<App, Migrator>(
StartMode::ServerOnly,
&environment,
config,
)
.await?;
lambda_http::run(boot.router.expect("no router")).await
}
素の axum の場合¶
Loco を使わず axum の Router を直接組む場合も、set_envs() → lambda_http::run() の
流れは同じです。
use axum::{routing::get, Router};
#[tokio::main]
async fn main() -> Result<(), lambda_http::Error> {
magic_pocket_rs::set_envs().await.unwrap();
let router = Router::new().route("/api/health", get(|| async { "ok" }));
lambda_http::run(router).await
}
この場合 Cargo.toml の loco-rs は不要で、axum を直接依存に加えます。以降の
set_envs() / pocket.toml / Dockerfile はフレームワークを問わず共通です
(後述の「注意点」の production.yaml / static 設定は Loco 固有なので素の axum では
不要)。
set_envs()¶
set_envs() は、Secrets Manager / SSM からシークレットを取得し、CloudFormation Output から API Gateway のホスト情報を取得して、すべて環境変数にセットします。セットされる環境変数の一覧は「実行環境」を参照してください。
| 関数 | 説明 |
|---|---|
set_envs() |
シークレット + AWS リソース情報をすべてセット |
set_envs_from_secrets(stage) |
シークレットのみセット |
set_envs_from_resources(stage) |
AWS リソース情報のみセット |
POCKET_STAGE が設定されていない場合、シークレット取得はスキップされます(ローカル環境での安全動作)。
origin verify middleware¶
enable_origin_verify を使う
distribution 配下に Rust container を置く場合は、feature axum を有効にして同梱の
middleware を Router に組み込みます(Django 版 OriginVerifyMiddleware の axum 版)。
検証用 secret(POCKET_ORIGIN_VERIFY_SECRET)の runtime env 注入は set_envs() が
cloudfront 設定から自動で行うため、利用者側の宣言は不要です。
[dependencies]
magic-pocket-rs = { git = "https://github.com/worgue/magic-pocket.git", features = ["axum"] }
use axum::{routing::get, Extension, Router};
use magic_pocket_rs::origin_verify::{origin_verify_middleware, ClientIp};
// origin verify を通過したリクエストには詐称耐性のある client IP が
// ClientIp extension として入る (Django 版の REMOTE_ADDR 上書きに相当)
async fn whoami(ip: Option<Extension<ClientIp>>) -> String {
ip.map(|Extension(ClientIp(ip))| ip.to_string())
.unwrap_or_else(|| "unknown".to_string())
}
let app: Router = Router::new()
.route("/whoami", get(whoami))
// client IP を読む layer より外側 (.layer() は後着が外側) に置く
.layer(axum::middleware::from_fn(origin_verify_middleware));
挙動は Django 版と同じです:
| 状況 | 挙動 |
|---|---|
| env secret 未設定 (local/dev、CloudFront 無し) | no-op。ClientIp は挿入されない |
| secret header が一致 (CloudFront 経由) | viewer IP を ClientIp extension で提供 |
| secret header が無い / 不一致 (origin 直叩き) | 403 で拒否 |
pocket.toml の構成例¶
[general]
region = "ap-northeast-1"
stages = ["dev", "prod"]
[s3]
[neon]
project_name = "dev-myproject"
[container.main]
dockerfile_path = "pocket.Dockerfile"
[container.main.handlers.wsgi]
command = "myapp-lambda"
[dev.container.main.handlers.wsgi]
apigateway = {}
[container.main.secrets.managed]
LOCO_SECRET_KEY = { type = "password", options = { length = 50 } }
DATABASE_URL = { type = "neon_database_url" }
データベースの選択¶
set_envs() は Django 版と同じ DB backend に対応しています。DATABASE_URL の
type を差し替えるだけで切り替わります。
| type | 備考 |
|---|---|
neon_database_url |
上記の例。VPC 不要 |
tidb_database_url |
VPC 不要 |
rds_database_url |
VPC 必須。認証情報から実行時に DATABASE_URL を構築 |
| (DSQL) | [dsql] を設定。IAM 認証トークンを POCKET_DSQL_TOKEN にセット |
RDS を使う場合は [rds] と VPC の設定が必要です。
[vpc]
ref = "main"
zone_suffixes = ["a", "c"] # managed VPC では RDS に 2AZ 以上必須
[rds]
[container.main.secrets.managed]
DATABASE_URL = { type = "rds_database_url" }
RDS のマスターパスワードは自動ローテーションされるため、DATABASE_URL は
deploy 時ではなく set_envs() 呼び出し時に認証情報から構築されます。詳細は
「実行環境」を参照してください。
Dockerfile の例¶
FROM rust:1-bookworm AS builder
WORKDIR /app
COPY . .
RUN cargo build --release --bin myapp-lambda
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/myapp-lambda /usr/local/bin/
COPY --from=builder /app/config /app/config
COPY pocket.runtime.toml /app/pocket.runtime.toml
WORKDIR /app
CMD ["myapp-lambda"]
command は Docker の CMD をオーバーライドするため、Dockerfile では CMD のみを使用し ENTRYPOINT は設定しないことを推奨します。詳細は「設定ファイル - container.main.handlers」を参照してください。
注意点¶
以下は Loco の設定ファイル(production.yaml)に関する注意で、Loco 固有です。
素の axum アプリでは該当しません。
production.yaml の環境変数クォート¶
magic-pocket が生成するシークレット(type = "password")には *, !, # 等の YAML 特殊文字が含まれる場合があります。Loco の設定テンプレートでは環境変数を必ずクォートで囲んでください。
# NG - SECRET_KEY に * が含まれると YAML パースが壊れる
secret: {{ get_env(name="SECRET_KEY") }}
# OK
secret: "{{ get_env(name="SECRET_KEY") }}"
database:
uri: "{{ get_env(name="DATABASE_URL") }}"
static ミドルウェアの無効化¶
Loco のデフォルト production.yaml には static.must_exist: true で frontend/dist を参照する設定があります。Lambda コンテナにフロントエンドを含めない場合は無効にしてください。
DB 接続タイムアウト¶
Neon がアプリケーションと異なるリージョンにある場合、デフォルトの接続タイムアウトでは不足する場合があります。クロスリージョン接続では余裕を持った値を設定してください。
Neon のリージョン選択
Neon プロジェクトは Lambda と同じリージョン(または近いリージョン)に作成することを推奨します。レイテンシと接続タイムアウトの問題を回避できます。