⚙️

Rust製APIをECSで動かすまで

2023/03/04に公開

GOAL

  • RustでAPIを作る
  • GitHub Actions & AWS CDKでデプロイ
    • APIのDockerイメージをECRに登録
    • ECS & Fargateで動かす

API

まずはECSで動かすサンプルAPIを作ります。
今回のメインではないので、Actix Webのスタートくらいの実装に留めます。

lib.rs
use actix_web::{get, HttpResponse, Responder};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug)]
pub struct User {
    id: u32,
    name: String,
}

#[get("/")]
pub async fn users() -> impl Responder {
    HttpResponse::Ok().json(User {
        id: 1,
        name: "Mosu".into(),
    })
}
main.rs
extern crate rust_aws_sample;

use actix_web::{App, HttpServer};
use std::io::Result;

#[actix_web::main]
async fn main() -> Result<()> {
    HttpServer::new(|| {
        App::new().service(rust_aws_sample::users)
    })
    .bind(("0.0.0.0", 8080))? // Docker用の設定です
    .run()
    .await
}

Docker

作成したAPIをDocker上で起動できるようにします。

Dockerfile
FROM rust:1.67.1

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

EXPOSE 8080
ENTRYPOINT ["cargo", "run", "-r"]
.dockerignore
.gitignore
.github
.vscode
target
tests
cdk
README.md

一度動作確認しておきます。

shell
docker build . -t rust-app
docker run -d --rm -p 8080:8080 rust-app
curl "http://localhost:8080/"

Hello worldが表示されればOKです。

CDK

デプロイをGitHub Actions経由で行いたいので、今回はCDK(TypeScript)を利用します。
まずはTypeScriptとCDKの準備をします。

Code

Rustプロジェクトと同階層にCDK用のディレクトリを作って、そこで作業していきます。

./cdk
npx cdk init -l typescript

テストコードなどが生成されますが、不要な場合は適宜削除してください。
また、DockerイメージをECRにアップロード後、ECSで稼働させるまで行うので、こちらを使います。
https://github.com/cdklabs/cdk-ecr-deployment

./cdk
npm i cdk-docker-image-deployment

準備が整ったので、CDKのコードを書いていきます。

.cdk/lib/cdk-stack.ts
import * as cdk from "aws-cdk-lib";
import { Vpc } from "aws-cdk-lib/aws-ec2";
import { Repository } from "aws-cdk-lib/aws-ecr";
import { Cluster, ContainerImage } from "aws-cdk-lib/aws-ecs";
import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns";
import {
  Destination,
  DockerImageDeployment,
  Source,
} from "cdk-docker-image-deployment";
import { Construct } from "constructs";

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ECRに登録するTAGを環境変数から取得します
    const tag = process.env.CURRENT_TAG || "latest";

    const repository = new Repository(this, "RustAppRepository", {
      repositoryName: "rust-app",
      imageScanOnPush: true,
    });
    new DockerImageDeployment(this, "RustAppDeploy", {
      source: Source.directory("../"),
      destination: Destination.ecr(repository, { tag }),
    });

    const vpc = new Vpc(this, "RustAppVpc", { maxAzs: 2 });
    const cluster = new Cluster(this, "RustAppCluster", {
      vpc,
      clusterName: "rust-app-cluster",
    });
    new ApplicationLoadBalancedFargateService(this, "RustFargateService", {
      cluster,
      taskImageOptions: {
        image: ContainerImage.fromEcrRepository(repository, tag),
        containerPort: 8080, // DockerfileでEXPOSEしたポート
      },
    });
  }
}

GitHub Actions

以下のように作ります。

  • mainにpushされたらテスト
  • tagがpushされたらCDKデプロイ

Test

.github/workflow/test.yml
name: Rust test
on:
  push:
    branches: ["main"]
env:
  CARGO_TERM_COLOR: always
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/cache@v3
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
      - name: Run tests
        run: cargo test --verbose

AWS

さて、あとはデプロイするようにymlを書くだけですが、ここでAWSの設定が必要になります。
というのも、GitHub上でAWSのリソースにアクセスするための情報が、何も設定していないからです。
GitHubの設定でSecretsにAWSの機密情報を登録して、yml上でenvに設定する方法もありますが、好ましくないのでOIDCを使った方法にします。

https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services

手順は公式ドキュメントに記載されているので、ここでは割愛します。

Deploy

ようやく準備が整ったので、デプロイ用のWorkflowを作成します。
Assume Roleには用意されているActionを利用します。
https://github.com/aws-actions/configure-aws-credentials

.github/workflow/deploy.yml
name: Rust deploy
on:
  push:
    tags:
      - 'v*' # v0.1.0 のようなtagをpushした際にデプロイ実行
env:
  CARGO_TERM_COLOR: always
  CURRENT_TAG: ${{ github.ref_name }} # pushされたtagを環境変数に設定
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install package
        run: npm ci
        working-directory: cdk
      - name: Assume Role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          # FIXME 適宜修正してください、OIDCの設定をしたRoleです
          role-to-assume: arn:aws:iam::<account_id>:role/<role name>
          role-session-name: deploysession
          aws-region: ap-northeast-1
      - name: CDK Deploy
        run: npx cdk deploy --require-approval never
        working-directory: cdk

まとめ

以上で、CDKとGitHub Actionsを使って
Releaseを作成すると、ECSへデプロイされる仕組みが完成しました。

Discussion