CodePipelineをCloudFormationで作成してみた

2023.01.20

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

CodePipelineを使用したCI/CD環境をCloudFormationで作成する機会があったのでブログに残します。

作成するもの

今回作成するのはEC2インスタンスにデプロイを行うCI/CD環境です。
CloudFormationで作成するのは以下のサービスです。

  • アーティファクト用S3バケット
  • CodeCommit
  • CodeBuild
  • CodeDeploy
  • CodePipeline
  • CodeBuild用サービスロール
  • CodeDeploy用サービスロール
  • CodePipeline用サービスロール
  • CodePipelineを動かすためのEventBridgeルール
  • EC2用IAMロール
  • デプロイ先のEC2、VPC、サブネット、セキュリティグループなど

構成図にすると以下のようになります。

今回のブログではCodeCommitにgit pushできるIAMユーザの作成は記載していません。
以下の公式ドキュメントをご確認いただき作成してください。
CodeCommit での IAM の使用: Git 認証情報、SSH キー、および AWS アクセスキー

作成してみた

IAMロール + アーティファクト用S3 + ネットワーク周り + デプロイ先EC2

今回作成したCloudFormationテンプレートは以下になります。
※300行以上のテンプレートなので折りたたんでいます。

作成した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
      - 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.3.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

  CodeBuildIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Action:
              - 's3:PutObject'
              - 's3:GetObject'
            Resource: 
              - !Join 
                - ''
                - - !GetAtt S3.Arn
                  - '/*'
          - Effect: Allow
            Action: 
              - 'codecommit:GitPull'
            Resource: "*"
          - Effect: Allow
            Action: 
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
            Resource: "*"
      ManagedPolicyName: iam-policy-codebuild

  CodeBuildIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - codebuild.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - !Ref CodeBuildIAMPolicy
      RoleName: iam-role-codebuild
      Tags:
        - Key: Name
          Value: iam-role-codebuild

  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:
              - "codebuild:BatchGetBuilds"
              - "codebuild:StartBuild"
            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
      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

# ------------------------------------------------------------#
# 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

# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for ec2
      GroupName: test-sg-ec2
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          CidrIp: 0.0.0.0/0
          ToPort: 80
      Tags: 
        - Key: Name
          Value: test-sg-ec2
      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: 
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref EC2SG
          SubnetId: !Ref PublicSubnet01
      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
        systemctl start httpd
        systemctl enable httpd
Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  CodeBuildIAMRoleARN:
    Value: !GetAtt CodeBuildIAMRole.Arn
    Export: 
      Name: iam-role-codebuild-arn

  CodeDeployIAMRoleARN:
    Value: !GetAtt CodeDeployIAMRole.Arn
    Export: 
      Name: iam-role-codedeploy-arn

  CodePipelineIAMRoleARN:
    Value: !GetAtt CodePipelineIAMRole.Arn
    Export: 
      Name: iam-role-codepipeline-arn

上記のCloudFormationテンプレートで作成されるのはアーティファクト用S3バケット、IAMロール、VPC、インターネットゲートウェイ、サブネット、ルートテーブル、EC2になります。

アーティファクト用S3バケット

こちらはテンプレートファイルの59行目~77行目で設定しています。
デフォルトの暗号化の有効化、パブリックアクセスブロックを全てオンにしたS3バケットが作成されます。
アーティファクトが何者かというのは以下の公式ドキュメントで説明されています。
アーティファクト

アーティファクトとは、パイプラインアクションによって処理されるアプリケーションのソースコード、構築されたアプリケーション、依存関係、定義ファイル、テンプレートなどのデータの集合を指します。

IAMロール

こちらはテンプレートファイルの82行目~254行目で設定しています。
82行目~125行目ではEC2用のIAMとインスタンスプロファイルを作成します。
EC2からアーティファクト用S3へアクセスできるようにGetObject、ListBucketをカスタムIAMポリシーで付与しています。
マネージドポリシーはセッションマネージャーでEC2にアクセスできるようAmazonSSMManagedInstanceCoreを付与しました。

127行目~171行目ではCodeBuild用サービスロールを作成します。
CodeBuildのサービスロールはビルド時のアクションによって必要な権限が変わってきます。
以下のドキュメントの例ではECRへの権限などもついてたりします。
CodeBuild サービスロールの作成
CodeBuildではビルドの完了したソースコードをアーティファクト用S3に保存する権限が必要なのでPutObjectを付与しています。
またビルドログを出力するためCloudwatch Logsの権限を付与しています。

173行目~190行目ではCodeDeploy用サービスロールを作成します。
今回はマネージドポリシーのAWSCodeDeployRoleを付与しています。

192行目~254行目ではCodePipeline用サービスロールを作成します。
CodePipelineではCodeCommit、CodeBuild、CodeDeploy + アーティファクトをS3に出す権限が必要なので付与しています。
他のサービスを利用する場合は以下の公式ドキュメントをご確認ください。
CodePipeline サービスロールの管理

VPC、インターネットゲートウェイ、サブネット、ルートテーブル

259行目~344行目でネットワーク周りを作成しています。
ネットワーク周りはシンプルにVPCの中にパブリックサブネットを1つ作成するテンプレートになっています。

デプロイ先EC2

349行目~384行目でEC2を作成しています。
EC2にはCodeDeployエージェントのインストールが必要なのでUserDataでインストールコマンドを記載しています。

デプロイは以下のコマンドで行います。
VPCやサブネットのCIDRが変更したい場合は「--parameters」オプションを設定してください。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM

CodeCommit

CodeCommitはリポジトリを作成するだけなのでシンプルです。

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=リポジトリの名前

リソース作成ができたらbuildspec.ymlとappspec.ymlを作成してリポジトリに配置します。
今回使用するbuildspec.ymlのサンプルは以下になります。

version: 0.2

phases:
  install:
    on-failure: ABORT
    commands:
      - echo "install phases"
      - echo "yum install などでビルドに使うパッケージをインストールするのに使用"
  pre_build:
    on-failure: ABORT
    commands:
      - echo "pre_build phases"
      - echo "Amazon ECR にサインインしたりするのに使用"
  build:
    on-failure: ABORT
    commands:
      - echo "build phases"
      - echo "ビルド、テストなどを実行するのに使用"
  post_build:
    on-failure: ABORT
    commands:
      - echo "post_build phases"
      - echo "Docker イメージをAmazon ECR にプッシュしたりZIPとかにアーカイブするのに使用"
artifacts:
  files:
    - "**/*"

versionは「0.1」も使えるとのことですが、「0.2」が推奨されいています。
phasesではビルドに使用するパッケージのインストールやビルド、テストを実行するコマンドを記載します。
artifactsはアーティファクト用S3へビルドが完了したものを入れる設定を記載します。
filesでビルド完了後のソースコードを指定します。「"**/*"」は全てのファイルを意味します。
他にも設定できるものがあるので、以下のドキュメントをご確認ください。
CodeBuild のビルド仕様に関するリファレンス

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
#hooks:
#      デプロイする際に動かすシェルスクリプトなどを設定するセクション

versionは今のところ「0.0」しか設定ができません。
osはデプロイ先のOSを選択します。Linuxなら「linux」、Windowsなら「windows」を設定します。
filesはデプロイ先 (今回はEC2) にデプロイするファイルとデプロイ先のディレクトリを設定します。sourceが「/」だと全てのファイルをデプロイします。
permissionsはデプロイするファイルの権限を変更する部分になります。Linuxの場合は設定が可能になります。
他にも各セクションで細かい設定がありますので、以下のドキュメントをご確認ください。
AppSpec ファイル構造
AppSpec 「ファイル」セクション (EC2/オンプレミスのデプロイのみ)
AppSpec 「許可」セクション (EC2/オンプレミスデプロイのみ)
AppSpec EC2/オンプレミスデプロイの「hooks」セクション

ファイル配置手順

以下のコマンドでCodeCommitリポジトリのクローンURLを確認します。

aws codecommit get-repository --repository-name Repository --query repositoryMetadata.cloneUrlHttp

ローカルに以下のコマンドでリポジトリをクローンしてファイルの配置とブランチを作成します。
認証情報の作成方法は以下の公式ドキュメントをご確認ください。
CodeCommit での IAM の使用: Git 認証情報、SSH キー、および AWS アクセスキー

git clone クローンURL

クローンしてファイルを配置したら以下のコマンドでデフォルトのブランチを設定します。

git config --local init.defaultBranch main

デフォルトブランチを設定したらgit pushしていきます。

git add .
git commit -m "add buildspec.yml appspec.yml"
git push origin main

ここまで実行するとファイルがリポジトリに追加されます。

CodeBuild

今回CodeBuildの作成はしますが、ビルドフェーズでビルドしたりテストしたりするものは作成していません。
作成したCloudFormationテンプレートは以下になります。

AWSTemplateFormatVersion: "2010-09-09"

Description: CodeBuild Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for CodeCommit
        Parameters:
          - Description
          - Name

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  Description:
    MaxLength: 255
    Type: String

  Name:
    MaxLength: 255
    Type: String

Resources:
# ------------------------------------------------------------#
# CodeBuild
# ------------------------------------------------------------# 
  CodeBuild:
    Type: AWS::CodeBuild::Project
    Properties: 
      Artifacts:
        Type: CODEPIPELINE
      Description: !Ref Description
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0
        Type: LINUX_CONTAINER
      Name: !Ref Name
      ServiceRole: !ImportValue iam-role-codebuild-arn
      Source: 
        BuildSpec: buildspec.yml
        Type: CODEPIPELINE
      Tags:
        - Key: Name
          Value: test-build

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  CodeBuildProjectname:
    Value: !Ref CodeBuild
    Export: 
      Name: codebuild-project-name

  CodeBuildProjectARN:
    Value: !GetAtt CodeBuild.Arn
    Export: 
      Name: codebuild-project-arn

アーティファクトやソースの設定はCodePipelineで大体の設定が管理されるため記述が少なめになっています。
デプロイは以下のコマンドで行います。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=Description,ParameterValue=ビルドプロジェクトの説明 ParameterKey=Name,ParameterValue=ビルドプロジェクトの名前

CodeDeploy

今回CodeDeployでは単体のEC2へのデプロイに利用するのでAllAtOnceを使用して一回で全部デプロイが終わるように設定します。
作成した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
      DeploymentConfigName: CodeDeployDefault.AllAtOnce
      DeploymentGroupName: !Ref DeploymentGroupName
      Ec2TagFilters: 
        - Key: Name
          Type: KEY_AND_VALUE
          Value: test-ec2
      ServiceRoleArn: !ImportValue iam-role-codedeploy-arn
      Tags:
        - Key: Name
          Value: !Ref DeploymentGroupName

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  CodeDeployApplicationName:
    Value: !Ref CodeDeployApplication
    Export: 
      Name: codedeploy-application-name

  CodeDeployGroupName:
    Value: !Ref CodeDeployGroup
    Export: 
      Name: codedeploy-group

今回デプロイ先はEC2なので37行目のComputePlatformがServerになっています。ここの設定はECS、Lambdaも選択できます。
46行目からの設定でデプロイに失敗した際ロールバックする設定を入れいています。
デプロイは以下のコマンドで行います。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=ApplicationName,ParameterValue=アプリケーション名 ParameterKey=DeploymentGroupName,ParameterValue=デプロイグループ名

CodePipeline

CodeCommitにgit pushされた段階でデプロイが開始されてほしいのでEventBridgeも一緒に作成しています。
作成したCloudFormationテンプレートは以下になります。

AWSTemplateFormatVersion: "2010-09-09"

Description: CodePipeline Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for CodePipeline
        Parameters:
          - CodePipelineName

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  CodePipelineName:
    MaxLength: 100
    Type: String

Resources:
# ------------------------------------------------------------#
# 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: Build
              Owner: AWS
              Provider: CodeBuild
              Version: 1
            Configuration:
              ProjectName: !ImportValue codebuild-project-name
            InputArtifacts: 
              - Name: SourceArtifact
            Name: Build
            Namespace: BuildVariables
            OutputArtifacts: 
              - Name: BuildArtifact
            Region: ap-northeast-1
            RunOrder: 1
          Name: Build
        - Actions:
          - ActionTypeId: 
              Category: Deploy
              Owner: AWS
              Provider: CodeDeploy
              Version: 1
            Configuration:
              ApplicationName: !ImportValue codedeploy-application-name
              DeploymentGroupName: !ImportValue codedeploy-group
            Name: Deploy
            Namespace: DeployVariables
            InputArtifacts:
              - Name: BuildArtifact
            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

こちらのCloudFormationテンプレートでメインになるのが36行目から90行目のStagesの設定になります。
各Actionsでソース、ビルド、デプロイプロバイダの設定を行っています。
Configurationの設定パラメータについては以下のドキュメントをご確認ください。
CodeCommit
CodeBuild
CodeDeploy
デプロイは以下のコマンドで行います。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=CodePipelineName,ParameterValue=CodePipeline名 --capabilities CAPABILITY_NAMED_IAM

ここまで設定が完了するとCI/CDが利用できるようになります。

動作確認

CodeCommitリポジトリにtest.htmlという名前のHTMLファイルをgit pushしてみます。
ディレクトリの中身は以下のようになっています。

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2023/01/19     17:38            335 appspec.yml
-a----        2023/01/19     17:37            770 buildspec.yml
-a----        2022/09/29      1:16            158 test.html

以下のコマンドを実行します。

git add test.html
git commit -m "add test.html"
git push origin main

EC2インスタンスに接続してappspec.ymlで指定したディレクトリを確認するとtest.htmlがデプロイされています。

ls -la /var/www/html/
total 12
drwxr-xr-x 2 root   root    63 Jan 19 15:07 .
drwxr-xr-x 4 root   root    33 Jan 19 01:20 ..
-rwxr-xr-x 1 apache apache 327 Jan 19 15:07 appspec.yml
-rwxr-xr-x 1 apache apache 770 Jan 19 15:07 buildspec.yml
-rwxr-xr-x 1 apache apache 158 Jan 19 15:07 test.html

さいごに

今回CloudFormationでCodePipeline周りの設定を行いましたが、思っていた以上に設定項目が多く作成に時間がかかりました。
次回はAutoScaling グループにデプロイする設定をやってみようと思います。