Rust+terraform+cloudrunでHello world
概要
SAM(lambda)でRustの処理を組み込んだ経験はあるけど、cloudrunとかで試したことがなかったのと、terraformと組み合わせようということで、作ってみました。
lambdaをデプロイする際にクロスコンパイル周りで過去に苦戦したことがあるのですが、同様にcloudrunでもMacでハマったところがあるので、そちらを含めて備忘録としてまとめます。今回はMacで開発を進めていますので、ご容赦ください。
開発手順
構成
ファイル構成は下記になります。
.
├── Cargo.lock
├── Cargo.toml
├── Dockerfile
├── README.md
├── docker-compose.yml
├── src
│ └── main.rs
└── terraform
├── main.tf
├── terraform.tfvars
├── terraform.tfvars.example
└── variables.tf
認証設定(セットアップ)
GCPで何かしらのプロジェクトを作成してあることを前提としています。
# gcloud認証
gcloud auth login
gcloud config set project <project-id>
# SDK認証とcloudrunで必要なサービスを有効化
gcloud auth application-default login
gcloud services enable run.googleapis.com
また、下記に環境変数を設定します。
DOCKER_IMAGE=*******************
DOCKER_WORKDIR=/root/workspace
REGION=*******************
PROJECT_ID=*******************
REPOSITORY=*******************
PATCH_PHASE=*******************
環境変数を読み込みます。
$ source ./.env
必要なイメージをGARにプッシュ
下記を実行する前に、必ずArtifact Registryでリポジトリを作成するようにしてください。
# 認証
gcloud auth configure-docker <region>-docker.pkg.dev --quiet
# ビルド
docker compose build
# GARにプッシュ
docker compose push
ここで注意したいのは、下記の二点です。
- イメージの軽量化
下記でも指摘されていますが、一般的に提供されてるrustイメージを使ってビルドしてpushすると、サイズが膨大(2GB近く)になってしまいます。
そこで、今回はglibcやlibsslすら含まない、最も小さくて軽量なイメージにlibgcc1とその依存関係を含む distroless/cc
を使用しました。
ビルド用コンテナとpush用コンテナに分けてマルチステージビルドを行うことで、イメージ容量を削減しています。
# Build stage: Compile binary in official Rust image
FROM rust:1.86 as builder
WORKDIR /app
COPY . .
RUN cargo build --release
# Runtime stage: Distroless for security and small image
FROM gcr.io/distroless/cc
COPY --from=builder /app/target/release/icarust /icarust
EXPOSE 8080
CMD ["/icarust"]
2.クロスコンパイル対策
Macでビルドしたものをcloud runにデプロイすると、プラットフォームの問題でビルドした実行ファイルが動かなくなります。この場合、cloud logを見ると、 exec format error
と出てきます。
こちらの対策として、docker-compose.ymlに platform: 'linux/x86_64'
を追加します。
x-template: &template
build:
context: .
platform: 'linux/x86_64'
env_file:
- .env
working_dir: $DOCKER_WORKDIR
volumes:
- .:${DOCKER_WORKDIR}
tty: true
services:
icarust-demo:
<<: *template
image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/icarust-dev-${PATCH_PHASE}:dev
command: /bin/bash
volumes:
python-packages:
実装
今回は単純なhello worldの表示だけします。
axumで下記のように実装しました。注意すべきは [0, 0, 0, 0]
の部分で、このように指定しないとcloudrunでは動作しないので注意してください。
use axum::{routing::get, Router};
use std::{env, net::SocketAddr};
async fn hello() -> &'static str {
"Hello from Axum on Cloud Run!"
}
#[tokio::main]
async fn main() {
let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string());
let addr = SocketAddr::from(([0, 0, 0, 0], port.parse().unwrap()));
let app = Router::new().route("/", get(hello));
println!("Listening on {}", addr);
axum_server::bind(addr)
.serve(app.into_make_service())
.await
.unwrap();
}
terraform
terraformの設定を行います。今回はcloudrunのデプロイに必要な基本的な設定のみ行います。
provider "google" {
project = var.project_id
region = var.region
}
resource "google_cloud_run_service" "default" {
name = "icarust-cloudrun"
location = var.region
template {
spec {
containers {
image = var.image_url
ports {
container_port = 8080
}
}
timeout_seconds = 180
}
}
traffic {
percent = 100
latest_revision = true
}
}
resource "google_cloud_run_service_iam_member" "invoker" {
location = var.region
service = google_cloud_run_service.default.name
role = "roles/run.invoker"
member = "allUsers"
}
確認
ローカルでの確認
下記で実行して、指定したURLを開くと文章が表示されます。
$ cargo run
コンテナでの確認
$ docker run -p 8080:8080 <IMAGE ID>
デプロイ
下記の手順で簡単にデプロイできます。
$ terraform init
$ terraform plan
$ terraform apply
# デプロイした環境を破棄したい場合は、下記を実行
$ terraform destroy
参考
Discussion