GitHub Actions + ecspressoでCI/CDパイプラインを構築する

GitHub Actions + ecspressoでCI/CDパイプラインを構築する
この記事をシェアする

こんにちは、クラウドビルダーズのきむです。

本記事は、クラウドビルダーズ株式会社のアドベントカレンダー2024の8日目の記事となります!

今回は下記の投稿の続きです。

上記の投稿ではecspressoを使用してECSをデプロイする方法をご紹介しました。

実際の運用ではCI/CDパイプラインを構築してデプロイの自動化をすることが多いと思います。

今回はGitHub Actionsを使用してCI/CDパイプラインを構築します。

ソースコード

今回使用するソースコードは下記に置いています。

IAMロールの作成

ECRへのイメージプッシュやAWS CDKの利用をする場合はAWS認証情報が必要になります。

GitHub Actionsでは下記を使用してIAMロールによる一時的な認証情報を使用することができます。

GitHub Actionのワークフローを定義する前にIAMロールを作成します。

下記のファイルでIAMロールを定義しています。

import { Construct } from 'constructs';

import * as iam from 'aws-cdk-lib/aws-iam';

import { Props } from '@bin/main';

export class Iam extends Construct {
  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id);

    const repoName = process.env.GIT_REPO;

    if (repoName) {
      const oidcProvider = new iam.OpenIdConnectProvider(
        this,
        'GitHubOIDCProvider',
        {
          url: 'https://token.actions.githubusercontent.com',
          clientIds: ['sts.amazonaws.com'],
        },
      );

      new iam.Role(this, 'GitHubOIDCRole', {
        roleName: `${props.prefix}-github-oidc-role`,
        assumedBy: new iam.FederatedPrincipal(
          oidcProvider.openIdConnectProviderArn,
          {
            StringEquals: {
              'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
            },
            StringLike: {
              'token.actions.githubusercontent.com:sub': [
                `repo:${repoName}:ref:refs/heads/*`,
                `repo:${repoName}:pull_request`,
              ],
            },
          },
          'sts:AssumeRoleWithWebIdentity',
        ),
        managedPolicies: [
          iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
        ],
      });
    }
  }
}

検証のためにAdministratorAccessをロールにアタッチしていますが、実際の運用では適切なポリシーをアタッチしましょう。

環境変数GIT_REPOにはGitHub Actionsを実行するリポジトリを設定しています。

cdk.shでIAMロールをデプロイしましょう。

./cdk.sh deploy:n 

デプロイが完了したら作成されたIAMロールのARNが必要になるので控えておきます。

シークレットの設定

上記で控えたIAMロールのARNやイメージを保存するECRのURIなどをActionsのシークレットに設定し、Actionsから参照します。

まずはリポジトリのSettingsを開きます。

続いてActionsのシークレット設定画面を開きます。

Repository secretsにシークレットを追加します。

下記の通り2つのシークレットを追加しました。

ワークフロー定義の作成

今回はmainブランチへのpushをトリガーとして実行されるActionsを作成します。

ワークフロー定義はAWS CDK用とecspresso用の2つを用意しました。

いずれのワークフローもpath-filterを使用して関連するファイルが変更された場合にのみ適切なデプロイが実行されるようにしています。

cdk workflow

name: cdk workflow

permissions:
  id-token: write
  contents: read

on:
  push:
    branches: [main]
  workflow_dispatch:

env:
  # Github Actionsを実行しているリポジトリ名を取得
  GIT_REPO: ${{ github.repository }}

jobs:
  initialize:
    runs-on: ubuntu-latest
    permissions: read-all
    outputs:
      infra_changed: ${{ steps.filter.outputs.infra }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # infraディレクトリ以下のファイル変更を検知
      - name: Detect File Changes
        uses: dorny/paths-filter@v3
        id: filter
        with:
          base: ${{ github.event.before }}
          filters: |
            infra:
              - 'infra/bin/**'
              - 'infra/lib/**'

  deploy:
    runs-on: ubuntu-latest
    needs: [initialize]
    # infraディレクトリ以下のファイル変更があった場合のみデプロイを実行
    if: |
      github.event_name == 'push' &&
      needs.initialize.outputs.infra_changed == 'true'
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: npm install
        shell: bash
        run: |
          npm install
        working-directory: infra

      - name: Set Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
          aws-region: ap-northeast-1

      - name: Run CDK Deploy
        run: |
          npm run cdk:diff -- -c isFirst=n
        working-directory: infra

ecsoreso workflow

name: ecsoreso workflow

permissions:
  id-token: write
  contents: read

on:
  push:
    branches: [main]
  workflow_dispatch:

env:
  SYS_NAME: ecspresso-demo
  CLUSTER: cluster
  SERVICE: service
  TAG: ${{ github.sha }} # コミットハッシュを取得
  APPLICATION_NAME: ecs-app
  DEPLOYMENT_GROUP_NAME: ecs-deploy-group
  ECR_URI: ${{ secrets.ECR_URI }} # シークレットからECR URIを取得

jobs:
  initialize:
    runs-on: ubuntu-latest
    permissions: read-all
    outputs:
      app_changed: ${{ steps.filter.outputs.app }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # appディレクトリ以下のファイル変更を検知
      - name: Detect File Changes
        uses: dorny/paths-filter@v3
        id: filter
        with:
          base: ${{ github.event.before }}
          filters: |
            app:
              - 'app/main.go'
              - 'app/ecspresso/**'

  deploy:
    runs-on: ubuntu-latest
    needs: [initialize]
    # appディレクトリ以下のファイル変更があった場合のみデプロイを実行
    if: |
      github.event_name == 'push' &&
      needs.initialize.outputs.app_changed == 'true'
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Set Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
          aws-region: ap-northeast-1

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: 1.22.5

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

      - name: Build and Push Image
        uses: docker/build-push-action@v5
        with:
          context: ./app
          platforms: linux/arm64
          push: true
          tags: ${{ env.ECR_URI }}:${{ github.sha }}

      - name: Set up ecspresso
        uses: kayac/ecspresso@v2
        with:
          version: latest

      - run: |
          ecspresso deploy --config ecspresso/ecspresso.jsonnet
        working-directory: app

作成されるECSはARMアーキテクチャとなっているので、QEMUとBuildxのセットアップを行いARM用のイメージをビルドできるようにしています。イメージのタグにはGitHubのコミットハッシュ値を使用します。

GitHub Actionsを実行する

実際にリポジトリにpushを行いActionsを実行してみます。

Actions実行前にALBにアクセスすると’Hello, World!’が返却されるようになっています。

curl -i http://ecspresso-demo-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
HTTP/1.1 200 OK
Date: Sat, 07 Dec 2024 12:19:59 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 13
Connection: close     

Hello, World!%

main.goを修正して返却される文字列を変更してリポジトリにpushします。

package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()

	e.GET("/", func(c echo.Context) error {
		// return c.String(http.StatusOK, "Hello, World!") コメントアウト
		return c.String(http.StatusOK, "Welcome to ecspresso-demo!") // コメントアウト解除
	})

	e.Logger.Fatal(e.Start(":8080"))
}
git add .
git commit -m 'ecspresso demo'
git push origin HEAD

Actionsの画面を確認すると2つのワークフローが実行されています。

AWS CDK用のワークフローを確認すると、deployが実行されていないことがわかります。

変更したファイルは’app/main.go’のみとなっているので、path-filterによりAWS CDKの関連ファイルの変更が検知されなかったためです。

ecspresso用のワークフローではファイルの変更を検知しdeployまで実行されています。

デプロイ完了後にALBへアクセスすると、返却される文字列が修正後の’Welcome to ecspresso-demo’に変更されています。

curl -i http://ecspresso-demo-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com

HTTP/1.1 200 OK
Date: Sat, 07 Dec 2024 13:26:00 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 26
Connection: close     

Welcome to ecspresso-demo!%  

さいごに

専用のActionsが用意されているのでGitHub Actionsでもecspressoを簡単に利用することができます。

今回はpushをトリガーにデプロイするのみでしたが、pull requestをトリガーにテスト行うなどの処理を組み込めばより実用的なワークフローを構築することができると思います。ぜひ活用してみてください。

この記事をシェアする
著者:きむ
フェスとポケカに夢中です