GitHub Actionsを使用したECSへのBlue/Greenデプロイを試してみた

GitHub Actionsを使用したECSへのBlue/Greenデプロイを試してみた

Clock Icon2025.07.01

前回のブログでGitHub Actionsを使用したローリングアップデートを試しました。
今回はBlue/Greenデプロイを試してみたのでブログに残しておこうと思います。
https://dev.classmethod.jp/articles/github-actions-ecs/

やること

前回同様GitHub Actionsでamazon-ecs-deploy-task-definitionを使用します。
前回との差分としてBlue/Greenデプロイを行うためにCodeDeployを使用します。
作成する構成としては以下のようになります。
GitHub Actions ECS Blue_Green

設定

設定内容は前回のブログと殆ど同じなのでデプロイ方法などは割愛します。
https://dev.classmethod.jp/articles/github-actions-ecs/

GitHubとAWSの連携

前回との差分としてGitHub Actionsが使用するIAMロールのIAMポリシーが少し異なります。
CodeDeployを実行するために"GetDeployment"、"GetDeploymentConfig"、"RegisterApplicationRevision"を許可しています。

AWSTemplateFormatVersion: '2010-09-09'
Description: OIDC settings for Github Actions. 

Parameters:
  EnvName:
    Type: String
    Default: dev
    AllowedValues:
      - dev
    Description: Environment name
  OrgID:
    Type: String
    Description: Github Organazation ID
  RepoName:
    Type: String
    Default: RepositoryName
    Description: Repository name

Resources:
  OIDCProvider:
    Type: AWS::IAM::OIDCProvider
    Properties:
      ClientIdList:
        - 'sts.amazonaws.com'
      Url: https://token.actions.githubusercontent.com

  GithubActionsPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - 'ecr:UploadLayerPart'
              - 'ecr:PutImage'
              - 'ecr:InitiateLayerUpload'
              - 'ecr:CompleteLayerUpload'
              - 'ecr:BatchCheckLayerAvailability'
              - 'ecr:GetAuthorizationToken'
              - 'iam:PassRole'
              - 'ecs:DescribeServices'
              - 'ecs:RegisterTaskDefinition'
              - 'ecs:DescribeTaskDefinition'
              - 'codedeploy:GetDeployment'
              - 'codedeploy:GetDeploymentGroup'
              - 'codedeploy:GetDeploymentConfig'
              - 'codedeploy:RegisterApplicationRevision'
              - 'codedeploy:CreateDeployment'
            Resource: '*'
      ManagedPolicyName: !Sub policy-${EnvName}-github-oidc-${RepoName}-001

  OIDCProviderRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub 'role-${EnvName}-github-oidc-${RepoName}-001'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Federated: !Sub 'arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com'
            Action: 'sts:AssumeRoleWithWebIdentity'
            Condition:
              StringLike:
                'token.actions.githubusercontent.com:sub':
                  - !Sub 'repo:${OrgID}/${RepoName}:*'
      ManagedPolicyArns:
        - !Ref GithubActionsPolicy

ECSサービスなどの作成

ECRと初期デプロイ時に使用するコンテナイメージの作成は前回のブログを参照してください。
https://dev.classmethod.jp/articles/github-actions-ecs/#ecs%25E3%2582%25B5%25E3%2583%25BC%25E3%2583%2593%25E3%2582%25B9%25E3%2581%25AA%25E3%2581%25A9%25E3%2581%25AE%25E4%25BD%259C%25E6%2588%2590

ECSクラスターなどの作成は以下のCloudFormationテンプレートで行います。
前回との差分としてCodeDeployとBlue/Greenで使用するALBのリスナールールなどが追加されています。

AWSTemplateFormatVersion: "2010-09-09"
Description: ECS

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for env Name
        Parameters:
          - env
      - Label:
          default: Parameters for Network
        Parameters:
          - VPCCIDR
          - PublicSubnet01CIDR
          - PublicSubnet02CIDR
          - PrivateSubnet01CIDR
          - PrivateSubnet02CIDR

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  env:
    Type: String
    Default: dev
    AllowedValues:
      - dev

  VPCCIDR:
    Default: 192.168.0.0/16
    Type: String

  PublicSubnet01CIDR:
    Default: 192.168.0.0/24
    Type: String

  PublicSubnet02CIDR:
    Default: 192.168.1.0/24
    Type: String

  PrivateSubnet01CIDR:
    Default: 192.168.2.0/24
    Type: String

  PrivateSubnet02CIDR:
    Default: 192.168.3.0/24
    Type: String

Resources:
# ------------------------------------------------------------#
# CloudWatch Logs
# ------------------------------------------------------------# 
  ECSLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "/ecs/logs/ecs-${env}-log"

# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------# 
  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      RoleName: !Sub iam-${env}-ecs-tast-execution-role

  CodeDeployRole:
    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/AWSCodeDeployRoleForECS
      RoleName: !Sub iam-${env}-codedeploy-ecs-role

  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: !Sub iam-${env}-codedeploy-role
      Tags:
        - Key: Name
          Value: !Sub iam-${env}-codedeploy-role

# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: !Sub vpc-${env}

# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------# 
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: !Sub igw-${env}

  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 subnet-${env}-pub1
      VpcId: !Ref VPC

  PublicSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PublicSubnet02CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: !Sub subnet-${env}-pub2
      VpcId: !Ref VPC

  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PrivateSubnet01CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: !Sub subnet-${env}-prv1
      VpcId: !Ref VPC

  PrivateSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PrivateSubnet02CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: !Sub subnet-${env}-prv2
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: rtb-${env}-pub

  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: rtb-${env}-prv

  PrivateRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet01

  PrivateRtAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet02

# ------------------------------------------------------------#
# SecurityGroup
# ------------------------------------------------------------# 
  ALBSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for alb
      GroupName: !Sub securitygroup-${env}-alb
      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
        - FromPort: 8080
          IpProtocol: tcp
          CidrIp: 0.0.0.0/0
          ToPort: 8080
      Tags: 
        - Key: Name
          Value: !Sub securitygroup-${env}-alb
      VpcId: !Ref VPC

  ECSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for ecs
      GroupName: !Sub securitygroup-${env}-ecs
      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: !Sub securitygroup-${env}-ecs
      VpcId: !Ref VPC

  VPCEndpointSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for vpc endpoint
      GroupName: !Sub securitygroup-${env}-vpc-endpoint
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress:
        - FromPort: 443
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref ECSSG
          ToPort: 443
      Tags: 
        - Key: Name
          Value: !Sub securitygroup-${env}-vpc-endpoint
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# VPC Endpoint
# ------------------------------------------------------------# 
  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties: 
      RouteTableIds: 
        - !Ref PrivateRouteTable
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcEndpointType: Gateway
      VpcId: !Ref VPC

  ECRdkrEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr
      VpcId: !Ref VPC
      SubnetIds: 
        - !Ref PrivateSubnet01
        - !Ref PrivateSubnet02
      SecurityGroupIds:
        - !Ref VPCEndpointSG

  ECRapiEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet01
        - !Ref PrivateSubnet02
      SecurityGroupIds:
        - !Ref VPCEndpointSG

  LogsEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.logs
      VpcId: !Ref VPC
      SubnetIds: 
        - !Ref PrivateSubnet01
        - !Ref PrivateSubnet02
      SecurityGroupIds:
        - !Ref VPCEndpointSG

# ------------------------------------------------------------#
# ALB
# ------------------------------------------------------------# 
  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      LoadBalancerAttributes:
        - Key: deletion_protection.enabled
          Value: false
      Name: !Sub alb-${env}-ecs
      Scheme: internet-facing
      SecurityGroups:
        - !Ref ALBSG
      Subnets: 
        - !Ref PublicSubnet01
        - !Ref PublicSubnet02
      Tags: 
        - Key: Name
          Value: !Sub alb-${env}-ecs
      Type: application

  TargetGroup1:
    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: !Sub tg-${env}-01
      Port: 80
      Protocol: HTTP
      ProtocolVersion: HTTP1
      Tags: 
        - Key: Name
          Value: !Sub tg-${env}-01
      TargetType: ip
      UnhealthyThresholdCount: 2
      VpcId: !Ref VPC

  TargetGroup2:
    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: !Sub tg-${env}-02
      Port: 80
      Protocol: HTTP
      ProtocolVersion: HTTP1
      Tags: 
        - Key: Name
          Value: !Sub tg-${env}-02
      TargetType: ip
      UnhealthyThresholdCount: 2
      VpcId: !Ref VPC

  ALBHTTPListener1:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup1
          Type: forward
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP

  ALBHTTPListener2:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup2
          Type: forward
      LoadBalancerArn: !Ref ALB
      Port: 8080
      Protocol: HTTP

# ------------------------------------------------------------#
# ECS
# ------------------------------------------------------------# 
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      CapacityProviders:
        - FARGATE
      ClusterName: !Sub ecs-${env}-cluster
      DefaultCapacityProviderStrategy:
        - CapacityProvider: FARGATE
          Weight: 1

  ECSTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/ecr-${env}:latest
          LogConfiguration:
            LogDriver: awslogs
            Options: 
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: !Sub ecs-${env}-log
          Name: !Sub task-${env}
          PortMappings:
            - ContainerPort: 80
              HostPort: 80
      Cpu: 256
      ExecutionRoleArn: !Ref TaskExecutionRole
      Family: !Sub task-${env}
      Memory: 512
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE

  ECSService:
    Type: AWS::ECS::Service
    DependsOn: 
      - ALBHTTPListener1
      - ALBHTTPListener2
    Properties:
      Cluster: !Ref ECSCluster
      DesiredCount: 1
      LoadBalancers:
        - ContainerName: !Sub task-${env}
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup1
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            - !Ref ECSSG
          Subnets:
            - !Ref PrivateSubnet01
            - !Ref PrivateSubnet02
      ServiceName: !Sub service-${env}
      TaskDefinition: !Ref ECSTaskDef
      PlatformVersion: LATEST
      DeploymentController: 
        Type: CODE_DEPLOY

# ------------------------------------------------------------#
# CodeDeploy
# ------------------------------------------------------------# 
  CodeDeployApp:
    Type: AWS::CodeDeploy::Application
    DependsOn: ECSService
    Properties:
      ApplicationName: !Sub codedeploy-${env}-app
      ComputePlatform: ECS

  CodeDeployGroup:
    Type: AWS::CodeDeploy::DeploymentGroup
    Properties:
      ApplicationName: !Ref CodeDeployApp
      DeploymentGroupName: !Sub codedeploy-${env}-group
      DeploymentConfigName: CodeDeployDefault.ECSAllAtOnce
      AutoRollbackConfiguration:
        Enabled: true
        Events:
          - DEPLOYMENT_FAILURE
          - DEPLOYMENT_STOP_ON_REQUEST
      BlueGreenDeploymentConfiguration:
        DeploymentReadyOption:
          ActionOnTimeout: CONTINUE_DEPLOYMENT
          WaitTimeInMinutes: 0
        TerminateBlueInstancesOnDeploymentSuccess:
          Action: TERMINATE
          TerminationWaitTimeInMinutes: 5
      DeploymentStyle:
        DeploymentOption: WITH_TRAFFIC_CONTROL
        DeploymentType: BLUE_GREEN
      LoadBalancerInfo:
        TargetGroupPairInfoList:
          - ProdTrafficRoute:
              ListenerArns:
                - !Ref ALBHTTPListener1
            TestTrafficRoute:
              ListenerArns:
                - !Ref ALBHTTPListener2
            TargetGroups:
              - Name: !GetAtt TargetGroup1.TargetGroupName
              - Name: !GetAtt TargetGroup2.TargetGroupName
      ServiceRoleArn: !GetAtt CodeDeployRole.Arn
      ECSServices:
        - ClusterName:
            !Ref ECSCluster
          ServiceName:
            !GetAtt ECSService.Name

GitHub Actions設定

ECS周りのリソースが作成できたらGitHub ActionsのワークフローファイルとCodeDeployで使用するappspec.yamlの作成を行っていきます。

appspec.yamlは以下のものを使用します。
TaskDefinitionはGitHub Actions実行時に書き換えられるのでそのままで大丈夫です。
書き換えはindex.jsの399行目あたりで行われているようです。
findAppSpecValue関数でappspec.yaml内の'resources'を探して、さらに'properties'から'taskDefinition'を探して書き換えを行っています。

version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: "task-dev"
          ContainerPort: 80

GitHub Actionsで使用するワークフローファイルは以下になります。
前回との差分としてはCodeDeployを使用するために"codedeploy-appspec"、"codedeploy-application"、"codedeploy-deployment-group"を追加しています。
こちらのパラメータはCodeDeployのアプリケーション、デプロイグループ、appspec.yamlファイル名を記載してください。

name: ECS deploy

on:
  pull_request:
    branches:
      - main
    types: [closed]

env:
  AWS_ACCOUNT_ID: AWSアカウントID
  TASK_DEF: "task-dev"
  ECS_SERVICE: "service-dev"
  ECS_CLUSTER: "ecs-dev-cluster"

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    defaults:
      run:
        working-directory: ./
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: "arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/role-dev-github-oidc-ecs-deploy-001"
          aws-region: "ap-northeast-1"

      - name: Login to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v2
        id: login-ecr

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          REPOSITORY: "ecr-dev"
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build . --tag ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
          docker push ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}
          echo "image=${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }}" >> $GITHUB_OUTPUT

      - name: Download task definition
        run: |
          aws ecs describe-task-definition --task-definition ${{ env.TASK_DEF }} --query taskDefinition > task-definition.json

      - name: Fill in the new image ID in the Amazon ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: ${{ env.TASK_DEF }}
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy Amazon ECS task definition
        uses: aws-actions/amazon-ecs-deploy-task-definition@v2
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true
          codedeploy-appspec: appspec.yaml
          codedeploy-application: codedeploy-dev-app
          codedeploy-deployment-group: codedeploy-dev-group

ディレクトリ構成は以下のようにします。

.
├── .github
│   └── workflows
│       └── deploy.yaml
├── Dockerfile
├── appspec.yaml
└── html
    └── index.html

動作確認

ファイルの作成などが完了したらGitHubリポジトリへプッシュ後、mainブランチへマージしてください。
マージ後、Actionsタブからデプロイが開始していることを確認できます。
また、CodeDeployのデプロイメントからデプロイが実行されていることが確認できます。
スクリーンショット 2025-07-01 181043

デプロイ中にALBのテストポートへアクセスすることで新しいWeb画面を確認することができます。
スクリーンショット 2025-07-01 181215

さいごに

GitHub ActionsでECSのCI/CDを試してみました。
CodePipelineを使用しない分、アーティファクト用のS3バケットやCodeBuildを作成しなくてもよいので設定がシンプルになります。
GitHub Actionsには慣れていてもAWSでのCI/CDに慣れていない方であれば今回の方法はAWS側の設定項目が少なくなるのでよいと思いました。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.