概要
CodePipelineでコンテナデプロイを実行するCICD構築をまとめました。
本記事は【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その2(CodeCommit, CodeBuild, CodePipeline)の続きです。
CodePipelineでのECSデプロイ環境をコード化し、CloudFormationで構築します。
目次
記事を3つに分割しました。
-
コンテナ構築 / 起動
1-1. Flaskアプリケーションの作成
1-2. ローカル環境でコンテナの作成 / 起動確認
1-3. ECRへイメージをプッシュ
1-4. ECSでコンテナ起動 -
CICDパイプラインの作成
2-1. CodeCommitの作成
2-2. CodeBuildの作成
2-3. CodePipelineの作成 -
上記のコンテナ / CICDパイプライン構築をコード化
3-1. CloudFormationテンプレート ←本記事はここから
前の記事はこちら
【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その1(ECR, ECS Fargate)
【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その2(CodeCommit, CodeBuild, CodePipeline)
構成図
3. ECSコンテナ / CICDパイプライン構築をコード化
パイプライン構築は扱うサービスも設定項目も多いので複数環境作成する際に手間がかかりますし、意図せぬ環境差分を作ってしまう恐れがあります。
そのため関連リソースをコード化してCloudFormationで構築する流れをまとめます。
リソース依存関係等を踏まえた上で下記の構築フローとなりました。
拡張性やファイル管理を考慮してなるべくAWSサービスごとにテンプレートファイルを分割しています。
- network.yml
- ecr.yml (イメージのプッシュまで)
- ecs.yml
- codecommit.yml (ローカルにクローン)
- s3.yml
- codebuild.yml (ビルドの実行確認)
- codepipeline.yml (パイプラインの実行確認)
- eventbridge.yml (ソースの更新、developブランチへマージ→パイプラインの起動確認)
※ymlファイルが長いため折りたたみで表示しています。
3-1. network.yml
ECSクラスター作成時にAWS側で自動作成していたネットワークリソースを作成します。
構築リソースは下記。
- VPC(&セキュリティグループ)
- サブネット
- ルートテーブル
- インターネットゲートウェイ
network.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for Network"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
VpcCidr:
Type: "String"
Default: "10.0.0.0/16"
Description: "VPC CIDR"
SubnetCidr:
Type: "String"
Default: "10.0.0.0/24"
Description: "Subnet CIDR"
AvailabilityZone:
Type: "String"
Default: "ap-northeast-1a"
Description: "Availability Zone"
########################################
# Resources
########################################
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-${Environment}-vpc"
# Subnet
Subnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref SubnetCidr
AvailabilityZone: !Ref AvailabilityZone
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-${Environment}-subnet"
# Internet Gateway
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-${Environment}-igw"
## InternetGatewayのアタッチ
IGWAttach:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref IGW
VpcId: !Ref VPC
# Route Table
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-${Environment}-rtb"
## パブリックルート
PublicRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref IGW
RouteTableId: !Ref RouteTable
## サブネットへの関連付け
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref Subnet
# Security Group
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${ProjectName}-${Environment}-sg-http"
GroupDescription: !Sub "${ProjectName}-${Environment}-sg-http"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: 80
ToPort: 80
CidrIp: "0.0.0.0/0"
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-${Environment}-sg-http"
########################################
# Output
########################################
Outputs:
VpcId:
Value: !Ref VPC
Export:
Name: "VpcId"
SubnetId:
Value: !Ref Subnet
Export:
Name: "SubnetId"
SecurityGroupId:
Value: !Ref SecurityGroup
Export:
Name: "SecurityGroupId"
3-2. ecr.yml
ECRリポジトリを作成します。
ecr.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for ECR"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
########################################
# Resources
########################################
Resources:
# ECR
ECR:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub "${ProjectName}-${Environment}-ecr"
########################################
# Output
########################################
Outputs:
RepositoryName:
Value: !Ref ECR
Export:
Name: "RepositoryName"
RepositoryUri:
Value: !GetAtt ECR.RepositoryUri
Export:
Name: "RepositryUri"
3-3. ecs.yml
ECS関連の下記リソースを作成します。
- ECSクラスター
- タスク定義
- ECSサービス
- ECS実行ロール
ecs.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for ECS"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
Image:
Type: "String"
Description: "Image Name"
ImageTag:
Type: "String"
Description: "Image Tag (e.g. 'latest')"
DesiredCount:
Type: "Number"
Default: 1
Description: "How many ECS tasks are running"
########################################
# Resources
########################################
Resources:
# ECSクラスター
EcsCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub "${ProjectName}-${Environment}-ecs-cluster"
# タスク定義
EcsTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub "${ProjectName}-${Environment}-ecs-task-definition"
NetworkMode: "awsvpc"
Cpu: 256
Memory: 512
ContainerDefinitions:
- Name: !Sub "${ProjectName}-${Environment}-ecs-container"
Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Image}:${ImageTag}"
ExecutionRoleArn: !GetAtt EcsTaskExecutionRole.Arn
## ECSタスク実行ロール
EcsTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
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"
RoleName: !Sub "${ProjectName}-${Environment}-role-ecs-task-exexution"
# ECSサービス
EcsService:
Type: AWS::ECS::Service
Properties:
LaunchType: "FARGATE"
Cluster: !Ref EcsCluster
TaskDefinition: !Ref EcsTaskDefinition
ServiceName: !Sub "${ProjectName}-${Environment}-ecs-service"
DesiredCount: !Ref DesiredCount
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: "ENABLED"
SecurityGroups:
- !ImportValue SecurityGroupId
Subnets:
- !ImportValue SubnetId
########################################
# Output
########################################
Outputs:
ClusterName:
Value: !Ref EcsCluster
Export:
Name: "ClusterName"
ClusterArn:
Value: !GetAtt EcsCluster.Arn
Export:
Name: "ClusterArn"
ContainerName:
Value: !Sub "${ProjectName}-${Environment}-ecs-container"
Export:
Name: "ContainerName"
ServiceName:
Value: !GetAtt EcsService.Name
Export:
Name: "ServiceName"
ServiceArn:
Value: !GetAtt EcsService.ServiceArn
Export:
Name: "ServiceArn"
タスクステータスがRUNNING
になったらパブリックIPにアクセスしindex.htmlが表示されるか確認します。
3-4. codecommit.yml
CodeCommitリポジトリを作成します。
codecommit.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for CodeCommit"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
########################################
# Resources
########################################
Resources:
#CodeCommitリポジトリ
Codecommit:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: !Sub "${ProjectName}-${Environment}-codecommit"
########################################
# Output
########################################
Outputs:
CodeCommitRepositoryName:
Value: !GetAtt Codecommit.Name
Export:
Name: "CodeCommitRepositoryName"
CodeCommitArn:
Value: !GetAtt Codecommit.Arn
Export:
Name: "CodeCommitArn"
CodeCommitCloneUrl:
Value: !GetAtt Codecommit.CloneUrlHttp
Export:
Name: "CodeCommitCloneUrl"
ローカル環境にクローンしてdevelopブランチを切っておきましょう。
3-5. s3.yml
アーティファクト保存用のS3バケットをCodeBuild、CodePipeline構築時に指定するため、前もって作成しておきます。
s3.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for S3"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
########################################
# Resources
########################################
Resources:
# アーティファクト用S3バケット
S3ArtifactCodePipeline:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${ProjectName}-${Environment}-s3-artifact-codepipeline"
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
########################################
# Output
########################################
Outputs:
S3ArtifactCodePipelineId:
Value: !Ref S3ArtifactCodePipeline
Export:
Name: "S3ArtifactCodePipelineId"
S3ArtifactCodePipelineArn:
Value: !GetAtt S3ArtifactCodePipeline.Arn
Export:
Name: "S3ArtifactCodePipelineArn"
3-6. codebuild.yml
CodeBuild関連の下記リソースを作成します。
- ビルドプロジェクト
- ビルド実行ロール
- ビルドロググループ
前もってSecretManagerにDockerHubのユーザーネーム、パスワードを保存しておきます。
SecretManagerは管理の安全上、コード化しない方が良いと考えたため、マネジメントコンソールからの保存で進めます。
CloudFormationに渡すパラメーターとしてSecretのIDは使うので控えておきましょう。
またこのタイミングでbuildspec.ymlは作成してdevelopブランチにプッシュしておきます。
codebuild.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for CodeBuild"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
SecretManager:
Type: String
Description: "Secret Name after 'secret: ' (e.g. my-secret-xxxxxx)"
########################################
# Resources
########################################
Resources:
# ビルドプロジェクト
Codebuild:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${ProjectName}-${Environment}-codebuild"
Source:
Type: "CODECOMMIT"
Location: !ImportValue CodeCommitCloneUrl
BuildSpec: "./buildspec.yml"
SourceVersion: "refs/heads/develop"
Artifacts:
Type: "no_artifacts"
Environment:
Type: "LINUX_CONTAINER"
ComputeType: "BUILD_GENERAL1_SMALL"
Image: "aws/codebuild/amazonlinux2-x86_64-standard:4.0"
PrivilegedMode: true
EnvironmentVariables:
- Name: "AWS_ACCOUNT_ID"
Value: !Sub ${AWS::AccountId}
Type: "PLAINTEXT"
- Name: "AWS_DEFAULT_REGION"
Value: !Sub ${AWS::Region}
Type: "PLAINTEXT"
- Name: "IMAGE_REPO_NAME"
Value: !ImportValue RepositoryName
Type: "PLAINTEXT"
- Name: "CONTAINER_NAME"
Value: !ImportValue ContainerName
Type: "PLAINTEXT"
LogsConfig:
CloudWatchLogs:
GroupName: !Ref LogGroupCodeBuild
Status: "ENABLED"
ServiceRole: !Ref IamRoleCodeBuild
## ビルド実行ロール
IamRoleCodeBuild:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-${Environment}-role-codebuild"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "codebuild.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser"
Policies:
- PolicyName: !Sub "${ProjectName}-${Environment}-policy-codebuild"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectName}-${Environment}-codebuild"
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectName}-${Environment}-codebuild:*"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- Effect: "Allow"
Resource: !Join
- ""
- - !ImportValue S3ArtifactCodePipelineArn
- "*"
Action:
- "s3:PutObject"
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketAcl"
- "s3:GetBucketLocation"
- Effect: "Allow"
Resource: !ImportValue CodeCommitArn
Action: "codecommit:GitPull"
- Effect: "Allow"
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${SecretManager}
Action: "secretsmanager:GetSecretValue"
## ビルドロググループ
LogGroupCodeBuild:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/codebuild/${ProjectName}-${Environment}-codebuild"
RetentionInDays: 30
########################################
# Output
########################################
Outputs:
CodeBuildProjectName:
Value: !Ref Codebuild
Export:
Name: "CodeBuildProjectName"
CodeBuildProjectArn:
Value: !GetAtt Codebuild.Arn
Export:
Name: "CodeBuildProjectArn"
3-7. codepipeline.yml
CodePipeline関連の下記リソースを作成します。
- CodePipeline
- パイプライン実行ロール
codepipeline.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for CodePipeline"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
########################################
# Resources
########################################
Resources:
# CodePipeline
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub "${ProjectName}-${Environment}-codepipeline"
RoleArn: !GetAtt IamRoleCodePipeline.Arn
ArtifactStore:
Type: "S3"
Location: !ImportValue S3ArtifactCodePipelineId
Stages:
- Name: "Source"
Actions:
- Name: "CodeCommit"
ActionTypeId:
Category: "Source"
Provider: "CodeCommit"
Owner: "AWS"
Version: 1
Configuration:
RepositoryName: !ImportValue CodeCommitRepositoryName
BranchName: "develop"
PollForSourceChanges: false
OutputArtifacts:
- Name: "SourceArtifact"
Namespace: "SourceVariables"
- Name: "Build"
Actions:
- Name: "Build"
ActionTypeId:
Category: "Build"
Provider: "CodeBuild"
Owner: "AWS"
Version: 1
Configuration:
ProjectName: !ImportValue CodeBuildProjectName
BatchEnabled: false
InputArtifacts:
- Name: "SourceArtifact"
OutputArtifacts:
- Name: "BuildArtifact"
Namespace: "BuildVariables"
- Name: "Deploy"
Actions:
- Name: "Deploy"
ActionTypeId:
Category: "Deploy"
Provider: "ECS"
Owner: "AWS"
Version: 1
Configuration:
ClusterName: !ImportValue ClusterName
ServiceName: !ImportValue ServiceName
FileName: "imagedefinitions.json"
InputArtifacts:
- Name: "BuildArtifact"
Namespace: "DeployVariables"
## CodePipeline用ロール
IamRoleCodePipeline:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-${Environment}-role-codepipeline"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "codepipeline.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: !Sub "${ProjectName}-${Environment}-policy-codepipeline"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Resource:
- !Sub "arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${ProjectName}-${Environment}-codepipeline"
Action:
- "codepipeline:*"
- Effect: "Allow"
Resource:
- !ImportValue CodeCommitArn
Action:
- "codecommit:CancelUploadArchive"
- "codecommit:GetBranch"
- "codecommit:GetCommit"
- "codecommit:GetRepository"
- "codecommit:GetUploadArchiveStatus"
- "codecommit:UploadArchive"
- Effect: "Allow"
Resource: !Join
- ""
- - !ImportValue S3ArtifactCodePipelineArn
- "*"
Action:
- "s3:PutObject"
- "s3:GetObject"
- "s3:GetObjectVersion"
- "s3:GetBucketAcl"
- "s3:GetBucketLocation"
- Effect: "Allow"
Resource: !ImportValue CodeBuildProjectArn
Action:
- "codebuild:BatchGetBuilds"
- "codebuild:StartBuild"
- "codebuild:BatchGetBuildBatches"
- "codebuild:StartBuildBatch"
- Effect: "Allow"
Resource: "*"
Action:
- "ecs:DescribeServices"
- "ecs:DescribeTaskDefinition"
- "ecs:DescribeTasks"
- "ecs:ListTasks"
- "ecs:RegisterTaskDefinition"
- "ecs:UpdateService"
- "iam:PassRole"
########################################
# Output
########################################
Outputs:
CodePipelineArn:
Value: !Sub "arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${ProjectName}-${Environment}-codepipeline"
Export:
Name: "CodePipelineArn"
3-8. eventbridge.yml
最後にAmazon EventBridgeを構築し、パイプラインの自動起動を確認します。
EventBridge関連の下記リソースを作成します。
- イベントルール
- EventBridge実行ロール
eventbridge.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Cloudformation template for Event Bridge"
########################################
# Parameters
########################################
Parameters:
ProjectName:
Type: "String"
Description: "Project Name"
Environment:
Type: "String"
AllowedValues:
- "dev"
- "stg"
- "prd"
Description: "Environment"
########################################
# Resources
########################################
Resources:
## Event Bridge
EventsRuleCodePipeline:
Type: AWS::Events::Rule
Properties:
Name: !Sub "${ProjectName}-${Environment}-event-rule-codepipeline"
EventPattern:
source:
- "aws.codecommit"
detail-type:
- "CodeCommit Repository State Change"
resources:
- !ImportValue CodeCommitArn
detail:
event:
- "referenceCreated"
- "referenceUpdated"
referenceType:
- "branch"
referenceName:
- "develop"
Targets:
- Arn: !ImportValue CodePipelineArn
Id: !Sub "${ProjectName}-${Environment}-event-rule-codepipeline"
RoleArn: !GetAtt IamRoleEventRule.Arn
## Event Bridge用ロール
IamRoleEventRule:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "events.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: !Sub "${ProjectName}-${Environment}-policy-event-rule-codepipeline"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Resource:
- !ImportValue CodePipelineArn
Action:
- "codepipeline:StartPipelineExecution"
RoleName: !Sub "${ProjectName}-${Environment}-role-event-rule-codepipeline"
まとめ
今回はECRの作成からCodePipelineの作成までをコード化してCloudFormationで構築する流れをまとめました。
マネジメントコンソールでリソース構築する際はIAMロール(ポリシー)など、ある程度AWS側で自動作成してくれますが、必要以上の権限が自動的に付与されていることがあります。
コード化する場合は、構築後の拡張性を持たせてあらかじめ権限範囲をある程度持たせておく等、そういった権限周りも自身で設計する必要がありますので難しいですね。
パラメーターに与えている内容等もより管理のしやすいように見直しが必要かもしれません。
前の記事はこちら
【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その1(ECR, ECS Fargate)
【AWS】 (初心者向け) CodePipelineを使ったECSコンテナのデプロイ自動化 その2(CodeCommit, CodeBuild, CodePipeline)