CodeDeployでAutoScalingグループにデプロイする設定をCloudFormationで作成してみた
前回のブログで単体で起動しているEC2にデプロイするCI/CD環境をCloudFormationで作成しました。
今回はAutoScalingでスケールアウト、スケールインする環境にデプロイする設定をCloudFormationで作成してみます。
作成するもの
殆ど前回と同じものを作成します。
追加されるのはALB、AutoScalingグループ、ALBでバランシングするサブネット
設定が変わるのがCodeDeploy、CodePipelineになります
前回はCodePipelineの一連の流れが知りたかったのでCodeBuildを作成しましたが、今回はCodeDeployがメインになるのでビルドフェーズは作成しません。
- アーティファクト用S3バケット
- CodeCommit
- CodeDeploy
- CodePipeline
- CodeDeploy用サービスロール
- CodePipeline用サービスロール
- CodePipelineを動かすためのEventBridgeルール
- EC2(AutoScalingグループ管理)用IAMロール
- デプロイ先のAutoScalingグループ、ゴールデンAMI作成用EC2、VPC、サブネット、セキュリティグループ、NATゲートウェイなど
構成図にすると以下のようになります。
今回はEC2をプライベートサブネットに配置するのでパブリックサブネットに1つNATゲートウェイを配置しています。
今回のブログではCodeCommitにgit pushできるIAMユーザの作成は記載していません。
以下の公式ドキュメントをご確認いただき作成してください。
CodeCommit での IAM の使用: Git 認証情報、SSH キー、および AWS アクセスキー
作成してみた
IAMロール + アーティファクト用S3 + ネットワーク周り + ゴールデンAMI作成用EC2
作成したCloudFormationテンプレートは以下になります。
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 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 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 S3Name: Value: !Ref S3 Export: Name: S3Name ALBSGName: Value: !Ref ALBSG Export: Name: ALBSGName EC2SGName: Value: !Ref EC2SG Export: Name: EC2SGName 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
こちらのCloudFormationテンプレートをデプロイするとVPCなどのネットワーク周り、CI/CDで使用するIAMロール、アーティファクト用S3、プライベートサブネットに1台EC2が作成されます。
EC2はCodeDeployエージェントをUserDataでインストールするようにしています。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM
デプロイが完了したら以下のコマンドを実行してEC2のAMIを作成します。
実行するとAMI IDが出力されます。
こちらはAutoScalingで使用する起動テンプレートを作成する際に使用します。
aws ec2 create-image --instance-id インスタンスID --name AMIに付ける名前
ALB + AutoScalingグループ
ここでは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
28行目から79行目でALBとターゲットグループを作成しています。
AutoScalingでEC2を起動するのでターゲットグループにEC2と紐づける記載はありません。
84行目から136行目で起動テンプレートとAutoScalingグループを作成しています。
AutoScalingグループ作成時は最小2台の状態でEC2が起動するようになっています。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=EC2AMI,ParameterValue=AMI ID
CodeCommit
CodeCommitにつきましては前回と同じCloudFormationテンプレートと手順になりますので以下のブログをご確認ください。
CodeDeploy
CodeDeployはAutoScalingグループにデプロイできるように設定していきます。
作成したCloudFormationテンプレートは以下になります。
AWSTemplateFormatVersion: "2010-09-09" Description: CodeDeploy Stack Metadata: # ------------------------------------------------------------# # Metadata # ------------------------------------------------------------# AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Parameters for CodeDeploy Parameters: - ApplicationName - DeploymentGroupName Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# ApplicationName: MaxLength: 100 Type: String DeploymentGroupName: MaxLength: 100 Type: String Resources: # ------------------------------------------------------------# # CodeDeploy # ------------------------------------------------------------# CodeDeployApplication: Type: AWS::CodeDeploy::Application Properties: ApplicationName: !Ref ApplicationName ComputePlatform: Server Tags: - Key: Name Value: !Ref ApplicationName CodeDeployGroup: Type: AWS::CodeDeploy::DeploymentGroup Properties: ApplicationName: !Ref CodeDeployApplication AutoRollbackConfiguration: Enabled: true Events: - DEPLOYMENT_FAILURE AutoScalingGroups: - !ImportValue AutoScalingGloupName DeploymentConfigName: CodeDeployDefault.HalfAtATime DeploymentGroupName: !Ref DeploymentGroupName DeploymentStyle: DeploymentOption: WITH_TRAFFIC_CONTROL DeploymentType: IN_PLACE LoadBalancerInfo: TargetGroupInfoList: - Name: !ImportValue TargetGroupName OutdatedInstancesStrategy: UPDATE ServiceRoleArn: !ImportValue iam-role-codedeploy-arn Tags: - Key: Name Value: !Ref DeploymentGroupName Outputs: # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# CodeDeployApplicationName: Value: !Ref CodeDeployApplication Export: Name: codedeploy-app-name CodeDeployGrouplicationName: Value: !Ref CodeDeployGroup Export: Name: codedeploy-group-name
50行目のAutoScalingGroupsでデプロイするAutoScalingグループを指定しています。
デプロイ設定はCodeDeployDefault.HalfAtATimeを指定しています。
一度のデプロイで半分のインスタンスにデプロイする設定です。
例えば4台EC2があれば2台ずつ行う動きになります。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=ApplicationName,ParameterValue=アプリケーション名 ParameterKey=DeploymentGroupName,ParameterValue=デプロイグループ名
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 Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# MailAddress: Type: String CodePipelineName: MaxLength: 100 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: !ImportValue codedeploy-app-name DeploymentGroupName: !ImportValue codedeploy-group-name 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
35行目から68行目のSNSトピックは手動承認フェーズで承認者にメールを送信できるよう作成します。
100行目から112行目で手動承認フェーズを記載しています。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name スタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=MailAddress,ParameterValue=SNSで通知するメールアドレス ParameterKey=CodePipelineName,ParameterValue=CodePipeline名 --capabilities CAPABILITY_NAMED_IAM
動作確認
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
デプロイが開始され手動承認フェーズまで来ると以下の件名でメールが届きます。
APPROVAL NEEDED: AWS CodePipeline CodePipeline名 for action Approval
メールが届いたら本文の「Approve or reject:」の横にあるURLをクリックすればCodePipelineの画面へ飛びます。
CodePipelineの画面に移動したら手動承認フェーズの「レビュー」をクリックします。
クリックすると承認するか却下するのかを選択できるので「承認します」をクリックします。
承認するとデプロイが開始されます。
デプロイが現在どのような状態なのか確認するにはデプロイフェーズの「詳細」をクリックすると確認ができます。
しばらく待つとデプロイが完了します。
EC2にアクセスしてドキュメントルートを確認するとデプロイされたことが確認できます。
drwxr-xr-x 2 root root 60 Jan 24 08:37 . drwxr-xr-x 4 root root 33 Jan 23 13:46 .. -rwxr-xr-x 1 apache apache 327 Dec 31 1979 appspec.yml -rw-r--r-- 1 root root 16 Jan 23 13:46 index.html -rwxr-xr-x 1 apache apache 158 Dec 31 1979 test.html
スケールアウトしてEC2が起動したときの動作
EC2がスケールアウトした際もCodeDeployが動くのか確認します。
以下のコマンドを実行してEC2の台数を増やします。
aws autoscaling set-desired-capacity --auto-scaling-group-name AutoScalingグループ名 --desired-capacity 3
コマンドを実行後CodeDeployの「デプロイメント」から一番新しいデプロイIDのステータスが進行中になっていることが確認できます。
さいごに
今回はAutoScalingグループにデプロイするCodeDeploy設定をCloudFormationで作成してみました。
複雑な設定になってしまうのかと勝手に思っていたのですが、単一のEC2からAutoScalingグループがデプロイ先になったとしても設定が複雑化することはありませんでした。