Rust を App Runner にデプロイしてみた

[![YUMEMI New Grad Advent Calendar 2023](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672609/6d8a7098-9aef-a19a-54fe-83a2d493e033.png)](https://qiita.com/advent-calendar/2023/yumemi-23-graduation)

# はじめに
社内研修で Rust を使って API サーバを構築する機会がありました
そのデプロイ先に AWS App Runner を用いたので，手順を備忘録として残してみます

:::note warn
App Runner の利用によって生じる料金について，公式の最新情報を参照してください
:::

# 開発環境 / 使うもの
- Apple M1 Pro
- Rust v1.67
- Axum v0.7.2

# 手順
## 作業の流れ
1. Docker イメージの作成
1. ECR にイメージをプッシュ
1. App Runner の設定・起動

## 0. 今回動かすコード
今回は Axum 公式の [Hello World](https://github.com/tokio-rs/axum/tree/main/examples/hello-world) を参考に動かしてみます
コードと依存関係はこんな感じです

```toml:cargo.toml
[package]
name = "app-runner-example"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7.2"
tokio = { version = "1.35.0", features = ["full"] }
```

```rust:src/main.rs
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Hello World!" }));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

## 1. Docker イメージの作成
Rust のコードを実行するためのイメージを作成します
上のコードを配置したディレクトリに，下記のような Dockerfile を配置します

```dockerfile:Dockerfile
FROM rust:1.67 AS builder

WORKDIR /usr/src/app
COPY . .
RUN cargo build --release

# ---

FROM gcr.io/distroless/cc-debian12:latest

COPY --from=builder /usr/src/app/target/release/app-runner-example . 
ENTRYPOINT ["./app-runner-example"]
```

下記のコマンドでビルドします
ARM でビルドされたイメージは AppRunner では使えないようです
今回は ARM環境（M1 Mac） でビルドしたため，`--platform` を指定して x86 対応を行っています

```bash:ビルド時のコマンド
docker build -t app-runner-example --platform=linux/x86_64 .
```

一応ローカルで動作確認しておきます
```bash:動作テスト
docker run -dit -p 127.0.0.1:3000:3000 app-runner-example
curl localhost:3000  # Hello World! が出力されるはず
```

## 2. ECRへイメージをプッシュ
まず，AWS マネジメントコンソールから `Elastic Container Registry` を開きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/02a92da4-8ddc-896b-a401-0e924287b943.png)

`Private registry / repositories` -> `リポジトリを作成`へ進みます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/8216f199-46e1-89ba-0987-73212ca7685a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/dbe392e7-6b7c-04b5-e1de-68d6e1c7a471.png)

「リポジトリ名」を入力しリポジトリを作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/9b1dcffe-0fd3-0107-7bee-13741ff2e532.png)

作成したリポジトリ（`app-runner-exaple`）をクリックして詳細を表示します
![スクリーンショット 2023-12-16 13.54.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/a3c9aa2a-f6c4-e1e8-ed0b-de8a74082d7d.png)

「プッシュコマンドの表示」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/b38eba13-6726-f164-d56e-1d0349180f5c.png)

表示されたコマンドのうち，2番目を除いて実行していきます
（2番目のビルドは先述した手順で実行済みです）

![スクリーンショット 2023-12-16 14.02.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/bd4388af-e066-f0fa-28a3-f48b294735d0.png)

コマンドを実行後，ページをリロードするとプッシュしたイメージが見れるはずです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/d29446f7-e209-9f7d-da35-6c22ac9cfaed.png)

## 3. App Runner の設定・起動
ここまでの手順で App Runner 上で動かすコードの準備が完了しました
ここから App Runner を設定していきます

AWS マネジメントコンソールから `AWS App Runner` を開きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/794a87eb-3a3c-06c8-c83e-988bbc572956.png)

サイドメニューの「サービス」->「サービスの作成」と進みます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/266c159f-1893-7df1-222e-eaa250ffaf03.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/35224e33-ce0f-d867-e97b-366b1dd3760c.png)

「ソース」の欄を設定していきます
冒頭2項目はそのまま，「コンテナイメージのURI」の「参照」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/2b72da5d-9b7b-2911-9f0f-4b44d65dac22.png)

表示されたポップアップから，先ほど作成した ECR のリポジトリを選択し「続行」します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/7ddef802-c324-6c7c-42de-c6e89f6b2c65.png)

次に「デプロイ設定」を行います
デプロイトリガーは説明の通り，デプロイを手動で行うか自動化するかを選択する項目です
ここではイメージがプッシュされたら自動デプロイする「自動」を選択し，「次へ」をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/65680ffc-3397-a712-96ee-4f850d054288.png)

動作テスト用なので，CPU，メモリは最小に設定しています
また，ポートを忘れずに 3000 番に指定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/28e4ff14-7843-9c9f-ac32-223e9dd0e954.png)

残りの項目はデフォルト設定として「次へ」をクリックします
確認画面の「作成とデプロイ」を押してデプロイを開始します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/5041c19d-fffd-2af8-5076-38fee61bfcb2.png)

`Operation in progress` が `Runnig` となるまで待ちます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/6dc45d9b-2f59-c732-5047-1144a9663e73.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/01de143b-1fb7-e3d4-f392-27e2b1a680a4.png)

表示されている「デフォルトドメイン」をクリックして `Hello World!` が表示されたら成功です

### 3.1 自動デプロイのテスト
ついでなので，イメージがプッシュされた時に自動デプロイがされるかも試します
`Hello World! ver2`と表示するように修正してみます

```diff_rust:src/main.rs
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
-   let app = Router::new().route("/", get(|| async { "Hello World!" }));
+   let app = Router::new().route("/", get(|| async { "Hello World! ver2" }));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

ビルドしてプッシュします
```bash
docker build -t app-runner-example --platform=linux/x86_64 .
# ここから下は ECR の「プッシュコマンドの表示」を参照
docker tag app-runner-example:latest {AWS Account ID}.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner-example:latest
docker push {AWS Account ID}.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner-example:latest
```

イメージをプッシュすると，App Runner のデプロイが始まります
完了すると `Hello World! ver2` に表示が変化していることが確認できます

## 4. リソースの削除
このままだと課金が続いてしまうので，作成したリソースを削除していきます

### 4.1 App Runner の削除
AppRunnerの詳細画面「アクション」から「削除」を選択し削除する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/8ff48a8b-873a-1f37-a98e-cf8f3b66bcef.png)

### 4.2 ECR の削除
先ほど作成したECRのリポジトリ一覧から，先ほど作成したリポジトリを削除する
![スクリーンショット 2023-12-16 15.22.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382909/5a0a5a2d-8f5c-50eb-31fa-4475d5f65cf0.png)

# 最後に
かなり簡単に Rust 製の API をデプロイすることができました
GitHub Actions 等の利用でコードの更新時に自動デプロイを行うことも可能です

ARM の Docker イメージにも対応してくれると嬉しいなと思いました