CodeDeployでAutoScalingグループにBlue/Greenデプロイする設定をやってみた

2023.01.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

以下のブログでインプレースデプロイを使用して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デプロイ設定に対応してくれることを祈ります。