CDK で ECS on Fargate 環境を構築する際のサンプルテンプレートです。以下の用途で活用可能です。
- IaC(AWS CDK)
- ECS on Fargateに必要なインフラ
- CI/CD
- セキュリティ
このサンプルでは以下の技術スタックを使用しています。
機能 | 技術スタック | 補足 |
---|---|---|
言語 | TypeScript | |
ランタイム | Node.js | |
IaC | AWS CDK | |
テストフレームワーク | Jest | |
データベース | Amazon Aurora(MySQL) | |
ORM | Prisma | |
コンピューティング | AWS ECS on AWS Fargate | |
フレームワーク | NestJS | |
認証、ユーザーディレクトリ | Amazon Cognito user pools | |
パッケージマネージャー | pnpm | |
リンター/フォーマッター | Biome.js | |
CI/CD | GitHub Actions | .github/workflows にて実装 |
依存関係のを取得
$ pnpm install
AWS アカウントで cdk bootstrap を未実行の場合、ブートストラップを設定(AWS アカウントで新規に AWS CDK を動かす場合のみ実施)
$ pnpm cdk bootstrap --profile {.aws/configに設定したprofile名} -c environment=dev
CloudFormation テンプレートを生成して、CDK->CloudFormation の変換がうまくいくかを確認します。
pnpm cdk synth -c environment=dev
デプロイ内容と実環境との差分を確認したい場合は、cdk diff コマンドを利用する
pnpm cdk diff -c environment=dev
export CDK_DEFAULT_ACCOUNT=$(aws sts get-caller-identity --query "Account" --output text)
export CDK_DEFAULT_REGION="ap-northeast-1"
cd ./packages/iac
# GHAデプロイ用のロールのデプロイ
pnpm cdk deploy -c environment=dev dev-icasu-ecs-fargate-deploy-role-stack
# ECSを含むスタックは、ECRにコンテナイメージがプッシュ済みである必要があるため先に作成
pnpm cdk deploy -c environment=dev dev-icasu-ecs-fargate-ecr-stack
# 出力結果はこの後の手順で利用するため、セッションをクリアしないこと
# プロジェクトルートで実行
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
export REPOSITORY_NAME=icasu-ecs-fargate-sample-app
export REGISTRY_NAME=$AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com
export COMMIT_HASH=$(git rev-parse --short HEAD)
docker build \
--platform=linux/x86_64 \
-t $COMMIT_HASH \
-f Dockerfile.server .
docker tag $COMMIT_HASH \
$REGISTRY_NAME/$REPOSITORY_NAME:$COMMIT_HASH
# assume-roleが必要
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $REGISTRY_NAME
docker push "$AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$REPOSITORY_NAME:$COMMIT_HASH"
cd ./packages/iac
pnpm cdk deploy \
-c environment=dev \
-c imageTag=$COMMIT_HASH \
dev-icasu-ecs-fargate-infra-stack
export USER_POOL_ID="<infraスタックデプロイ出力より指定>"
export CLIENT_ID="<infraスタックデプロイ出力より指定>"
export EMAIL="<自分で指定>"
export PASSWORD="<自分で指定>"
# ユーザー作成
aws cognito-idp admin-create-user \
--user-pool-id $USER_POOL_ID \
--username $EMAIL \
--user-attributes Name="email",Value="$EMAIL" Name="email_verified",Value="true" \
--message-action SUPPRESS
export USER_UUID="<前項の出力結果 User.Attributes[0].sub>"
# パスワード設定
aws cognito-idp admin-set-user-password \
--user-pool-id $USER_POOL_ID \
--username $USER_UUID \
--password $PASSWORD \
--permanent
# トークン発行
export AT=$(aws cognito-idp admin-initiate-auth \
--user-pool-id $USER_POOL_ID \
--client-id $CLIENT_ID \
--auth-flow "ADMIN_USER_PASSWORD_AUTH" \
--auth-parameters USERNAME=$EMAIL,PASSWORD=$PASSWORD \
--no-cli-pager | jq -r ".AuthenticationResult.AccessToken")
# 存在を確認
echo $AT
# リポジトリ直下へ戻りpackages/serverへ移動
cd -
cd ./packages/server
sed -i -e "s/<replace>/$USER_UUID/g" prisma/seed.ts
常駐プロセスのため別シェルを新しく起動した上で実行
# 別シェルを立てて、assume-roleする
export BASTION_EC2_INSTANCE_ID="<infraスタックデプロイ出力より指定>"
# Auroraのエンドポイントを指定
export MYSQL_ENDPOINT="<infraスタックデプロイ出力より指定>"
export MYSQL_PORT=3307
# ローカル:3307に踏み台EC2の:3306をポートをマッピング
aws ssm start-session \
--target "$BASTION_EC2_INSTANCE_ID" \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters "{\"host\":[\"$MYSQL_ENDPOINT\"],\"portNumber\":[\"3306\"], \"localPortNumber\":[\"$MYSQL_PORT\"]}"
常駐プロセスのため別シェルを新しく起動した上で実行
# 別シェルを立てて、assume-roleする
# packages/server 配下で実行
export USER_NAME="icasu"
export MYSQL_PORT=3307
export PASSWORD="<SecretMangerのaurora-root-secretのpasswordを参照>"
export DATABASE_URL="mysql://$USER_NAME:$PASSWORD@127.0.0.1:$MYSQL_PORT/icasudb"
pnpm prisma generate
pnpm prisma migrate deploy
pnpm prisma db seed
すでに起動しているAT
を設定したシェルに戻り、以下コマンドを実行
export LOAD_BALANCER_DNS_NAME="<infraスタックデプロイ出力より指定>"
curl "http://$LOAD_BALANCER_DNS_NAME/graphql" -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: http://dev-cx-devcx-o7yzvncdwtin-190078616.ap-northeast-1.elb.amazonaws.com' -H "Authorization: Bearer $AT" --data-binary '{"query":"query {\n task(id: \"4e5b93e1-b027-4eb7-83ab-f6be67d280cc\") {\n taskId\n }\n}\n"}' --compressed
IAM 周りのセキュリティに関連する変更差分確認をスキップしたい場合は、以下のコマンドを使う
$ pnpm cdk deploy -c environment=dev --require-approval never
度々設計判断に迷う部分や躓きやすいポイントにICASU_NOTE
というコメントを残しています。
同じような設計を行う場合は、このポイントが考慮できているか参考に確認してください。
以下のコマンドでICASU_NOTE
をまとめて確認できるので、構築前後にチェックしてみてください。(Mac/Linuxのみ)
% find ./ -type f -not -regex ".*/node_modules/.*" -exec grep -H "ICASU_NOTE:" {} \; | cut -d ':' -f1,3
サンプルコード開発用の環境設定をコミットしないため .gitignore
ファイルでcdk.context.json
はコミット対象外としている。通常の開発ではコミットが必要なため、開発時にcdk.context.json
の行は削除すること
参考: https://docs.aws.amazon.com/cdk/v2/guide/context.html 意訳:cdk.json と cdk.context.json はアプリケーションの状態の一部なので、アプリケーションのソースコードと一緒にリポジトリにコミットする必要があります。
Prismaを使っていますがあくまでAurora接続確認用途です。新規案件は以下も検証してみることを推奨します。
- ORM系
- クエリビルダ系
biomejs
を利用しております。eslintとどちらを使うかは、以下の観点で導入検討してください。
- eslintの静的解析で時間がかかっており、速度を上げたい場合
- 簡易な設定で高速な静的解析/コードフォーマットを導入したい場合
- eslintやprettierでカスタムプラグインを利用する予定が無い場合
また以下のルールに関しては、errorにしてください。現状ICASUでは作成途中のためwarnにしております。
- lint/correctness/noUnusedVariables
- lint/suspicious/noExplicitAny
ローカルにSSM Manager Pluginがない場合は、手順からインストール
踏み台とローカルのポートをフォワードするプロキシの起動
# 踏み台サーバーのEC2インスタンスIDを取得
export BASTION_EC2_INSTANCE_ID=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=BastionHost" --query "Reservations[*].Instances[*].InstanceId" --output text)
# Auroraのエンドポイントを指定
export MYSQL_ENDPOINT=""
export MYSQL_PORT=3307
aws ssm start-session \
--target "$BASTION_EC2_INSTANCE_ID" \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters "{\"host\":[\"$MYSQL_ENDPOINT\"],\"portNumber\":[\"3306\"], \"localPortNumber\":[\"$MYSQL_PORT\"]}"
セッションへログイン(対話型でSecretMangerにあるパスワードを入力する)
mysql -u icasu -p -h 127.0.0.1 -P 3307 --local_infile=1 -D icasudb
現在本リポジトリは、クラスメソッド内部のミラーとして機能しています。
今後PRやISSUE受け入れを検討しており準備している段階です。フィードバックがありましたら、tmk2154, shuntaka_jpまでお願いします。