AWS CodeシリーズとECSを使ったCI/CD環境構築
はじめに
概要
AWS Code シリーズを使った CI/CD 環境の記事をあまり見かけないかも...という単純な理由から今回記事を書くことにしました。
デプロイ先は構成的にありがちな Amazon ECS をターゲットにしています。
今回の構成を利用するケースとしては下記を想定しています。
- 環境別(本番/開発など)へのデプロイを考慮した AWS Code シリーズ実装方式
- Amazon ECS へのアプリケーション開発における CI/CD 自動化(一部承認などは手動)
対象読者
今回の記事では、利用するサービスの詳細まで記載していません。
そのため下記サービスの概要レベルの知識を有していることを前提としています。
- AWS Code シリーズのサービス概要/機能概要
- ECS のサービス概要/機能概要 + ECR との連携
- IAM のサービス概要/機能概要
- ネットワークに関する一般的な知識
免責事項
本記事に掲載されている情報は筆者の個人的見解に基づくものであり、品質を保証するものではありません。皆様は自己責任での利用をお願いします。
また、本記事の情報に関連して発生した損失や被害については筆者は一切の責任を負いません。
全体概要図
いきなりですが、全体構成図はこんな感じです。
AWS Code シリーズを用いた CI/CD 全体概要構成図
構成図について
- 環境は、開発環境と本番環境の 2 面を想定しています。
- 環境に合わせて 2 つのパイプラインを構築しています。
- 本番環境へのデプロイの前には手動承認を入れています。
- 今回の環境ではアプリケーションのデプロイ先は ECS on fargate を使用します。
- アプリケーションの開発には Cloud9 を使用することを前提としています。(必須ではありません。)
- 開発したアプリケーションは ECR に Push してイメージを管理しています。
主に利用するサービスの概要説明
サービス名 | 概要説明 | 今回の利用用途/役割 |
---|---|---|
CodeCommit | プライベート Git リポジトリを安全にホストし、コードで共同作業する | コード管理 |
CodeBuild | 自動スケーリングによるコードのビルド | コードのビルド、コンテナイメージの作成 |
CodeDeploy | 様々なコンピューティングサービスへのデプロイを自動化する | アプリケーションのデプロイ |
CodePipeline | パイプラインの継続的デリバリーの自動化 | CI/CD パイプライン |
Amazon ECS | コンテナ化されたアプリケーションをデプロイ、管理する | ※本記事ではアプリのデプロイ先として使用 |
Amazon ECR | コンテナレジストリであり、アプリケーションイメージを管理する | コンテナイメージの格納先 |
Amazon EventBridge | イベント駆動で各種サービスのトリガーを実施するサービス | パイプラインの起動などのトリガー |
Amazon SNS | フルマネージドの Pub/Sub サービス | 承認用のメール通知 |
IAM | ID と AWS のサービスおよびリソースへのアクセス、権限管理を行う | 権限制御 |
AWS Cloud9 | コードを記述、実行、デバッグできるクラウドベースの統合開発環境 (IDE) | コード開発 |
処理方式
デプロイまでの処理方式について記載します。
基本的に環境でリリース処理を分割しているので、開発環境向けと本番環境向けの 2 つの処理フローがあります。
開発環境向けの処理方式
開発環境向け処理フロー
-
ソースコードを clone
CodeCommit から ソースファイルを clone する。 -
対象ブランチへの Push
ソースファイルの修正を実施し、開発用ブランチ(以降、develop ブランチ)に Push する。 -
パイプラインの起動
develop ブランチへのイベントをトリガーに EventBridge が CodePipeline を起動する。 -
ビルド処理
4-1. CodeCommit をコード元としてビルド処理を実行する。
4-2. ビルドして作成されたコンテナイメージを ECR に Push する。 -
デプロイ処理
前処理で作成された ECR 上のコンテナイメージを ECS にデプロイを実施する。
本番環境向けの処理方式
本番環境向け処理フロー
-
プルリクエストのマージ
CodeCommit で develop ブランチをベースにプルリクエストを作成し、本番用ブランチ(以降、main ブランチ)にマージする。 -
パイプラインの起動
main ブランチへのイベントをトリガーに EventBridge が CodePipeline を起動する。 -
ビルド処理
3-1. CodeCommit をコード元としてビルド処理を実行する。
3-2. ビルドして作成されたコンテナイメージを ECR に Push する。 -
手動承認処理
4-1. パイプラインの手動承認ステージで Amazon SNS をトリガーして承認者宛てにメールを送付する。
4-2. 承認者が処理内容を確認し、デプロイを承認するか否かを決定する。
申請を承認した場合は処理が継続され、却下した場合は処理終了となる。 -
デプロイ処理
前処理で作成された ECR 上のコンテナイメージを ECS にデプロイする。
実際の各種処理の設定がどうなっているのかは次項目以降で記載していきます。
構築・説明
0. 前提事項
- Code シリーズの実装方式・設定内容を重点的に記載しています。
- ネットワークおよびデプロイ先となる環境(ECS)は既に構築済みと想定して詳細を記載しません。
- 本記事で利用したネットワークや ECS などを構築する CloudFormation のコードは本記事の最後に記載しています。
1. CodeCommit の作成
まずは CodeCommit でリポジトリを作成します。
作成後、本番環境用ブランチ(main)と開発環境用ブランチ(develop)を作成します。
※main が デフォルトブランチになるように設定します。
作成したリポジトリには下記のファイル群を Push します。
各ファイルは使用する工程で内容を記載します。
ファイル名 | 内容 |
---|---|
buildspec.yaml | CodeBuild で使用されるビルドの処理が記載されたファイル |
taskdef.json | ECS のタスク定義を更新するためのファイル |
appspec.yaml | CodeDeploy で使用されるデプロイ処理が記載されたファイル |
src/Dockerfile | コンテナイメージ作成用ファイル |
src/index.html | WEB アプリケーションファイル(テスト用) |
ここではテスト用に利用するアプリ作成のための Dockerfile
と index.html
だけ載せておきます。
FROM nginx:latest
COPY index.html /usr/share/nginx/html
EXPOSE 80
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>test-web</title>
</head>
<body>
<h1>Welcome to Test Website!!</h1>
<a>This is website on ecs fargate!!</a>
</body>
</html>
2. CodeBuild の作成
次に CodeBuild にビルドプロジェクトを作成します。
ちょっと長いですが、設定項目は下記の通りです。
ポイントを記載します。
- ソース
- ソースプロバイダは先ほど作成した CodeCommit を指定しています。
- 開発環境向けのビルドプロジェクトは
develop
ブランチ、本番環境向けはmain
ブランチを指定します。
- Buildspec
- Buildspec 名には、先ほど Codecommit に Push した
buildspec.yaml
を設定します。 -
buildspec.yaml
のコードは下記に記載します。
- Buildspec 名には、先ほど Codecommit に Push した
- 環境
- 追加設定の環境変数に下記の項目の値を設定します。
- AWS_ACCOUNT_ID
- AWS_DEFAULT_REGION
- IMAGE_REPO_NAME
- IMAGE_TAG
- TASK_ROLE_ARN
- EXECUTION_ROLE_ARN
- TASK_FAMILY
- CONTAINER_NAME
- 追加設定の環境変数に下記の項目の値を設定します。
CodeBuild の環境変数設定
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG ./src
- echo docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- printf '{"Version":"1.0","ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json
- sed -i -e "s#<CONTAINER_NAME>#${CONTAINER_NAME}#" taskdef.json
- sed -i -e "s#<TASK_FAMILY>#${TASK_FAMILY}#" taskdef.json
- sed -i -e "s#<TASK_ROLE_ARN>#${TASK_ROLE_ARN}#" taskdef.json
- sed -i -e "s#<EXECUTION_ROLE_ARN>#${EXECUTION_ROLE_ARN}#" taskdef.json
- sed -i -e "s#<CONTAINER_NAME>#${CONTAINER_NAME}#" appspec.yaml
artifacts:
files:
- imageDetail.json
- taskdef.json
- appspec.yaml
このbuildspec.yaml
の内容を端的に説明すると、ECR へのログインを実施後に docker ビルドでアプリのコンテナイメージを作成し、ECR にプッシュしています。
また post_build ステージ の ECR にプッシュ(docker push
)後に実施している処理は、各ファイル内に環境固有の文字列(アカウント ID を含む ARN、デプロイ先のコンテナ名など)が含まれているため、少し力技になりますがこのビルド処理の中で文字列を置換しています。(CodeBuild の環境変数として設定した値が入力されます。)
参考までに CodeBuild のビルドプロジェクト作成の CloudFormation コードの載せておきますので参考にしてみてください。
CodeBuild 構築用の CoudFormation コード
AWSTemplateFormatVersion: "2010-09-09"
Description: "Sample CodeBuild"
# Parameters
Parameters:
### Env Prefix ###
EnvPrefix:
Type: String
# Resources
Resources:
# IAM Role
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "CodeBuildBasePolicy-sample-${EnvPrefix}-build-role"
Path: /
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: !Sub "CodeBuildBasePolicy-sample-${EnvPrefix}-build-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- ecr:BatchCheckLayerAvailability
- ecr:CompleteLayerUpload
- ecr:GetAuthorizationToken
- ecr:InitiateLayerUpload
- ecr:PutImage
- ecr:UploadLayerPart
Effect: Allow
Resource: "*"
- Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/sample-${EnvPrefix}-build"
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/sample-${EnvPrefix}-build:*"
- Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
Effect: Allow
Resource:
- !Sub "arn:aws:s3:::codepipeline-${AWS::Region}-*"
- Action:
- codecommit:GitPull
Effect: Allow
Resource:
- !Sub "arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:sample-${EnvPrefix}-repo"
- Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- codebuild:BatchPutCodeCoverages
Effect: Allow
Resource:
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/sample-${EnvPrefix}-build*"
# Code Build
CodeBuildProject:
Type: "AWS::CodeBuild::Project"
Properties:
Name: !Sub "sample-${EnvPrefix}-build"
Source:
BuildSpec: "buildspec.yml "
GitCloneDepth: 1
GitSubmodulesConfig:
FetchSubmodules: false
InsecureSsl: false
Location: !Sub "https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/sample-${EnvPrefix}-repo"
Type: "CODECOMMIT"
Artifacts:
Type: "NO_ARTIFACTS"
Cache:
Type: "NO_CACHE"
Environment:
ComputeType: "BUILD_GENERAL1_SMALL"
EnvironmentVariables:
- Name: "AWS_ACCOUNT_ID"
Type: "PLAINTEXT"
Value: !Sub ${AWS::AccountId}
- Name: "AWS_DEFAULT_REGION"
Type: "PLAINTEXT"
Value: !Sub ${AWS::Region}
- Name: "IMAGE_REPO_NAME"
Type: "PLAINTEXT"
Value: !Sub "sample-${EnvPrefix}-app"
- Name: "IMAGE_TAG"
Type: "PLAINTEXT"
Value: "latest"
- Name: "TASK_ROLE_ARN"
Type: "PLAINTEXT"
Value: !Sub "arn:aws:iam::${AWS::AccountId}:role/${EnvPrefix}-ecsTaskExecutionRole"
- Name: "EXECUTION_ROLE_ARN"
Type: "PLAINTEXT"
Value: !Sub "arn:aws:iam::${AWS::AccountId}:role/${EnvPrefix}-ecsTaskExecutionRole"
- Name: "TASK_FAMILY"
Type: "PLAINTEXT"
Value: !Sub "sample-${EnvPrefix}-cluster-task"
- Name: "CONTAINER_NAME"
Type: "PLAINTEXT"
Value: !Sub "sample-${EnvPrefix}-app"
Image: "aws/codebuild/amazonlinux2-x86_64-standard:corretto11"
ImagePullCredentialsType: "CODEBUILD"
PrivilegedMode: false
Type: "LINUX_CONTAINER"
ServiceRole: !GetAtt CodeBuildServiceRole.Arn
TimeoutInMinutes: 15
QueuedTimeoutInMinutes: 480
EncryptionKey: !Sub "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3"
BadgeEnabled: false
LogsConfig:
CloudWatchLogs:
Status: "ENABLED"
S3Logs:
Status: "DISABLED"
EncryptionDisabled: false
Visibility: "PRIVATE"
ちなみに下記が taskdef.json
と appspec.yaml
の内容です。
{
"containerDefinitions": [
{
"name": "<CONTAINER_NAME>",
"image": "<IMAGE1_NAME>",
"portMappings": [
{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp"
}
]
}
],
"family": "<TASK_FAMILY>",
"taskRoleArn": "<TASK_ROLE_ARN>",
"executionRoleArn": "<EXECUTION_ROLE_ARN>",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "1024",
"memory": "3072"
}
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "<CONTAINER_NAME>"
ContainerPort: 80
3. CodeDeploy の作成
次に CodeDeploy を構築します。
CodeDeploy はデプロイするアプリケーションの設定とデプロイグループを設定する必要があります。
また CodeBuild では buildspec.yaml
にビルドで実行される内容を記載しましたが、CodeDeploy では appspec.yaml
にデプロイ内容を記載します。
アプリケーションの作成
デプロイグループの作成
今回は ECS へのデプロイのため、デプロイパターンは Blue/Green で実装しており、デプロイ設定は All at Once にしています。
その他の項目は AWS 公式ページ(Blue/Green デプロイを使用した CodeDeploy)に記載があるため、そちらを参照してください。
4. CodePipeline の作成
続いて、CodePipeline を作成します。
その事前作業として、本番環境向けのパイプラインで使用する手動承認
のための SNS トピックおよびサブスクリプションを作成します。
SNS トピックの作成
SNS サブスクリプションの作成
エンドポイントに設定したアドレスにメールが届くので、サブスクリプションを確認
のリンクをクリックすれば完了です。
ここまでが事前準備で、本題のパイプライン作成をしていきます。
パイプラインの設定
ソースの設定
ビルドの設定
デプロイの設定
各設定項目には、SourceArtifact
とBuildArtifact
があります。
SourceArtifact は、ソースコードとしてリポジトリに格納したコードを使用してデプロイ処理を実施し、BuildArtifact はビルド処理で生成されたファイルを使用して処理を実施します。
今回は「CodeBuild の作成」でも記載した通り、buildspec.yaml
の中で変数を置換したりする処理を実施しているため、BuildArtifact を設定しています。
また本番環境向けのパイプラインには、手動承認のステージを追加します。
ここで先ほど作成した SNS トピックの ARN を設定します。
手動承認の追加
5. 動かしてみる
ここまでの作業で構築が完了したので、実際に起動してみます。
-
まずは Cloud9 でアプリケーションコードを修正し、リポジトリに Push します。
$ git add . $ git commit -m "fix html." $ git push origin develop
-
ブランチへのイベントをトリガーにパイプラインが起動します。
パイプラインの処理が無事に成功しました。
-
次に
develop
ブランチをmain
ブランチにマージします。(プルリクエスト作成・実行)
-
上記のブランチへのイベントをトリガーにパイプラインが起動します。
本番環境向けのパイプラインには手動承認があるため、承認処理でパイプラインが待機します。
-
先ほど設定した SNS トピックを経由して承認者にメールが届きます。
本文の中の「Approve or reject」に記載された URL をクリックすると、パイプラインの承認画面に遷移します。
レビューボダンをクリックすると、承認か却下を選択できる画面が出てくるので、入力します。
-
承認されると処理が再開します。もちろん却下すると処理はそこで終了します。
無事に処理が完了しました。
おわりに
今回は Code シリーズを使用して CI/CD パイプラインの構築を実施してみました。
本記事内では実装していないのですが、ビルドステージでのテスト自動化や他のサービスとの連携する(CodeCommit の代わりに GitHub などを使用する)のも面白そうだと思いました。
本記事の構成は最低限の処理内容なのですが、実案件対応時の参考になれば幸いです。
おまけ:サンプルコード(簡易版ハンズオンのコード置き場)
本記事の検証で使用した構成のうち、Code シリーズ以外のリソースを作成する CloudFormation を記載します。何かの参考になれば幸いです。
構築順序
下記表の順番でファイル単位にスタックを作成することで環境を構築出来ます。
※各スタック間での依存関係があるため、項番通りに実施しないとエラーになります。
項番 | CFn ファイル | リソース |
---|---|---|
1 | network.yaml | VPC、サブネット、ルートテーブルなどのネットワーク全般 |
2 | securitygroup.yaml | ECS や ALB にアタッチするセキュリティグループ |
3 | ecr.yaml | ECR のリポジトリ |
4 | webapp.yaml | ECS on Fargate やそれに関連するサービス(IAM、ALB など) |
コード
AWSTemplateFormatVersion: "2010-09-09"
Description: Test-WebSite-Network-Template
# Parameters
Parameters:
### Env Prefix ###
EnvPrefix:
Type: String
### VPC ###
VPCCIDR:
Type: String
Default: "10.100.0.0/20"
### Public Subnet ###
PublicSubnetACIDR:
Type: String
Default: "10.100.1.0/24"
PublicSubnetCCIDR:
Type: String
Default: "10.100.2.0/24"
### Private Subnet ###
PrivateSubnetACIDR:
Type: String
Default: "10.100.3.0/24"
PrivateSubnetCCIDR:
Type: String
Default: "10.100.4.0/24"
### Resources ###
Resources:
# VPC
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-vpc"
# IGW
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-igw"
IGWAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# NatGW
### NAT Gateway ###
NatGatewayA:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- NatGatewayEIPA
- AllocationId
SubnetId: !Ref PublicSubnetA
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-NAT-Gateway-A"
# NAT-GW
NatGatewayC:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- NatGatewayEIPC
- AllocationId
SubnetId: !Ref PublicSubnetC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-NAT-Gateway-C"
### NAT Gateway EIP ###
NatGatewayEIPA:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-NGW-EIP-A"
# NAT-GW
NatGatewayEIPC:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-NGW-EIP-C"
# Subnet
### Public Subnet ###
PublicSubnetA:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PublicSubnetACIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-public-subnet-a"
PublicSubnetC:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PublicSubnetCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-public-subnet-c"
### Private Subnet ###
PrivateSubnetA:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PrivateSubnetACIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-private-subnet-a"
PrivateSubnetC:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PrivateSubnetCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-private-subnet-c"
# RouteTable
### Public Subnet A Routing ###
PublicARTB:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-public-route-a"
PublicASubnetRTBAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref PublicARTB
PublicARoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicARTB
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
### Public Subnet C Routing ###
PublicCRTB:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-public-route-c"
PublicCSubnetRTBAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetC
RouteTableId: !Ref PublicCRTB
PublicCRoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicCRTB
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
### Private Subnet A Routing ###
PrivateARTB:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-private-route-a"
PrivateSubnetRTBAAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnetA
RouteTableId: !Ref PrivateARTB
PrivateRouteA01:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PrivateARTB
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NatGatewayA
### Private Subnet C Routing ###
PrivateCRTB:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvPrefix}-private-route-c"
PrivateSubnetRTBCAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnetC
RouteTableId: !Ref PrivateCRTB
PrivateRouteC01:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PrivateCRTB
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NatGatewayA
# Output
Outputs:
### VPC ###
## VPC ID ##
VPC:
Value: !Ref VPC
Export:
Name: !Sub "${EnvPrefix}-vpc"
## VPC CIDR ##
VPCCidr:
Value: !Ref VPCCIDR
Export:
Name: !Sub "${EnvPrefix}-vpc-cidr"
### Subnet ###
## Public Subnet A ##
PublicSubnetA:
Value: !Ref PublicSubnetA
Export:
Name: !Sub "${EnvPrefix}-public-subnet-a"
## Public Subnet C ##
PublicSubnetC:
Value: !Ref PublicSubnetC
Export:
Name: !Sub "${EnvPrefix}-public-subnet-c"
## Private Subnet A ##
PrivateSubnetA:
Value: !Ref PrivateSubnetA
Export:
Name: !Sub "${EnvPrefix}-private-subnet-a"
## Private Subnet C ##
PrivateSubnetC:
Value: !Ref PrivateSubnetC
Export:
Name: !Sub "${EnvPrefix}-private-subnet-c"
### NAT-GW ###
## NAT-GW-A ##
NatGatewayEIPA:
Value: !Ref NatGatewayEIPA
Export:
Name: !Sub "${EnvPrefix}-natgw-a"
## NAT-GW-C ##
NatGatewayEIPC:
Value: !Ref NatGatewayEIPC
Export:
Name: !Sub "${EnvPrefix}-natgw-c"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Sample Security Group"
# Parameters
Parameters:
### Env Prefix ###
EnvPrefix:
Type: String
# Resources
Resources:
ALBSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: !Sub "sample-${EnvPrefix}-webapp-alb-sg"
GroupName: !Sub "sample-${EnvPrefix}-webapp-alb-sg"
VpcId: !ImportValue dev-vpc
SecurityGroupIngress:
- CidrIp: !ImportValue dev-vpc-cidr
IpProtocol: "-1"
- CidrIp: !Sub
- ${Natgweipa}/32
- Natgweipa: { Fn::ImportValue: dev-natgw-a }
IpProtocol: "-1"
- CidrIp: !Sub
- ${Natgweipc}/32
- Natgweipa: { Fn::ImportValue: dev-natgw-c }
IpProtocol: "-1"
SecurityGroupEgress:
- CidrIp: "0.0.0.0/0"
IpProtocol: "-1"
ECSSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: !Sub "sample-${EnvPrefix}-webapp-ecs-sg"
GroupName: !Sub "sample-${EnvPrefix}-webapp-ecs-sg"
VpcId: !ImportValue dev-vpc
SecurityGroupIngress:
- SourceSecurityGroupId: !Ref ALBSecurityGroup
SourceSecurityGroupOwnerId: !Ref AWS::AccountId
IpProtocol: "-1"
SecurityGroupEgress:
- CidrIp: "0.0.0.0/0"
IpProtocol: "-1"
# Output
Outputs:
ALBSecurityGroupId:
Value: !Ref ALBSecurityGroup
Export:
Name: !Sub "${EnvPrefix}-alb-sg-id"
ECSSecurityGroupId:
Value: !Ref ECSSecurityGroup
Export:
Name: !Sub "${EnvPrefix}-ecs-sg-id"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Sample ECR Repository"
# Parameters
Parameters:
### Env Prefix ###
EnvPrefix:
Type: String
# Resources
Resources:
ECRRepository:
Type: "AWS::ECR::Repository"
Properties:
RepositoryName: !Sub "sample-${EnvPrefix}-app"
EncryptionConfiguration:
EncryptionType: KMS
# Output
Outputs:
ECRArn:
Value: !GetAtt ECRRepository.Arn
AWSTemplateFormatVersion: "2010-09-09"
Description: "Sample ECS and ALB"
# Parameters
Parameters:
### Env Prefix ###
EnvPrefix:
Type: String
Resources:
# IAM Role
EcsTaskExecutionRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub "${EnvPrefix}-ecsTaskExecutionRole"
Path: /
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: !Sub "sample-${EnvPrefix}-cluster"
CapacityProviders:
- "FARGATE"
- "FARGATE_SPOT"
ECSService:
Type: "AWS::ECS::Service"
Properties:
ServiceName: !Sub "sample-${EnvPrefix}-app-service"
Cluster: !GetAtt ECSCluster.Arn
LoadBalancers:
- TargetGroupArn: !Ref ElasticLoadBalancingV2TargetGroup
ContainerName: !Sub "sample-${EnvPrefix}-app"
ContainerPort: 80
DesiredCount: 1
LaunchType: "FARGATE"
PlatformVersion: "1.4.0"
TaskDefinition: !Ref ECSTaskDefinition
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 100
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: "DISABLED"
SecurityGroups:
- !ImportValue dev-ecs-sg-id
Subnets:
- !ImportValue dev-private-subnet-a
- !ImportValue dev-private-subnet-c
HealthCheckGracePeriodSeconds: 0
SchedulingStrategy: "REPLICA"
DeploymentController:
Type: "CODE_DEPLOY"
ECSTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
ContainerDefinitions:
- Essential: true
Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/sample-${EnvPrefix}-app:latest"
Name: !Sub "sample-${EnvPrefix}-app"
PortMappings:
- ContainerPort: 80
HostPort: 80
Protocol: "tcp"
Family: !Sub "${ECSCluster}-task"
TaskRoleArn: !Ref EcsTaskExecutionRole
ExecutionRoleArn: !Ref EcsTaskExecutionRole
NetworkMode: "awsvpc"
RequiresCompatibilities:
- "FARGATE"
Cpu: "1024"
Memory: "3072"
ElasticLoadBalancingV2LoadBalancer:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: "sample-dev-app-alb"
Scheme: "internet-facing"
Type: "application"
Subnets:
- !ImportValue dev-public-subnet-a
- !ImportValue dev-public-subnet-c
SecurityGroups:
- !ImportValue dev-alb-sg-id
IpAddressType: "ipv4"
LoadBalancerAttributes:
- Key: "access_logs.s3.enabled"
Value: "false"
- Key: "idle_timeout.timeout_seconds"
Value: "60"
- Key: "deletion_protection.enabled"
Value: "false"
- Key: "routing.http2.enabled"
Value: "true"
- Key: "routing.http.drop_invalid_header_fields.enabled"
Value: "false"
- Key: "routing.http.xff_client_port.enabled"
Value: "false"
- Key: "routing.http.preserve_host_header.enabled"
Value: "false"
- Key: "routing.http.xff_header_processing.mode"
Value: "append"
- Key: "load_balancing.cross_zone.enabled"
Value: "true"
- Key: "routing.http.desync_mitigation_mode"
Value: "defensive"
- Key: "waf.fail_open.enabled"
Value: "false"
- Key: "routing.http.x_amzn_tls_version_and_cipher_suite.enabled"
Value: "false"
- Key: "connection_logs.s3.enabled"
Value: "false"
ElasticLoadBalancingV2Listener:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
LoadBalancerArn: !Ref ElasticLoadBalancingV2LoadBalancer
Port: 80
Protocol: "HTTP"
DefaultActions:
- TargetGroupArn: !Ref ElasticLoadBalancingV2TargetGroup
Type: "forward"
ElasticLoadBalancingV2TargetGroup:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckPath: "/"
Port: 80
Protocol: "HTTP"
HealthCheckPort: "traffic-port"
HealthCheckProtocol: "HTTP"
HealthCheckTimeoutSeconds: 5
UnhealthyThresholdCount: 2
TargetType: "ip"
Matcher:
HttpCode: "200"
HealthyThresholdCount: 5
VpcId: !ImportValue dev-vpc
Name: !Sub "sample-${EnvPrefix}-app-tg"
HealthCheckEnabled: true
TargetGroupAttributes:
- Key: "target_group_health.unhealthy_state_routing.minimum_healthy_targets.percentage"
Value: "off"
- Key: "deregistration_delay.timeout_seconds"
Value: "300"
- Key: "stickiness.type"
Value: "lb_cookie"
- Key: "stickiness.lb_cookie.duration_seconds"
Value: "86400"
- Key: "slow_start.duration_seconds"
Value: "0"
- Key: "stickiness.app_cookie.duration_seconds"
Value: "86400"
- Key: "target_group_health.dns_failover.minimum_healthy_targets.percentage"
Value: "off"
- Key: "load_balancing.cross_zone.enabled"
Value: "use_load_balancer_configuration"
- Key: "load_balancing.algorithm.type"
Value: "round_robin"
- Key: "target_group_health.unhealthy_state_routing.minimum_healthy_targets.count"
Value: "1"
- Key: "stickiness.enabled"
Value: "false"
- Key: "target_group_health.dns_failover.minimum_healthy_targets.count"
Value: "1"
- Key: "load_balancing.algorithm.anomaly_mitigation"
Value: "off"
- Key: "stickiness.app_cookie.cookie_name"
Value: ""
ElasticLoadBalancingV2ListenerTest:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
LoadBalancerArn: !Ref ElasticLoadBalancingV2LoadBalancer
Port: 8080
Protocol: "HTTP"
DefaultActions:
- TargetGroupArn: !Ref ElasticLoadBalancingV2TargetGroupTest
Type: "forward"
ElasticLoadBalancingV2TargetGroupTest:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckPath: "/"
Port: 8080
Protocol: "HTTP"
HealthCheckPort: "traffic-port"
HealthCheckProtocol: "HTTP"
HealthCheckTimeoutSeconds: 5
UnhealthyThresholdCount: 2
TargetType: "ip"
Matcher:
HttpCode: "200"
HealthyThresholdCount: 5
VpcId: !ImportValue dev-vpc
Name: !Sub "sample-${EnvPrefix}-app-tg-test"
HealthCheckEnabled: true
TargetGroupAttributes:
- Key: "target_group_health.unhealthy_state_routing.minimum_healthy_targets.percentage"
Value: "off"
- Key: "deregistration_delay.timeout_seconds"
Value: "300"
- Key: "stickiness.type"
Value: "lb_cookie"
- Key: "stickiness.lb_cookie.duration_seconds"
Value: "86400"
- Key: "slow_start.duration_seconds"
Value: "0"
- Key: "stickiness.app_cookie.duration_seconds"
Value: "86400"
- Key: "target_group_health.dns_failover.minimum_healthy_targets.percentage"
Value: "off"
- Key: "load_balancing.cross_zone.enabled"
Value: "use_load_balancer_configuration"
- Key: "load_balancing.algorithm.type"
Value: "round_robin"
- Key: "target_group_health.unhealthy_state_routing.minimum_healthy_targets.count"
Value: "1"
- Key: "stickiness.enabled"
Value: "false"
- Key: "target_group_health.dns_failover.minimum_healthy_targets.count"
Value: "1"
- Key: "load_balancing.algorithm.anomaly_mitigation"
Value: "off"
- Key: "stickiness.app_cookie.cookie_name"
Value: ""
# Output Parameter
Outputs:
ElasticLoadBalancingV2LoadBalancerInfo:
Value: !Ref ElasticLoadBalancingV2LoadBalancer
Export:
Name: !Sub "${EnvPrefix}-alb"
Discussion