以下のブログでインプレースデプロイを使用してAutoScalingグループのEC2にデプロイするCI/CD環境をCloudFormationで作成しました。
今回はBlue/GreenデプロイでAutoScalingグループにデプロイする設定をやってみます。
インプレースデプロイとBlue/Greenデプロイ
インプレースデプロイは既に動いている環境に対してデプロイする方法です。
既存の環境を使用するのでBlue/Greenデプロイに比べるとコストが低くなります。
インプレースデプロイの概要
Blue/Greenデプロイは新しくデプロイ先の環境を作成してデプロイする方法です。
既に動いている環境 (Blue) から新しい環境 (Green) へ切り替えることでデプロイが完了します。
インプレースデプロイと違い新しい環境 (Green) が作成されるのでコストは上がりますが、Green環境で問題があった際にBlue環境へロールバックを素早く行うことが可能となります。
Blue/Green デプロイの概要
作成してみた
CodeDeployの部分以外は基本的に以下のブログと同じなのでCloudFormationテンプレートと実行コマンドだけ記載する形にしています。
CloudFormationでCodeDeployを作成する場合Blue/GreenデプロイはLambdaにしか対応していない (2023/01/30) ので、CodeDeployはマネジメントコンソールから設定します。
DeploymentStyle
For blue/green deployments, AWS CloudFormation supports deployments on Lambda compute platforms only. You can perform Amazon ECS blue/green deployments using AWS::CodeDeploy::BlueGreen hook. See Perform Amazon ECS blue/green deployments through CodeDeploy using AWS CloudFormation for more information.
IAMロール + アーティファクト用S3 + ネットワーク周り + ゴールデンAMI作成用EC2
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Description: CI/CD test Stack
Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Parameters for VPC
Parameters:
- VPCCIDR
- Label:
default: Parameters for Subnet
Parameters:
- PublicSubnet01CIDR
- PublicSubnet02CIDR
- PrivateSubnet01CIDR
- PrivateSubnet02CIDR
- Label:
default: Parameters for ec2
Parameters:
- EC2VolumeSize
- EC2VolumeIOPS
- EC2AMI
- EC2InstanceType
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
VPCCIDR:
Default: 172.30.0.0/16
Type: String
PublicSubnet01CIDR:
Default: 172.30.1.0/24
Type: String
PublicSubnet02CIDR:
Default: 172.30.2.0/24
Type: String
PrivateSubnet01CIDR:
Default: 172.30.3.0/24
Type: String
PrivateSubnet02CIDR:
Default: 172.30.4.0/24
Type: String
EC2VolumeSize:
Default: 32
Type: Number
EC2VolumeIOPS:
Default: 3000
Type: Number
EC2AMI:
Default: ami-0bba69335379e17f8
Type: AWS::EC2::Image::Id
EC2InstanceType:
Default: t3.micro
Type: String
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
BucketName: !Sub ${AWS::StackName}-${AWS::AccountId}-artifact
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-${AWS::AccountId}-artifact
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------#
EC2IAMPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "s3:GetObject"
- "s3:ListBucket"
Resource:
- !Join
- ''
- - !GetAtt S3.Arn
- '/*'
- !GetAtt S3.Arn
ManagedPolicyName: iam-policy-deploy-ec2
EC2IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- !Ref EC2IAMPolicy
RoleName: iam-role-ec2
Tags:
- Key: Name
Value: iam-role-ec2
EC2IAMInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: iam-instanceprofile-ec2
Roles:
- !Ref EC2IAMRole
CodeDeployIAMPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "iam:PassRole"
- "ec2:RunInstances"
- "ec2:CreateTags"
Resource:
- "*"
ManagedPolicyName: iam-policy-codedeploy
CodeDeployIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- codedeploy.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole
RoleName: iam-role-codedeploy
Tags:
- Key: Name
Value: iam-role-codedeploy
CodePipelineIAMPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "codecommit:CancelUploadArchive"
- "codecommit:GetBranch"
- "codecommit:GetCommit"
- "codecommit:GetRepository"
- "codecommit:GetUploadArchiveStatus"
- "codecommit:UploadArchive"
Resource:
- "*"
- Effect: Allow
Action:
- "codedeploy:CreateDeployment"
- "codedeploy:GetApplication"
- "codedeploy:GetApplicationRevision"
- "codedeploy:GetDeployment"
- "codedeploy:GetDeploymentConfig"
- "codedeploy:RegisterApplicationRevision"
Resource:
- "*"
- Effect: Allow
Action:
- "s3:GetObject"
- "s3:PutObject"
- "s3:ListBucket"
Resource:
- !Join
- ''
- - !GetAtt S3.Arn
- '/*'
- !GetAtt S3.Arn
- Effect: Allow
Action:
- "sns:Publish"
Resource:
- "*"
ManagedPolicyName: iam-policy-codepipeline
CodePipelineIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- !Ref CodePipelineIAMPolicy
RoleName: iam-role-codepipeline
Tags:
- Key: Name
Value: iam-role-codepipeline
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: test-vpc
# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------#
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub test-igw
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------#
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: !Ref PublicSubnet01CIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub test-public-subnet-01
VpcId: !Ref VPC
PublicSubnet02:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1c
CidrBlock: !Ref PublicSubnet02CIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub test-public-subnet-02
VpcId: !Ref VPC
PrivateSubnet01:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1a
CidrBlock: !Ref PrivateSubnet01CIDR
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub test-private-subnet-01
VpcId: !Ref VPC
PrivateSubnet02:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: ap-northeast-1c
CidrBlock: !Ref PrivateSubnet02CIDR
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub test-private-subnet-02
VpcId: !Ref VPC
# ------------------------------------------------------------#
# NatGateWay
# ------------------------------------------------------------#
NatGateWayEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: eip-natgw
NatGateWay:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateWayEIP.AllocationId
ConnectivityType: public
SubnetId: !Ref PublicSubnet01
Tags:
- Key: Name
Value: test-natgw-1a
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: test-public-rtb
PublicRouteTableRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref PublicRouteTable
PublicRtAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet01
PublicRtAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet02
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: test-private-rtb
PrivateRouteTableRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateWay
RouteTableId: !Ref PrivateRouteTable
PrivateRtAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet01
PrivateRtAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet02
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
ALBSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for alb
GroupName: test-sg-alb
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
FromPort: 80
IpProtocol: tcp
ToPort: 80
Tags:
- Key: Name
Value: test-sg-alb
VpcId: !Ref VPC
EC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: for ec2
GroupName: test-sg-ec2-web
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
SecurityGroupIngress:
- FromPort: 80
IpProtocol: tcp
SourceSecurityGroupId: !Ref ALBSG
ToPort: 80
Tags:
- Key: Name
Value: test-sg-ec2-web
VpcId: !Ref VPC
# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------#
EC2:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
Encrypted: true
Iops: !Ref EC2VolumeIOPS
VolumeSize: !Ref EC2VolumeSize
VolumeType: gp3
DisableApiTermination: false
IamInstanceProfile: !Ref EC2IAMInstanceProfile
ImageId: !Ref EC2AMI
InstanceType: !Ref EC2InstanceType
NetworkInterfaces:
- DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref EC2SG
SubnetId: !Ref PrivateSubnet01
Tags:
- Key: Name
Value: test-ec2-ami
UserData: !Base64 |
#!/bin/bash
yum update -y
yum install ruby -y
wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install
chmod +x ./install
./install auto
service codedeploy-agent start
yum install httpd -y
echo "CodeDeploy Test" > /var/www/html/index.html
systemctl start httpd
systemctl enable httpd
Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
CodeDeployIAMRoleARN:
Value: !GetAtt CodeDeployIAMRole.Arn
Export:
Name: iam-role-codedeploy-arn
CodePipelineIAMRoleARN:
Value: !GetAtt CodePipelineIAMRole.Arn
Export:
Name: iam-role-codepipeline-arn
EC2IAMInstanceProfileARN:
Value: !Ref EC2IAMInstanceProfile
Export:
Name: EC2IAMInstanceProfile
S3Name:
Value: !Ref S3
Export:
Name: S3Name
ALBSGName:
Value: !Ref ALBSG
Export:
Name: ALBSGName
EC2SGName:
Value: !Ref EC2SG
Export:
Name: EC2SGName
VPCID:
Value: !Ref VPC
Export:
Name: VPC
PublicSubnet01ID:
Value: !Ref PublicSubnet01
Export:
Name: PublicSubnet01ID
PublicSubnet02ID:
Value: !Ref PublicSubnet02
Export:
Name: PublicSubnet02ID
PrivateSubnet01ID:
Value: !Ref PrivateSubnet01
Export:
Name: PrivateSubnet01ID
PrivateSubnet02ID:
Value: !Ref PrivateSubnet02
Export:
Name: PrivateSubnet02ID
前回と少し違うのが142行目~155行目でCodeDeploy用サービスロールに「iam:PassRole」、「ec2:RunInstances」、「ec2:CreateTags」を許可するポリシーを作成していることです。
これを付けないとデプロイする際にEC2を起動できない+AutoScalingグループのコピーでEC2用のIAMロールを付けられなくなります。
以下のコマンドでデプロイします。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM
デプロイが完了したら起動テンプレートで使用するAMIを以下のコマンドで作成します。
aws ec2 create-image --instance-id インスタンスID --name AMIに付ける名前
ALB + AutoScalingグループ
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Description: AutoScaling Stack
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
EC2VolumeSize:
Default: 32
Type: Number
EC2VolumeIOPS:
Default: 3000
Type: Number
EC2AMI:
Type: AWS::EC2::Image::Id
EC2InstanceType:
Default: t3.micro
Type: String
Resources:
# ------------------------------------------------------------#
# ALB
# ------------------------------------------------------------#
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
IpAddressType: ipv4
LoadBalancerAttributes:
- Key: deletion_protection.enabled
Value: false
Name: test-alb
Scheme: internet-facing
SecurityGroups:
- !ImportValue ALBSGName
Subnets:
- !ImportValue PublicSubnet01ID
- !ImportValue PublicSubnet02ID
Tags:
- Key: Name
Value: test-alb
Type: application
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckEnabled: true
HealthCheckIntervalSeconds: 30
HealthCheckPath: /
HealthCheckPort: traffic-port
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 5
IpAddressType: ipv4
Matcher:
HttpCode: 200
Name: test-alb-tg
Port: 80
Protocol: HTTP
ProtocolVersion: HTTP1
Tags:
- Key: Name
Value: test-alb-tg
TargetType: instance
UnhealthyThresholdCount: 2
VpcId: !ImportValue VPC
ALBHTTPListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
# ------------------------------------------------------------#
# AutoScaling
# ------------------------------------------------------------#
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
Encrypted: true
Iops: !Ref EC2VolumeIOPS
Throughput: 125
VolumeSize: !Ref EC2VolumeSize
VolumeType: gp3
IamInstanceProfile:
Name: !ImportValue EC2IAMInstanceProfile
ImageId: !Ref EC2AMI
InstanceType: !Ref EC2InstanceType
NetworkInterfaces:
- AssociatePublicIpAddress: false
DeleteOnTermination: true
DeviceIndex: 0
Groups:
- !ImportValue EC2SGName
SubnetId: !ImportValue PrivateSubnet01ID
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Name
Value: test-ec2
LaunchTemplateName: test-ec2-lt
AutoScalingGloup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test-ec2-asg
AvailabilityZones:
- ap-northeast-1a
- ap-northeast-1c
DesiredCapacity: 2
HealthCheckGracePeriod: 300
HealthCheckType: ELB
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: 1
MaxSize: 4
MetricsCollection:
- Granularity: 1Minute
MinSize: 2
TargetGroupARNs:
- !Ref TargetGroup
VPCZoneIdentifier:
- !ImportValue PrivateSubnet01ID
- !ImportValue PrivateSubnet02ID
Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
AutoScalingGloupName:
Value: !Ref AutoScalingGloup
Export:
Name: AutoScalingGloupName
TargetGroupName:
Value: !GetAtt TargetGroup.TargetGroupName
Export:
Name: TargetGroupName
以下のコマンドでデプロイします。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=EC2AMI,ParameterValue=AMI ID
CodeCommit
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Description: CodeCommit Stack
Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Parameters for CodeCommit
Parameters:
- RepositoryDescription
- RepositoryName
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
RepositoryDescription:
MaxLength: 4000
Type: String
RepositoryName:
MaxLength: 100
Type: String
Resources:
# ------------------------------------------------------------#
# CodeCommit
# ------------------------------------------------------------#
CodeCommit:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryDescription: !Ref RepositoryDescription
RepositoryName: !Ref RepositoryName
Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
CodeCommitRepositoryName:
Value: !GetAtt CodeCommit.Name
Export:
Name: codecommit-repository-name
CodeCommitRepositoryARN:
Value: !GetAtt CodeCommit.Arn
Export:
Name: codecommit-repository-arn
以下のコマンドでデプロイします。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=RepositoryDescription,ParameterValue=リポジトリの説明 ParameterKey=RepositoryName,ParameterValue=リポジトリの名前
デプロイが完了したらリポジトリにappspec.ymlをpushします。
pushするappspec.ymlの内容は以下になります。
version: 0.0
os: linux
files:
- source: /
destination: /var/www/html
permissions:
- object: /var/www/html
owner: apache
group: apache
mode: 755
type:
- file
- directory
pushは以下のコマンドで行います。
# クローンURL確認
aws codecommit get-repository --repository-name リポジトリの名前 --query repositoryMetadata.cloneUrlHttp
# クローン
git clone クローンURL
# デフォルトブランチ設定
git config --local init.defaultBranch main
# push
git add appspec.yml
git commit -m "add appspec.yml"
git push origin main
CodeDeploy
CodeDeployではBlue/Greenデプロイができるように設定します。
EC2/オンプレミス Blue/Green デプロイ用のデプロイグループを作成する (コンソール)
マネジメントコンソールにログインしてCodeDeployの「アプリケーション」へ移動します。
画面が遷移したら「アプリケーションの作成」をクリックします。
クリックしたら任意の「アプリケーション名」を入力して「コンピューティングプラットフォーム」を選択します。
選択したら「アプリケーションの作成」をクリックします。
アプリケーションが作成されて画面が遷移したら「デプロイグループの作成」をクリックします。
クリックして画面が遷移したら任意の「デプロイグループ名」を入力して「サービスロール」を選択します。
次に「デプロイタイプ」で「Blue/Green」を選択して「環境設定」で「Amazon EC2 Auto Scaling グループの自動コピー」を選択後、「Auto Scaling グループ」を選択します。
次に「デプロイ設定」で「すぐにトラフィックを再ルーティング」と「デプロイグループの置き換え元インスタンスを終了」を選択し時間を設定します。
下にある「デプロイ設定」は「CodeDeployDefault.AllAtOnce」にしています。
ここの設定はGreen環境 (切り替え先) が作成された際の動作を設定しています。
「すぐにトラフィックを再ルーティング」で設定するとGreen環境が出来上がったらロードバランサーに自動的に登録するという設定です。
「デプロイグループの置き換え元インスタンスを終了」は指定した時間 (今回の場合は15分) が経過したらBlue環境 (古い方) を削除する設定になります。
次に「Load balancer」で「Application Load Balancer またはNetwork Load Balancer」を選択して「ターゲットグループ」を選択します。
ここまで設定したら「デプロイグループの作成」をクリックします。
CodePipeline
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"
Description: CodePipeline Stack
Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Parameters for SNS
Parameters:
- MailAddress
- Label:
default: Parameters for CodePipeline
Parameters:
- CodePipelineName
- ApplicationName
- DeploymentGroupName
Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
MailAddress:
Type: String
CodePipelineName:
MaxLength: 100
Type: String
ApplicationName:
Type: String
DeploymentGroupName:
Type: String
Resources:
# ------------------------------------------------------------#
# SNS
# ------------------------------------------------------------#
SnsTopic:
Type: AWS::SNS::Topic
Properties:
Subscription:
- Endpoint: !Ref MailAddress
Protocol: email
TopicName: sns-codepipeline-approval
SnsTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Id: approval
Statement:
- Sid: approval
Effect: Allow
Principal:
AWS: '*'
Action:
- 'SNS:GetTopicAttributes'
- 'SNS:SetTopicAttributes'
- 'SNS:AddPermission'
- 'SNS:RemovePermission'
- 'SNS:DeleteTopic'
- 'SNS:Subscribe'
- 'SNS:ListSubscriptionsByTopic'
- 'SNS:Publish'
Resource: !Ref SnsTopic
Condition:
StringEquals:
'AWS:SourceOwner': !Sub ${AWS::AccountId}
Topics:
- !Ref SnsTopic
# ------------------------------------------------------------#
# CodePipeline
# ------------------------------------------------------------#
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
ArtifactStore:
Location: !ImportValue S3Name
Type: S3
Name: !Ref CodePipelineName
RoleArn: !ImportValue iam-role-codepipeline-arn
Stages:
- Actions:
- ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: 1
Configuration:
RepositoryName: !ImportValue codecommit-repository-name
BranchName: main
PollForSourceChanges: false
OutputArtifactFormat: CODE_ZIP
Name: Source
Namespace: SourceVariables
OutputArtifacts:
- Name: SourceArtifact
Region: ap-northeast-1
RunOrder: 1
Name: Source
- Actions:
- ActionTypeId:
Category: Approval
Owner: AWS
Provider: Manual
Version: 1
Configuration:
NotificationArn: !Ref SnsTopic
Name: Approval
Namespace: ApprovalVariables
Region: ap-northeast-1
RunOrder: 1
Name: Approval
- Actions:
- ActionTypeId:
Category: Deploy
Owner: AWS
Provider: CodeDeploy
Version: 1
Configuration:
ApplicationName: !Ref ApplicationName
DeploymentGroupName: !Ref DeploymentGroupName
Name: Deploy
Namespace: DeployVariables
InputArtifacts:
- Name: SourceArtifact
Region: ap-northeast-1
RunOrder: 1
Name: Deploy
Tags:
- Key: Name
Value: !Ref CodePipelineName
# ------------------------------------------------------------#
# EventBridge
# ------------------------------------------------------------#
EventBridgeIAMPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "codepipeline:StartPipelineExecution"
Resource:
- !Join
- ''
- - 'arn:aws:codepipeline:ap-northeast-1:'
- !Sub '${AWS::AccountId}:'
- !Ref CodePipeline
ManagedPolicyName: iam-policy-eventbridge
EventBridgeIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- !Ref EventBridgeIAMPolicy
RoleName: iam-role-eventbridge
Tags:
- Key: Name
Value: iam-role-eventbridge
EventBridge:
Type: AWS::Events::Rule
Properties:
Description: for codepipeline
EventPattern:
source:
- aws.codecommit
detail-type:
- 'CodeCommit Repository State Change'
resources:
- !ImportValue codecommit-repository-arn
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- main
Name: eventbridge-codepipeline
State: ENABLED
Targets:
- Arn: !Join
- ''
- - 'arn:aws:codepipeline:ap-northeast-1:'
- !Sub '${AWS::AccountId}:'
- !Ref CodePipeline
Id: CodePipeline
RoleArn: !GetAtt EventBridgeIAMRole.Arn
以下のコマンドでデプロイします。
aws cloudformation create-stack --stack-name スタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=MailAddress,ParameterValue=SNSで通知するメールアドレス ParameterKey=CodePipelineName,ParameterValue=CodePipeline名 ParameterKey=ApplicationName,ParameterValue=CodeDeployアプリケーション名 ParameterKey=DeploymentGroupName,ParameterValue=CodeDeployデプロイグループ名 --capabilities CAPABILITY_NAMED_IAM
デプロイが完了すると新しいAutoScalingグループが作成され始めます。
下記の画像だと上のAutoScalingグループがGreen環境になるものです。
下のBlue環境は環境の切り替え後15分で削除されます。
動作確認
CodeCommitリポジトリにtest.htmlという名前のHTMLファイルをgit pushしてみます。
ディレクトリの中身は以下のようになっています。
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2023/01/24 14:37 327 appspec.yml
-a---- 2022/09/29 1:16 158 test.html
以下のコマンドを実行してCodeCommitリポジトリにpushします。
git add .
git commit -m "add test.html"
git push origin main
pushして承認フェーズで承認したら正常にGreen環境が作成されることが確認できます。
CodeDeployの画面から各ステップの進行状況が確認できます。
新しく起動したEC2にアクセスしてドキュメントルートを確認するとデプロイされたことが確認できます。
ls -la /var/www/html/
total 12
drwxr-xr-x 2 root root 60 Jan 30 15:02 .
drwxr-xr-x 4 root root 33 Jan 30 05:47 ..
-rwxr-xr-x 1 apache apache 327 Dec 31 1979 appspec.yml
-rw-r--r-- 1 root root 16 Jan 30 05:47 index.html
-rwxr-xr-x 1 apache apache 159 Dec 31 1979 test.html
さいごに
CodePipeline周りのサービスはIAMロールの設定がサービスごとにあるので、どこで何が使用されているのか把握しておく必要があると思いました。
いつかCloudFormationでEC2のBlue/Greenデプロイ設定に対応してくれることを祈ります。