背景
今年のゴールデンウィーク頃、コロナ騒ぎで外出できなかったので、自宅でフロントエンド系(Vue)のお勉強をしていました。
その際、Vue(nodejs)で作ったWebアプリをAWS上に自動デプロイ(ECS/FargateのBlue/Greenデプロイ)する仕組みと、その環境をワンコマンドでセットアップ/クリーナップする仕組みを構築したので、そのやり方を思い出しながら書いてみます。
Webアプリについて
本題からは外れますが、軽くWebアプリを紹介しておきます。
作ったWebアプリは、ブラウザ上でオンライン対戦できるカードゲーム(ババ抜き)です。
プログラムの構成はこんな感じ
- フロントエンドは、Node.js(Vue.js/typescript)で作成
- バックエンドは、Node.js(Express/typescript)で作成
- フロントエンド-バックエンド間は、Web-Socketで通信
コードはここに置いておきました。
-
バックエンド (game-server)
https://github.com/yusuke-ka/game-server -
フロントエンド (game-ui)
https://github.com/yusuke-ka/game-ui
ちなみに、Vueどころか、フロントエンド開発自体ほとんどやったことがなかったので、最終的に出来上がったコードも、上級者から見ると、幼稚なものに仕上がっているかと思います。
なお、トランプの絵などの素材はこちらのフリー素材を利用させていただきました。
https://www.irasutoya.com/
https://chicodeza.com/freeitems/torannpu-illust.html
※個人の学習目的で作成したプログラム内の素材として利用しています。
AWSの構成
デプロイ後の構成はこんな感じ。
- ALBは一つを共有して、ポートで分ける。
- Code-DeployのB/GデプロイもALB共有状態で問題なく実行できた。
- フロントエンドからバックエンドへのリクエストはALBを経由(というかブラウザからリクエスト)
- フロントエンドはALBのURLを知っておく必要がある
- タスク数は1。
- デプロイ時のみB/Gなので2個に増える
- CloudFrontとS3はおまけ
- フロントエンド(http-server)の静的ファイルと同じものをS3においてホスティングしてみた。
- 前段にCloudFrontを置く構成もやってみた
自動デプロイの構成
game-serverとgame-uiのリポジトリをAWSのCodeCommitに作成し、それぞれコミットしたら自動的にデプロイされるようにした。
CodeCommitのコミットイベントからCodePipelineが起動され、まず、CodeBuildが実行される。
CodeBuildの設定ファイル(buildspec.yml)は、game-serverやgame-uiのリポジトリにプログラムのソースと一緒に入っている。
以下はgame-serverのプロジェクト直下に置いたbuildspec.yml。
({AWSのアカウントID}部分は念のため伏せています。)
version: 0.2
phases:
install:
runtime-versions:
docker: 18
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws --version
- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
- REPOSITORY_URI={AWSのアカウントID}.dkr.ecr.ap-northeast-1.amazonaws.com/game-server
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:latest .
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Writing image definitions file...
- printf '{"Version":"1.0","ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
files:
- imageDetail.json
以下はUI側(game-ui)用のbuildspec.yml。
version: 0.2
env:
parameter-store:
ALB_URL: "/game/ALBDNSName"
phases:
install:
runtime-versions:
docker: 18
pre_build:
commands:
- npm update -g npm
- npm install
- echo 'VUE_APP_SERVER='$ALB_URL > .env
- echo Building the S3 files...
- npm run build
- echo Logging in to Amazon ECR...
- aws --version
- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
- REPOSITORY_URI={AWSのアカウントID}.dkr.ecr.ap-northeast-1.amazonaws.com/game-ui
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:latest . --build-arg ALB_URL=$ALB_URL
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Writing image definitions file...
- printf '{"Version":"1.0","ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
- echo upload s3
- aws s3 sync --exact-timestamps --delete ./dist/ s3://game-ui-s3-hosting
artifacts:
files:
- imageDetail.json
yarnでなく、npmを使っているのは、yarn install中のファイルのダウンロードが時間内に終わらなくて、「unexpected end of file」のエラーが発生していたため。
手元なら再度yarn installすれば続きからやってくれるが、CodeBuildにリトライさせるのも微妙なので、とりあえずnpmに変更した。(たぶん他にやりようはあるはず。)
その他、パラメータストアからALBのURLを取得して、docker buildの引数として渡すようなコードが入っている。
あらかじめ用意したALBを使い続けるなら、こんな設定はいらないが(game-uiの.envファイルに最初から書いておけばよい)、
お金節約のため、ALB作成部分をCFn化して、毎日消して、再作成できるようにしたので、こんなことになっている。
パラメータストアに設定するあたりは、CFnでやっているので、後で説明する。
ちなみに、おまけで用意したS3ホスティング用のファイルはここ(CodeBuild)でsyncさせている。
docker buildのコマンドで使われるDockerfileは以下。
FROM node:lts-alpine
WORKDIR /app
COPY package*.json ./
RUN yarn install
COPY . .
RUN yarn build
EXPOSE 3000
CMD [ "yarn", "start" ]
上記はバックエンドサービス用で、フロントエンド側は以下となる。
FROM node:lts-alpine
ARG ALB_URL
RUN npm install -g http-server
WORKDIR /app
COPY package*.json ./
RUN yarn install
COPY . .
RUN echo 'VUE_APP_SERVER='$ALB_URL > .env
RUN yarn build
EXPOSE 80
CMD [ "http-server", "dist", "-p", "80" ]
バックエンド側はnodejsで起動するが、フロントエンド側のgame-uiはビルドで静的ファイルが生成されるので、http-serverで配信する。
また、UI側はバックエンドサービスのURL(ALBのURL)を知っておく必要があるので、ビルド前に以下の一文を追加している。
「RUN echo 'VUE_APP_SERVER='$ALB_URL > .env」
buildspec.yml側でdocker buildコマンドを呼び出す際に引数として渡したALB_URLを.envファイルに書き出している。
vueで書かれたgame-uiのプログラムが.envに書かれた環境変数を読み取って、バックエンド側にアクセスする。
buildspec.ymlに戻って、
結果のアーティファクトとなるimageDetail.jsonには、ECRに登録されたDockerイメージのURIが記載されていて、これを次のCodeDeployが読み取り&ECSのタスク定義に組み込んで、あらかじめ用意しておいたECS/Fargateのサービス上にBlue/Greenデプロイする。
CodeDeploy用の設定ファイルappspec.yamlとECSのタスク定義の設定ファイルtaskdef.jsonは、game-serverなどの実プログラムを入れたリポジトリとは別に用意したリポジトリに入れている。(バックエンド用、フロントエンド用で、それぞれ、infra-serverとinfra-uiというリポジトリ名にした)
CodeDeployが読み取るappspec.yamlは下記。
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "game-server"
ContainerPort: 3000
ECS用の設定になっている。
上記はgame-server用。game-ui側は下記。
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "game-ui"
ContainerPort: 80
<TASK_DEFINITION>部分には、パイプライン実行時に実際のタスク定義がセットされる。
続いてタスク定義用の設定ファイル(taskdef.json)。
まずはバックエンド用。
{
"executionRoleArn": "arn:aws:iam::{AWSのアカウントID}:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "game-server",
"image": <IMAGE1_NAME>,
"essential": true,
"portMappings": [
{
"hostPort": 3000,
"protocol": "tcp",
"containerPort": 3000
}
]
}
],
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"family": "game-server"
}
続いてフロントエンド用。
{
"executionRoleArn": "arn:aws:iam::{AWSのアカウントID}:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "game-ui",
"image": <IMAGE1_NAME>,
"essential": true,
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
]
}
],
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"family": "game-ui"
}
<IMAGE1_NAME>部分には、パイプライン実行時に実際のイメージ名がセットされる。
あとは、appspec.yamlの設定を読み込んだCodeDeployがよしなにやってくれる。
CodePipelineやCodeDeploy自体の構築は、CFnやCLIで書いたので次の章に記載する。
AWSのマネジメントコンソール上でパイプラインを設定する際の方法は下記を参照(AWS公式のチュートリアル)。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-ecs-ecr-codedeploy.html
(このチュートリアルの手順はCodeBuildによるイメージ作成がない代わりに、あらかじめECRに登録しておいたイメージをソースとして読み込んで利用する感じ。)
上記チュートリアルはマネジメントコンソール上で必要な環境を用意していく感じだが、今回はこれらをCFnとAWS Cliを使ってまとめて作成する感じにしたので、以降でそれについて記載する。
AWS環境セットアップ/クリーンナップ
これまで記載してきた環境をCFnとAWS Cliを使って、一括作成/一括削除するスクリプトを作成したので、それについて記載する。
(個人用のAWSなので無駄にお金を使いたくなかったというのが、これを作成したモチベーション)
一括作成のスクリプトのイメージは下記の通り。
なお、一括削除のスクリプトは、上記とは逆順に消していく感じになっている。
最初は、全部CFnで対応しようとしたが、この時点(2020/5/1)のCFnではcode-deployのB/Gデプロイがlambdaしか対応していないので、code-deployだけは、シェルでAWS-CLIを呼び出して作成
各スタック(とシェル)間のデータのやりとりはパラメータストアで実施している。
setup.sh
まずは、一括作成用のスクリプト。
#!/bin/sh
echo 'setup start'
echo 'create network stack'
aws cloudformation create-stack --stack-name network-stack --region ap-northeast-1 --template-body file://cfn-network-infra-template.yml
aws cloudformation wait stack-create-complete --stack-name network-stack
echo 'network stack created'
echo 'create ecs stack'
aws cloudformation create-stack --stack-name ecs-stack --region ap-northeast-1 --template-body file://cfn-ecs-infra-template.yml
aws cloudformation wait stack-create-complete --stack-name ecs-stack
echo 'ecs stack created'
echo 'create s3 stack'
aws cloudformation create-stack --stack-name s3-stack --region ap-northeast-1 --template-body file://cfn-s3-infra-template.yml
aws cloudformation wait stack-create-complete --stack-name s3-stack
echo 's3 stack created'
echo 'create code-deploy'
sh ./create-codedeploy.sh
echo 'codedeploy created'
echo 'create cicd stack'
aws cloudformation create-stack --stack-name cicd-stack --region ap-northeast-1 --template-body file://cfn-cicd-infra-template.yml
aws cloudformation wait stack-create-complete --stack-name cicd-stack
echo 'cicd stack created'
echo 'setup finish'
echo 'ALB URL :'
echo `aws ssm get-parameter --name /game/ALBDNSName --query "Parameter.Value" --output text`
echo 'Website URL :'
echo `aws ssm get-parameter --name /game/WebsiteURL --query "Parameter.Value" --output text`
各スタック作成の完了を待って、次のスタックを実行している。
最後にアクセス先のURLを出力している。
cfn-network-infra-template.yml
setup.shから最初に呼び出されるのは、ALBなど、ネットワーク環境を構築するテンプレート。
VPCやサブネットなどは、ほぼお金がかからないので、あらかじめ作成しておいたものを利用(削除しない)。
(セキュリティグループは、80,8080,3000,3001ポートを開けておく必要がある)
AWSTemplateFormatVersion: "2010-09-09"
Description:
Network Infra Create (TargetGroup ALB)
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
PJPrefix:
Type: String
Default: "game"
#Network
VpcId:
Type: String
Default: "vpc-xxxxxx"
SecurityGroupId:
Type: String
Default: "sg-xxxxxx"
SubnetIdA:
Type: String
Default: "subnet-xxxxxx"
SubnetIdC:
Type: String
Default: "subnet-xxxxxx"
Resources:
# ------------------------------------------------------------#
# Target Group
# ------------------------------------------------------------#
TargetGroupForUi1:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VpcId
Name: !Sub "${PJPrefix}-tg-ui-1"
Protocol: "HTTP"
Port: 80
TargetType: "ip"
Matcher:
HttpCode: 200
DependsOn: InternetALB
TargetGroupForUi2:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VpcId
Name: !Sub "${PJPrefix}-tg-ui-2"
Protocol: "HTTP"
Port: 8080
TargetType: "ip"
Matcher:
HttpCode: 200
DependsOn: InternetALB
TargetGroupForServer1:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VpcId
Name: !Sub "${PJPrefix}-tg-server-1"
Protocol: "HTTP"
Port: 3000
TargetType: "ip"
Matcher:
HttpCode: 200
DependsOn: InternetALB
TargetGroupForServer2:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VpcId
Name: !Sub "${PJPrefix}-tg-server-2"
Protocol: "HTTP"
Port: 3001
TargetType: "ip"
Matcher:
HttpCode: 200
DependsOn: InternetALB
# ------------------------------------------------------------#
# ALB
# ------------------------------------------------------------#
InternetALB:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
IpAddressType: "ipv4"
Name: !Sub "${PJPrefix}-alb"
Scheme: "internet-facing"
SecurityGroups:
- !Ref SecurityGroupId
Subnets:
- !Ref SubnetIdA
- !Ref SubnetIdC
Type: "application"
ALBListenerForUi1:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupForUi1
Type: "forward"
LoadBalancerArn: !Ref InternetALB
Port: 80
Protocol: "HTTP"
ALBListenerForUi2:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupForUi2
Type: "forward"
LoadBalancerArn: !Ref InternetALB
Port: 8080
Protocol: "HTTP"
ALBListenerForServer1:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupForServer1
Type: "forward"
LoadBalancerArn: !Ref InternetALB
Port: 3000
Protocol: "HTTP"
ALBListenerForServer2:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroupForServer2
Type: "forward"
LoadBalancerArn: !Ref InternetALB
Port: 3001
Protocol: "HTTP"
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
ALBDNSName:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ALBDNSName
Type: String
Value: !GetAtt InternetALB.DNSName
ALBName:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ALBName
Type: String
Value: !GetAtt InternetALB.LoadBalancerName
TargetGroupUi1Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupUi1Arn
Type: String
Value: !Ref TargetGroupForUi1
TargetGroupUi2Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupUi2Arn
Type: String
Value: !Ref TargetGroupForUi2
TargetGroupServer1Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupServer1Arn
Type: String
Value: !Ref TargetGroupForServer1
TargetGroupServer2Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupServer2Arn
Type: String
Value: !Ref TargetGroupForServer2
TargetGroupUi1Name:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupUi1Name
Type: String
Value: !GetAtt TargetGroupForUi1.TargetGroupName
TargetGroupUi2Name:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupUi2Name
Type: String
Value: !GetAtt TargetGroupForUi2.TargetGroupName
TargetGroupServer1Name:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupServer1Name
Type: String
Value: !GetAtt TargetGroupForServer1.TargetGroupName
TargetGroupServer2Name:
Type: AWS::SSM::Parameter
Properties:
Name: /game/TargetGroupServer2Name
Type: String
Value: !GetAtt TargetGroupForServer2.TargetGroupName
ALBListenerUi1Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ALBListenerUi1Arn
Type: String
Value: !Ref ALBListenerForUi1
ALBListenerUi2Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ALBListenerUi2Arn
Type: String
Value: !Ref ALBListenerForUi2
ALBListenerServer1Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ALBListenerServer1Arn
Type: String
Value: !Ref ALBListenerForServer1
ALBListenerServer2Arn:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ALBListenerServer2Arn
Type: String
Value: !Ref ALBListenerForServer2
他のCFnスタックが、ここで作成したALBの情報が利用できるように、最後のOutput Parameters部分でパラメータストアに格納しておく。
パラメータストアを利用しているのは、CFn化できなかったCodeDeploy作成のシェルからの呼び出しが容易だったから。
なお、ALBDNSNameとして保存したデータは、CodeBuildでgame-ui側をビルドする際に、バックエンドサービスにアクセスするためのエンドポイントとして設定される。
cfn-ecs-infra-template.yml
ECS/Fargate用にクラスターとサービスを作成するテンプレート。
AWSTemplateFormatVersion: "2010-09-09"
Description:
ECS Infra Create (Cluster Service)
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
PJPrefix:
Type: String
Default: "game"
#Network
SecurityGroupId:
Type: String
Default: "sg-xxxxxx"
SubnetIdA:
Type: String
Default: "subnet-xxxxxxx"
SubnetIdC:
Type: String
Default: "subnet-xxxxxx"
TargetGroupUi1Arn:
Type: AWS::SSM::Parameter::Value<String>
Default: "/game/TargetGroupUi1Arn"
TargetGroupServer1Arn:
Type: AWS::SSM::Parameter::Value<String>
Default: "/game/TargetGroupServer1Arn"
#ECS
TaskDefUi:
Type: String
Default: "game-ui:15"
TaskDefServer:
Type: String
Default: "game-server:5"
Resources:
# ------------------------------------------------------------#
# ECS
# ------------------------------------------------------------#
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub "${PJPrefix}-ecs-cluster"
ECSServiceForUi:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref ECSCluster
DeploymentController:
Type: "CODE_DEPLOY"
DesiredCount: 1
HealthCheckGracePeriodSeconds: 60
LaunchType: "FARGATE"
LoadBalancers:
- ContainerName: "game-ui"
ContainerPort: 80
TargetGroupArn: !Ref TargetGroupUi1Arn
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: "ENABLED"
SecurityGroups:
- !Ref SecurityGroupId
Subnets:
- !Ref SubnetIdA
- !Ref SubnetIdC
SchedulingStrategy: "REPLICA"
ServiceName: !Sub "${PJPrefix}-ecs-service-ui"
TaskDefinition: !Ref TaskDefUi
ECSServiceForServer:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref ECSCluster
DeploymentController:
Type: "CODE_DEPLOY"
DesiredCount: 1
HealthCheckGracePeriodSeconds: 60
LaunchType: "FARGATE"
LoadBalancers:
- ContainerName: "game-server"
ContainerPort: 3000
TargetGroupArn: !Ref TargetGroupServer1Arn
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: "ENABLED"
SecurityGroups:
- !Ref SecurityGroupId
Subnets:
- !Ref SubnetIdA
- !Ref SubnetIdC
SchedulingStrategy: "REPLICA"
ServiceName: !Sub "${PJPrefix}-ecs-service-server"
TaskDefinition: !Ref TaskDefServer
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
ECSClusterName:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ECSClusterName
Type: String
Value: !Ref ECSCluster
ECSServiceUiName:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ECSServiceUiName
Type: String
Value: !GetAtt ECSServiceForUi.Name
ECSServiceServerName:
Type: AWS::SSM::Parameter
Properties:
Name: /game/ECSServiceServerName
Type: String
Value: !GetAtt ECSServiceForServer.Name
パラメータ読み込みのところで、タスク定義のデフォルト値を"game-ui:15"や"game-server:5"としているのは、誤ってバージョン1のタスク定義を消してしまったから。
cfn-s3-infra-template.yml
S3とCloudFrontを作成するテンプレート。
おまけみたいなものなので、結構適当。
本当はCloudFrontのOAIを使ってS3にアクセスするようにし、S3側は公開しないようにした方がよいが、そのまま両方公開された状態になる。
AWSTemplateFormatVersion: "2010-09-09"
Description:
S3 Infra Create (S3 CloudFront)
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
PJPrefix:
Type: String
Default: "game"
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3Bucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub "${PJPrefix}-ui-s3-hosting"
AccessControl: "PublicRead"
WebsiteConfiguration:
IndexDocument: "index.html"
ErrorDocument: "error.html"
DeletionPolicy: Retain
BucketPolicy:
Type: "AWS::S3::BucketPolicy"
Properties:
PolicyDocument:
Id: 'S3Policy'
Version: '2012-10-17'
Statement:
- Sid: 'PublicReadForGetBucketObjects'
Effect: 'Allow'
Principal: '*'
Action: 's3:GetObject'
Resource: !Join
- ''
- - 'arn:aws:s3:::'
- !Ref S3Bucket
- '/*'
Bucket: !Ref S3Bucket
# ------------------------------------------------------------#
# CloudFront
# ------------------------------------------------------------#
CloudFrontDistribution:
Type: "AWS::CloudFront::Distribution"
Properties:
DistributionConfig:
PriceClass: PriceClass_All
Origins:
- DomainName: !Sub "${PJPrefix}-ui-s3-hosting.s3-website-${AWS::Region}.amazonaws.com"
Id: !Sub "S3-${PJPrefix}-ui-s3-hosting"
CustomOriginConfig:
OriginProtocolPolicy: "http-only"
DefaultCacheBehavior:
TargetOriginId: !Sub "S3-${PJPrefix}-ui-s3-hosting"
ViewerProtocolPolicy: "allow-all"
AllowedMethods:
- "GET"
- "HEAD"
CachedMethods:
- "GET"
- "HEAD"
DefaultTTL: 3600
MaxTTL: 86400
MinTTL: 60
Compress: true
ForwardedValues:
Cookies:
Forward: "none"
QueryString: false
Enabled: true
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
S3URL:
Type: AWS::SSM::Parameter
Properties:
Name: /game/S3URL
Type: String
Value: !GetAtt S3Bucket.WebsiteURL
WebsiteURL:
Type: AWS::SSM::Parameter
Properties:
Name: /game/WebsiteURL
Type: String
Value: !GetAtt CloudFrontDistribution.DomainName
一応、最後にS3で静的ホスティングしたURLとCloudFrontのURLをパラメータストアに入れている。
create-codedeploy.sh
コードデプロイのアプリケーションとデプロイグループをAWS CLIを使って作成するシェル。
最初にこれまでにCFnで作成したリソースの情報をパラメータストアから読み込んでいる。
次に、デプロイグループ設定用jsonテンプレートのプレイスホルダー部分にパラメータストアから取得した値などを埋め込んで、設定用jsonを生成している。
AWS CLIコマンドで生成した設定用jsonを指定してデプロイグループを作成する。
#!/bin/sh
# Parameter
DEPLOYMENT_GROUP_TEMPLATE=./deploy-group-template.json
DEPLOYMENT_GROUP_UI_JSON=./deploy-group-ui.json
DEPLOYMENT_GROUP_SERVER_JSON=./deploy-group-server.json
PJ_PREFIX=game
APPLICATION_UI_NAME=${PJ_PREFIX}-deploy-ui
APPLICATION_SERVER_NAME=${PJ_PREFIX}-deploy-server
DEPLOYMENT_GROUP_UI_NAME=${PJ_PREFIX}-deploy-group-ui
DEPLOYMENT_GROUP_SERVER_NAME=${PJ_PREFIX}-deploy-group-server
SERVICE_ROLE="arn:aws:iam::{AWSのアカウントID}:role/CodeDeployECSRole"
TARGET_GROUP_UI_1=(`aws ssm get-parameter --name /game/TargetGroupUi1Name --query "Parameter.Value" --output text`)
TARGET_GROUP_UI_2=(`aws ssm get-parameter --name /game/TargetGroupUi2Name --query "Parameter.Value" --output text`)
TARGET_GROUP_SERVER_1=(`aws ssm get-parameter --name /game/TargetGroupServer1Name --query "Parameter.Value" --output text`)
TARGET_GROUP_SERVER_2=(`aws ssm get-parameter --name /game/TargetGroupServer2Name --query "Parameter.Value" --output text`)
ALB_LISTENER_UI_1=(`aws ssm get-parameter --name /game/ALBListenerUi1Arn --query "Parameter.Value" --output text`)
ALB_LISTENER_UI_2=(`aws ssm get-parameter --name /game/ALBListenerUi2Arn --query "Parameter.Value" --output text`)
ALB_LISTENER_SERVER_1=(`aws ssm get-parameter --name /game/ALBListenerServer1Arn --query "Parameter.Value" --output text`)
ALB_LISTENER_SERVER_2=(`aws ssm get-parameter --name /game/ALBListenerServer2Arn --query "Parameter.Value" --output text`)
ECS_CLUSTER_NAME=(`aws ssm get-parameter --name /game/ECSClusterName --query "Parameter.Value" --output text`)
ECS_SERVICE_UI_NAME=(`aws ssm get-parameter --name /game/ECSServiceUiName --query "Parameter.Value" --output text`)
ECS_SERVICE_SERVER_NAME=(`aws ssm get-parameter --name /game/ECSServiceServerName --query "Parameter.Value" --output text`)
# Replace
## For UI
cp -f ${DEPLOYMENT_GROUP_TEMPLATE} ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#APPLICATION_NAME#@'${APPLICATION_UI_NAME}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#DEPLOYMENT_GROUP_NAME#@'${DEPLOYMENT_GROUP_UI_NAME}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#SERVICE_ROLE#@'"${SERVICE_ROLE}"'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#TARGET_GROUP_1#@'${TARGET_GROUP_UI_1}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#TARGET_GROUP_2#@'${TARGET_GROUP_UI_2}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#ALB_LISTENER_1#@'${ALB_LISTENER_UI_1}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#ALB_LISTENER_2#@'${ALB_LISTENER_UI_2}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#ECS_CLUSTER_NAME#@'${ECS_CLUSTER_NAME}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
sed -i -e 's@#ECS_SERVICE_NAME#@'${ECS_SERVICE_UI_NAME}'@g' ${DEPLOYMENT_GROUP_UI_JSON}
## For Server
cp -f ${DEPLOYMENT_GROUP_TEMPLATE} ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#APPLICATION_NAME#@'${APPLICATION_SERVER_NAME}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#DEPLOYMENT_GROUP_NAME#@'${DEPLOYMENT_GROUP_SERVER_NAME}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#SERVICE_ROLE#@'${SERVICE_ROLE}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#TARGET_GROUP_1#@'${TARGET_GROUP_SERVER_1}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#TARGET_GROUP_2#@'${TARGET_GROUP_SERVER_2}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#ALB_LISTENER_1#@'${ALB_LISTENER_SERVER_1}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#ALB_LISTENER_2#@'${ALB_LISTENER_SERVER_2}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#ECS_CLUSTER_NAME#@'${ECS_CLUSTER_NAME}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
sed -i -e 's@#ECS_SERVICE_NAME#@'${ECS_SERVICE_SERVER_NAME}'@g' ${DEPLOYMENT_GROUP_SERVER_JSON}
# execute AWS CLI
aws deploy create-application --application-name ${APPLICATION_UI_NAME} --compute-platform ECS
aws deploy create-application --application-name ${APPLICATION_SERVER_NAME} --compute-platform ECS
aws deploy create-deployment-group --application-name ${APPLICATION_UI_NAME} --deployment-group-name ${DEPLOYMENT_GROUP_UI_NAME} --cli-input-json file://${DEPLOYMENT_GROUP_UI_JSON}
aws deploy create-deployment-group --application-name ${APPLICATION_SERVER_NAME} --deployment-group-name ${DEPLOYMENT_GROUP_SERVER_NAME} --cli-input-json file://${DEPLOYMENT_GROUP_SERVER_JSON}
デプロイグループのテンプレートファイルはこれ。
#hogehoge# となっている部分を置換して、別ファイルとして出力。
{
"applicationName": "#APPLICATION_NAME#",
"deploymentGroupName": "#DEPLOYMENT_GROUP_NAME#",
"serviceRoleArn": "#SERVICE_ROLE#",
"deploymentStyle": {
"deploymentType": "BLUE_GREEN",
"deploymentOption": "WITH_TRAFFIC_CONTROL"
},
"blueGreenDeploymentConfiguration": {
"terminateBlueInstancesOnDeploymentSuccess": {
"action": "TERMINATE",
"terminationWaitTimeInMinutes": 0
},
"deploymentReadyOption": {
"actionOnTimeout": "CONTINUE_DEPLOYMENT",
"waitTimeInMinutes": 0
}
},
"loadBalancerInfo": {
"targetGroupPairInfoList": [
{
"targetGroups": [
{
"name": "#TARGET_GROUP_1#"
},
{
"name": "#TARGET_GROUP_2#"
}
],
"prodTrafficRoute": {
"listenerArns": [
"#ALB_LISTENER_1#"
]
},
"testTrafficRoute": {
"listenerArns": [
"#ALB_LISTENER_2#"
]
}
}
]
},
"ecsServices": [
{
"clusterName": "#ECS_CLUSTER_NAME#",
"serviceName": "#ECS_SERVICE_NAME#"
}
]
}
cfn-cicd-infra-template.yml
CodePipelineやCodeBuildを作成するテンプレート。
ロールはあらかじめ作成しておいたものを使っている。
(ここで生成するようにしてもよいが、お金がかからないので消さずにそのまま使っている)
AWSTemplateFormatVersion: "2010-09-09"
Description:
CI/CD Infra Create
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
PJPrefix:
Type: String
Default: "game"
#Network
ALBName:
Type: AWS::SSM::Parameter::Value<String>
Default: "/game/ALBName"
TargetGroupUi1Name:
Type: AWS::SSM::Parameter::Value<String>
Default: "/game/TargetGroupUi1Name"
TargetGroupUi2Name:
Type: AWS::SSM::Parameter::Value<String>
Default: "/game/TargetGroupUi2Name"
TargetGroupServer1Name:
Type: AWS::SSM::Parameter::Value<String>
Default: "/game/TargetGroupServer1Name"
TargetGroupServer2Name:
Type: AWS::SSM::Parameter::Value<String>
Default: "/game/TargetGroupServer2Name"
#Code Commit
RepositoryNameForUi:
Type: String
Default: "game-ui"
RepositoryNameForServer:
Type: String
Default: "game-server"
RepositoryNameForInfraUi:
Type: String
Default: "infra-ui"
RepositoryNameForInfraServer:
Type: String
Default: "infra-server"
CodeCommitGameUiArn:
Type: String
Default: "arn:aws:codecommit:ap-northeast-1:{AWSのアカウントID}:game-ui"
CodeCommitGameServerArn:
Type: String
Default: "arn:aws:codecommit:ap-northeast-1:{AWSのアカウントID}:game-server"
CodeCommitInfraUiArn:
Type: String
Default: "arn:aws:codecommit:ap-northeast-1:{AWSのアカウントID}:infra-ui"
CodeCommitInfraServerArn:
Type: String
Default: "arn:aws:codecommit:ap-northeast-1:{AWSのアカウントID}:infra-server"
#CodePipeline
CodePipelineS3Backet:
Type: String
Default: "codepipeline-ap-northeast-1-xxxxxxxxxxx"
#Role
CodePipelineServiceRoleArn:
Type: String
Default: "arn:aws:iam::{AWSのアカウントID}:role/service-role/AWSCodePipelineServiceRole"
CodeBuildServiceRoleArn:
Type: String
Default: "arn:aws:iam::{AWSのアカウントID}:role/CodeBuildServiceRole"
CodeDeployServiceRoleArn:
Type: String
Default: "arn:aws:iam::{AWSのアカウントID}:role/CodeDeployECSRole"
CloudWatchEventRoleArn:
Type: String
Default: "arn:aws:iam::{AWSのアカウントID}:role/CloudWatchEventRole"
# CodeDeploy (ECSへのBlue/GreenデプロイはCFnで未対応のためパラメータ化)
CodeDeployAppForUi:
Type: String
Default: "game-deploy-ui"
CodeDeployGroupForUi:
Type: String
Default: "game-deploy-group-ui"
CodeDeployAppForServer:
Type: String
Default: "game-deploy-server"
CodeDeployGroupForServer:
Type: String
Default: "game-deploy-group-server"
Resources:
# ------------------------------------------------------------#
# CodeBuild
# ------------------------------------------------------------#
CodeBuildProjectForUi:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: "CODEPIPELINE"
Source:
Type: "CODEPIPELINE"
Environment:
PrivilegedMode: true
ComputeType: "BUILD_GENERAL1_SMALL"
Image: "aws/codebuild/amazonlinux2-x86_64-standard:2.0-20.03.13"
Type: "LINUX_CONTAINER"
Name: !Sub "${PJPrefix}-build-ui"
ServiceRole: !Ref CodeBuildServiceRoleArn
CodeBuildProjectForServer:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: "CODEPIPELINE"
Source:
Type: "CODEPIPELINE"
Environment:
PrivilegedMode: true
ComputeType: "BUILD_GENERAL1_SMALL"
Image: "aws/codebuild/amazonlinux2-x86_64-standard:2.0-20.03.13"
Type: "LINUX_CONTAINER"
Name: !Sub "${PJPrefix}-build-server"
ServiceRole: !Ref CodeBuildServiceRoleArn
# ------------------------------------------------------------#
# CodeDeploy (ECSへのBlue/GreenデプロイはCFnで未対応)
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codedeploy-deploymentgroup-loadbalancerinfo.html
# Note
# AWS CloudFormation supports blue/green deployments on the AWS Lambda compute platform only.
# -> AWS CLI で事前に作成
# ------------------------------------------------------------#
# CodeDeployAppForUi:
# Type: AWS::CodeDeploy::Application
# Properties:
# ApplicationName: !Sub "${PJPrefix}-deploy-ui"
# ComputePlatform: "ECS"
# CodeDeployAppForServer:
# Type: AWS::CodeDeploy::Application
# Properties:
# ApplicationName: !Sub "${PJPrefix}-deploy-server"
# ComputePlatform: "ECS"
# CodeDeployGroupForUi:
# Type: AWS::CodeDeploy::DeploymentGroup
# Properties:
# ApplicationName: !Ref CodeDeployAppForUi
# DeploymentConfigName: "CodeDeployDefault.AllAtOnce"
# DeploymentGroupName: !Sub "${PJPrefix}-deploy-group-ui"
# DeploymentStyle:
# DeploymentOption: "WITH_TRAFFIC_CONTROL"
# DeploymentType: "BLUE_GREEN"
# LoadBalancerInfo:
# ElbInfoList:
# - Name: !Ref ALBName
# TargetGroupInfoList:
# - Name: !Ref TargetGroupUi1Name
# - Name: !Ref TargetGroupUi2Name
# ServiceRoleArn: !Ref CodeDeployServiceRoleArn
# CodeDeployGroupForServer:
# Type: AWS::CodeDeploy::DeploymentGroup
# Properties:
# ApplicationName: !Ref CodeDeployAppForServer
# DeploymentConfigName: "CodeDeployDefault.AllAtOnce"
# DeploymentGroupName: !Sub "${PJPrefix}-deploy-group-server"
# DeploymentStyle:
# DeploymentOption: "WITH_TRAFFIC_CONTROL"
# DeploymentType: "BLUE_GREEN"
# LoadBalancerInfo:
# ElbInfoList:
# - Name: !Ref ALBName
# TargetGroupInfoList:
# - Name: !Ref TargetGroupServer1Name
# - Name: !Ref TargetGroupServer2Name
# ServiceRoleArn: !Ref CodeDeployServiceRoleArn
# ------------------------------------------------------------#
# CodePipeLine
# ------------------------------------------------------------#
PipelineForUi:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !Ref CodePipelineServiceRoleArn
Name: !Sub "${PJPrefix}-pipline-ui"
ArtifactStore:
Type: S3
Location: !Ref CodePipelineS3Backet
Stages:
- Name: "Source"
Actions:
- Name: "InfraSourceAction"
ActionTypeId:
Category: "Source"
Owner: "AWS"
Version: "1"
Provider: "CodeCommit"
Configuration:
RepositoryName: !Ref RepositoryNameForInfraUi
PollForSourceChanges: false
BranchName: "master"
RunOrder: 1
OutputArtifacts:
- Name: "InfraSourceArtifact"
- Name: "ImageSourceAction"
ActionTypeId:
Category: "Source"
Owner: "AWS"
Version: "1"
Provider: "CodeCommit"
Configuration:
RepositoryName: !Ref RepositoryNameForUi
PollForSourceChanges: false
BranchName: "master"
RunOrder: 1
OutputArtifacts:
- Name: "ImageSourceArtifact"
- Name: "Build"
Actions:
- Name: "ImageBuildAction"
ActionTypeId:
Category: "Build"
Owner: "AWS"
Version: "1"
Provider: "CodeBuild"
Configuration:
ProjectName: !Ref CodeBuildProjectForUi
RunOrder: 1
InputArtifacts:
- Name: "ImageSourceArtifact"
OutputArtifacts:
- Name: "ImageBuildArtifact"
- Name: "Deploy"
Actions:
- Name: "ImageDeployAction"
ActionTypeId:
Category: "Deploy"
Owner: "AWS"
Version: 1
Provider: "CodeDeployToECS"
Configuration:
ApplicationName: !Ref CodeDeployAppForUi
DeploymentGroupName: !Ref CodeDeployGroupForUi
Image1ArtifactName: "ImageBuildArtifact"
Image1ContainerName: "IMAGE1_NAME"
TaskDefinitionTemplateArtifact: "InfraSourceArtifact"
TaskDefinitionTemplatePath: "taskdef.json"
AppSpecTemplateArtifact: "InfraSourceArtifact"
AppSpecTemplatePath: "appspec.yaml"
RunOrder: 1
InputArtifacts:
- Name: "InfraSourceArtifact"
- Name: "ImageBuildArtifact"
PipelineForServer:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !Ref CodePipelineServiceRoleArn
Name: !Sub "${PJPrefix}-pipline-server"
ArtifactStore:
Type: S3
Location: !Ref CodePipelineS3Backet
Stages:
- Name: "Source"
Actions:
- Name: "InfraSourceAction"
ActionTypeId:
Category: "Source"
Owner: "AWS"
Version: "1"
Provider: "CodeCommit"
Configuration:
RepositoryName: !Ref RepositoryNameForInfraServer
PollForSourceChanges: false
BranchName: "master"
RunOrder: 1
OutputArtifacts:
- Name: "InfraSourceArtifact"
- Name: "ImageSourceAction"
ActionTypeId:
Category: "Source"
Owner: "AWS"
Version: "1"
Provider: "CodeCommit"
Configuration:
RepositoryName: !Ref RepositoryNameForServer
PollForSourceChanges: false
BranchName: "master"
RunOrder: 1
OutputArtifacts:
- Name: "ImageSourceArtifact"
- Name: "Build"
Actions:
- Name: "ImageBuildAction"
ActionTypeId:
Category: "Build"
Owner: "AWS"
Version: "1"
Provider: "CodeBuild"
Configuration:
ProjectName: !Ref CodeBuildProjectForServer
RunOrder: 1
InputArtifacts:
- Name: "ImageSourceArtifact"
OutputArtifacts:
- Name: "ImageBuildArtifact"
- Name: "Deploy"
Actions:
- Name: "ImageDeployAction"
ActionTypeId:
Category: "Deploy"
Owner: "AWS"
Version: 1
Provider: "CodeDeployToECS"
Configuration:
ApplicationName: !Ref CodeDeployAppForServer
DeploymentGroupName: !Ref CodeDeployGroupForServer
Image1ArtifactName: "ImageBuildArtifact"
Image1ContainerName: "IMAGE1_NAME"
TaskDefinitionTemplateArtifact: "InfraSourceArtifact"
TaskDefinitionTemplatePath: "taskdef.json"
AppSpecTemplateArtifact: "InfraSourceArtifact"
AppSpecTemplatePath: "appspec.yaml"
RunOrder: 1
InputArtifacts:
- Name: "InfraSourceArtifact"
- Name: "ImageBuildArtifact"
# ------------------------------------------------------------#
# CloudWatchEvent
# ------------------------------------------------------------#
AmazonCloudWatchEventRuleForUi:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- "aws.codecommit"
detail-type:
- "CodeCommit Repository State Change"
resources:
- !Ref CodeCommitGameUiArn
detail:
event:
- "referenceCreated"
- "referenceUpdated"
referenceType:
- "branch"
referenceName:
- "master"
Targets:
- Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${PJPrefix}-pipline-ui" # CFnのPipelineはArnを取得できないため直接記載
RoleArn: !Ref CloudWatchEventRoleArn
Id: "codepipeline"
AmazonCloudWatchEventRuleForServer:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- "aws.codecommit"
detail-type:
- "CodeCommit Repository State Change"
resources:
- !Ref CodeCommitGameServerArn
detail:
event:
- "referenceCreated"
- "referenceUpdated"
referenceType:
- "branch"
referenceName:
- "master"
Targets:
- Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${PJPrefix}-pipline-server" # CFnのPipelineはArnを取得できないため直接記載
RoleArn: !Ref CloudWatchEventRoleArn
Id: "codepipeline"
AmazonCloudWatchEventRuleForUiInfra:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- "aws.codecommit"
detail-type:
- "CodeCommit Repository State Change"
resources:
- !Ref CodeCommitInfraUiArn
detail:
event:
- "referenceCreated"
- "referenceUpdated"
referenceType:
- "branch"
referenceName:
- "master"
Targets:
- Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${PJPrefix}-pipline-ui" # CFnのPipelineはArnを取得できないため直接記載
RoleArn: !Ref CloudWatchEventRoleArn
Id: "codepipeline"
AmazonCloudWatchEventRuleForServerInfra:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- "aws.codecommit"
detail-type:
- "CodeCommit Repository State Change"
resources:
- !Ref CodeCommitInfraServerArn
detail:
event:
- "referenceCreated"
- "referenceUpdated"
referenceType:
- "branch"
referenceName:
- "master"
Targets:
- Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${PJPrefix}-pipline-server" # CFnのPipelineはArnを取得できないため直接記載
RoleArn: !Ref CloudWatchEventRoleArn
Id: "codepipeline"
一括作成系の情報はこれで終わり。
cleanup.sh
一括削除用のスクリプト。
S3バケットはCFnで削除されないので、別途削除している。
#!/bin/sh
echo 'cleanup start'
echo 'delete cicd stack'
aws cloudformation delete-stack --stack-name cicd-stack
aws cloudformation wait stack-delete-complete --stack-name cicd-stack
echo 'cicd stack deleted'
echo 'delete code-deploy'
sh ./delete-codedeploy.sh
echo 'codedeploy deleted'
echo 'delete s3 stack'
aws cloudformation delete-stack --stack-name s3-stack
aws cloudformation wait stack-delete-complete --stack-name s3-stack
echo 's3 stack deleted'
echo 'delete s3 bucket'
aws s3 rb s3://game-ui-s3-hosting --force
echo 's3 bucket deleted'
echo 'delete ecs stack'
aws cloudformation delete-stack --stack-name ecs-stack
aws cloudformation wait stack-delete-complete --stack-name ecs-stack
echo 'ecs stack deleted'
echo 'delete network stack'
aws cloudformation delete-stack --stack-name network-stack
aws cloudformation wait stack-delete-complete --stack-name network-stack
echo 'network stack deleted'
echo 'cleanup finish'
delete-codedeploy.sh
コードデプロイ削除用のスクリプト。
CFnじゃないのでスクリプトからAWS CLIをたたいている。
#!/bin/sh
# Parameter
PJ_PREFIX=game
APPLICATION_UI_NAME=${PJ_PREFIX}-deploy-ui
APPLICATION_SERVER_NAME=${PJ_PREFIX}-deploy-server
DEPLOYMENT_GROUP_UI_NAME=${PJ_PREFIX}-deploy-group-ui
DEPLOYMENT_GROUP_SERVER_NAME=${PJ_PREFIX}-deploy-group-server
# execute AWS CLI
aws deploy delete-deployment-group --application-name ${APPLICATION_UI_NAME} --deployment-group-name ${DEPLOYMENT_GROUP_UI_NAME}
aws deploy delete-deployment-group --application-name ${APPLICATION_SERVER_NAME} --deployment-group-name ${DEPLOYMENT_GROUP_SERVER_NAME}
aws deploy delete-application --application-name ${APPLICATION_UI_NAME}
aws deploy delete-application --application-name ${APPLICATION_SERVER_NAME}
一括削除は、稀にネットワーク系の情報がCFnで削除できずに残ることがあった。
ネットワーク利用中だと消せないのかもしれない。
あと、ECRのイメージとECSのタスク定義がたまりつづけるので、これも消せるようにした方がよかったかな。
さいごに
これをやった時点ではCloudFormationでCodeDeployのECS/FargateのBlue/Greenデプロイがサポートされていなかったんですが、直後の5/19にできるようになったらしいです。
今度、書き換えてみようと思います。
最後にAWSの利用料金を載せておきます。
5/5 ~5/11 (7日間) ※AWS利用開始~作業終了まで
ECSとCodeBuildだけ料金が発生していました。
ECSは無料枠がないので想定通り。
CodeBuildは毎月100hの無料枠がありますが、複数のプロジェクトがあるので、簡単に超えてしまいました。
他は無料枠に収まっています。