
コンテナイメージのビルド&プッシュと、AWS Serverless Application Model (SAM)のビルド&デプロイを、GitHub Actionsで設定してみた
コンテナとCI/CDをGitHub Actionsで設定したい
おのやんです。
みなさん、CI/CD設定してますか?(挨拶
今回、同一のGitHub リポジトリで管理している複数システムのCI/CDを、GitHub Actionsで設定する機会がありました。GitHub Actionsのワークフローファイルはそこまで複雑なロジックもなく、設計図通りに書けるイメージでしたが、意外と動作確認などに手こずりました。また、似たような実装をする際のテンプレートも欲しいな、という気持ちにもなりました。
ということで、「最低限これを設定すれば動くようになるだろう」というワークフローファイルを、本ブログにて残しておこうと思います。
なお、今回はGitHub Actionsの記法などには言及せず、全体的な視点でお話ししようと思います。実際に筆者がGitHub Actionsをキャッチアップする際にお世話になった記事を載せておきますので、よければご確認ください。
IAMロールは既に作ってあるものとします
コードブロック中に出てくる${{ inputs.env-name }}-role-github-actions-oicdというIAMロールは、こちらのブログを参考に作成したOICD用IAMロールになりますので、ご留意いただければと思います。
GitHub Actionsワークフロー構造
GitHub Actionsのワークフローファイルは、今回次のように配置しました。
GitHub Actionsの再利用可能なワークフロー(actionsディレクトリ配下)を用いて、メインのワークフロー(workflowsディレクトリ配下)を実行する際に、メインのワークフローから再利用可能なワークフローをそれぞれ呼び出してしています。
├── actions
│   ├── container
│   │   └── action.yaml
│   ├── sam
│   │   └── action.yaml
│   └── update-ecs-service
│       └── action.yaml
└── workflows
    ├── deploy-system1-to-dev.yaml
同様の仕組みは次のブログも参考にしています。
各ワークフローファイルの紹介
それぞれのワークフローは、再利用可能なワークフローを順番に呼び出すことで構成されています。
今回は、system1というプロジェクトの中に、system1-component1(Amazon ECS(以下、ECS)上で動作しているシステム)と、system1-component2(AWS Lambda(以下、Lambda)上で動作しているシステム)の、2つのサブシステムに分けています。2つのサブシステムのAWS基盤は、ともに1つのSAMテンプレートにて管理しています。
メインで実行されるワークフロー
メインで実行されるワークフローファイルは、次のようになります。
name: Deploy system1 to dev environment
on:
  push:
    branches:
      - feature/**
    paths:
      - system1/**
  pull_request:
    branches:
      - develop
    types:
      - closed
    paths:
      - sysutem1/**
env:
  AWS_DEV_ACCOUNT_ID: xxxxxxxxxxxx
  AWS_ENV_NAME: dev
permissions:
  id-token: write
  actions: write
  contents: read
  pull-requests: read
jobs:
  #===================================================#
  # system1-component1のコンテナイメージを開発環境にプッシュ
  #===================================================#
  push_system1-component1_image_to_dev:
    needs:
      - cfn_lint_system1
      - pytest_system1-component1
      - pytest_system1-component2
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Push system1 component1 container image
        uses: ./.github/actions/container
        with:
          aws-account-id: ${{ env.AWS_DEV_ACCOUNT_ID }}
          env-name: ${{ env.AWS_ENV_NAME }}
          working-directory: system1
          image-name: system1-component1
          dockerfile-path: ./system1-component1
          image-tag: 'latest'
  #===================================================#
  # System1を開発環境にデプロイ
  #===================================================#
  deploy_system1_to_dev:
    needs:
      - push_system1-component1_image_to_dev
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Deploy System1
        uses: ./.github/actions/sam
        with:
          aws-account-id: ${{ env.AWS_DEV_ACCOUNT_ID }}
          env-name: ${{ env.AWS_ENV_NAME }}
          working-directory: system1
  #===================================================#
  # ECSサービスを更新
  #===================================================#
  update_system1_ecs_service:
    needs:
      - deploy_system1_to_dev
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Update ECS Service
        uses: ./.github/actions/update-ecs-service
        with:
          aws-account-id: ${{ env.AWS_DEV_ACCOUNT_ID }}
          env-name: ${{ env.AWS_ENV_NAME }}
          system-name: system1-component1
コンテナのワークフロー
続いて、それぞれのワークフローを見てみます。container/action.yamlでは、実行対象のディレクトリのDockerfileを参考にして、コンテナイメージをビルドし、Amazon ECR(以下、ECR)リポジトリにpushしています。
Dockerコンテナをいい感じにビルド&プッシュしてくれる有志のワークフローもあったのですが、もともと手動で運用していたコンテナ関係のコマンドが3行のみでしたので、実直にコマンドを直書きしても運用工数は変わらないだろう、ということで、run:配下にコマンドを記述しています。
name: Build container image and push it to ECR
inputs:
  aws-account-id:
    type: string
    description: AWS account id
  env-name:
    type: choice
    description: The environment name to deploy
    options:
      - dev
      - prd
  working-directory:
    type: choice
    description: The working directory to run the commands
  dockerfile-path:
    type: string
    description: Path to Dockerfile relative to working directory
  image-tag:
    type: string
    description: Image tag to push
    default: 001
  image-name:
    type: string
    description: Image name to push
runs:
  using: composite
  steps:
    - name: Get AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::${{ inputs.aws-account-id }}:role/${{ inputs.env-name }}-role-github-actions-oicd
        aws-region: ap-northeast-1
    - name: Login to ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2
    - name: Push image to ECR
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: ${{ inputs.env-name }}-${{ inputs.image-name }}-ecr
        IMAGE_TAG: ${{ inputs.image-tag }}
      run: |
        # Build Docker image
        docker build --platform linux/amd64 -t $ECR_REPOSITORY ${{ inputs.dockerfile-path }}
        # Tag Docker image for ECR
        docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        # Push Docker image to ECR
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
SAMのワークフロー
SAMのワークフローも同じく、SAMビルドとSAMデプロイのコマンドを素直に実行するワークフローとして作成しました
name: Build and deploy SAM application
inputs:
  aws-account-id:
    type: string
    description: AWS account id
  env-name:
    type: choice
    description: The environment name to deploy
    options:
      - dev
      - prd
  working-directory:
    type: choice
    description: The working directory to run the commands
runs:
  using: composite
  steps:
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: 3.12
    - name: Setup SAM
      uses: aws-actions/setup-sam@v2
    - name: Get credential
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::${{ inputs.aws-account-id }}:role/${{ inputs.env-name }}-role-github-actions-oicd
        aws-region: ap-northeast-1
    - name: SAM build
      run: sam build
      shell: bash
      working-directory: ${{ inputs.working-directory }}
    - name: SAM deploy
      run: sam deploy --config-env ${{ inputs.env-name }} --no-confirm-changeset --no-fail-on-empty-changeset
      shell: bash
      working-directory: ${{ inputs.working-directory }}
Amazon ECSサービスの更新ワークフロー
今回SAMで管理しているAWSサービスの中に、Amazon ECS(以下、ECS)があります。上記のコンテナpush、SAMアプリケーションデプロイの後に、Amazon ECR(以下、ECR)上のコンテナイメージをもとにECSのタスクを再作成する必要がありましたので、ここもGitHub Actionsの処理に加えています。
name: Update ECS Service
inputs:
  aws-account-id:
    type: string
    description: AWS account id
    required: true
  env-name:
    type: choice
    description: The environment name to deploy
    required: true
    options:
      - dev
      - prd
  system-name:
    type: string
    description: System name to update
    required: true
runs:
  using: composite
  steps:
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::${{ inputs.aws-account-id }}:role/${{ inputs.env-name }}-role-github-actions-oicd
        aws-region: ap-northeast-1
    - name: Update ECS Service
      run: |
        aws ecs update-service \
          --cluster ${{ inputs.env-name }}-${{ inputs.system-name }}-ecs-cluster \
          --service ${{ inputs.env-name }}-${{ inputs.system-name }}-ecs-service \
          --force-new-deployment
      shell: bash
こちらの実装は、次の記事を参考にしています
実装に移す際のテンプレート的な例が欲しかった
今回、GitHub Actionsで複数の処理を各Jobで順番に実行する例を作ってみました。冒頭でも紹介しましたが、この記事は、「細かい文法は理解したので素早く実装に移れるサンプルなどないかな」と思って探したが結局見つからずに自前で実装した経緯があって作成しました。
この記事がどなたかの参考になれば幸いです。では!












