AWS CDKを使って、App Runnerでアプリを楽々デプロイする方法
はじめに
App Runnerを利用してサービスでも作ろうとした時に、コマンド一つでデプロイできないかなーと試してみた内容です。
読むと幸せになるかもしれない人
- DockerイメージのビルドとECRへのプッシュをコマンドで実行している人
- AWSのコンソール画面にて、App Runnerをデプロイしている人
- App Runnerを取り敢えず使ってみたい人
コードについて
ディレクトリ構成
ディレクトリ構成は以下のようにしています。
今回のサンプルでは、Rustで作成したアプリをApp Runnerにて動作させるようにしています。
.
├── backend
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Dockerfile
│ └── src
│ └── main.rs
└── cdk
├── .gitignore
├── .npmignore
├── README.md
├── bin
│ └── cdk.ts
├── cdk.json
├── jest.config.js
├── lib
│ ├── cdk-stack.ts
│ └── constructs
│ ├── AppRunner.ts
│ └── ImageRepository.ts
├── package-lock.json
├── package.json
├── test
│ └── cdk.test.ts
└── tsconfig.json
CDKコード
Dockerイメージのビルド、ECRへのプッシュ
ImageRepository.ts
にて、cdk-docker-image-deployment
を利用しており、Dockerイメージのビルドと、ECRへのプッシュ処理を行うようにしています。
import { IRepository, Repository, TagMutability } from "aws-cdk-lib/aws-ecr";
import {
Destination,
DockerImageDeployment,
Source,
} from "cdk-docker-image-deployment";
import { Construct } from "constructs";
import path = require("path");
export class ImageRepository extends Construct {
repository: IRepository;
constructor(scope: Construct, id: string) {
super(scope, id);
const repository = new Repository(this, "RustBackendRepository", {
repositoryName: "rust_backend",
imageTagMutability: TagMutability.IMMUTABLE,
});
const result = new DockerImageDeployment(this, "RustBackendImage", {
source: Source.directory(path.join('..', 'backend')),
destination: Destination.ecr(repository, {
tag: "v1",
}),
});
this.repository = repository;
}
}
App Runnerのデプロイ
ImageRepository.ts
にて、作成したリポジトリ情報を元に、App Runnerへデプロイします。
App Runnerのライブラリについて、CDK v2のリファレンスが見つからなく、実装に苦労しました😰
import { IRepository } from "aws-cdk-lib/aws-ecr";
import {
aws_apprunner as apprunner,
} from 'aws-cdk-lib';
import { Construct } from "constructs";
export type AppRunnerProps = {
repository: IRepository;
};
export class AppRunner extends Construct {
constructor(scope: Construct, id: string, props: AppRunnerProps) {
super(scope, id);
const appRunnerECRAccessRole = process.env.ECR_ARN;
const { repository } = props;
const cfnService = new apprunner.CfnService(this, 'AppRunnerService', {
sourceConfiguration: {
authenticationConfiguration: {
accessRoleArn: appRunnerECRAccessRole,
},
autoDeploymentsEnabled: false,
imageRepository: {
imageIdentifier: repository.repositoryUriForTag("v1"),
imageRepositoryType: 'ECR',
imageConfiguration: {
port: '8080',
runtimeEnvironmentVariables: [],
},
},
},
healthCheckConfiguration: {
interval: 5,
healthyThreshold: 2,
},
});
}
}
CDK Stack
ECRへのプッシュ完了後に、App Runnerのデプロイ作業を行わないとエラーになってしまうため、addDependency
を利用して依存関係を明示しています。
import { Construct } from 'constructs';
import {
Stack,
StackProps,
} from 'aws-cdk-lib';
import { ImageRepository } from './constructs/ImageRepository';
import { AppRunner } from './constructs/AppRunner';
export class CdkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const imageRepository = new ImageRepository(this, "ImageRepository");
const backendApp = new AppRunner(this, "RustBackend", {
repository: imageRepository.repository,
});
backendApp.node.addDependency(imageRepository);
}
}
バックエンドコード
とりあえず、「Hello World!」と返す簡単なWeb APIサーバーです。
use actix_web::{get, web, App, HttpServer, Responder};
#[get("/hello/{name}")]
async fn greet(name: web::Path<String>) -> impl Responder {
format!("Hello {name}!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(|| async { "Hello World!" }))
.route("/hello", web::get().to(|| async { "Hello World!!" }))
.service(greet)
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}
Dockerファイルです。
FROM rust:1.71 as builder
WORKDIR /usr/src/backend
COPY . .
RUN cargo install --path .
FROM centos:latest
COPY /usr/local/cargo/bin/backend /usr/local/bin/backend
CMD ["backend"]
デプロイ確認
cdk deploy
コマンドを実行し、App Runnerへデプロイが完了すればOKです!
改善点
現在のコードだと、ECRのタグ指定方法をハードコーディングしてしまっているため、コマンドライン引数にて、タグの値を取得してきた方が良さそうです。
また、過去にデプロイしたDockerイメージへ巻き戻しする場合もあり得るので、Dockerイメージのビルド有無も指定できるようにもした方が良さそうです。
さいごに
これまで手間だと感じていたことが、コマンド一発でやれるようになるのは気持ちいいですね…☺️
これでRustを利用したサービスを作成する準備ができたので、次回はRustの記事にてお会いしましょう!
Discussion