なになれ

IT系のことを記録していきます。

GitHub Actionsを使ったECSのデプロイメントパイプライン構築

AWS Advent Calendar 2021 カレンダー2の6日目の記事です。

ここでは、GitHub Actionsを使ったECSのデプロイメントパイプラインを構築する方法を紹介します。
ECSのデプロイにおいて、GitHub Actionsを使う理由は以下のとおりです。

  • 環境構築、コンテナイメージのビルド、イメージのデプロイといった一連のタスクを一つのファイルに定義できる(ワークフローの定義)
  • Actionsといった形式で各タスクの記述を簡素化できる
  • GitHub OIDC トークンを使って、AWSへの処理をセキュアにできる

このような点で、現在ECSのデプロイメントパイプラインを構築するにはGitHub Actionsを使うのが有力だと考えています。

以前はGitHub ActionsからAWSにアクセスするにはアクセスキーをGitHub上で保持する必要があり、セキュリティ的に不安でした。
そのため、AWS上でパイプラインを構築する方法をよく採用していました。
ただCodePipelineやCodeBuildといったサービスを利用してパイプラインを構築するのは手間がかかると感じています。
この点が最近のGitHub Actionsのアップデートで解消されたというのが背景にあります。

今回のソースは以下にあります。

github.com

概要

デプロイメントパイプラインの構成

今回構築したデプロイメントパイプラインの構成は以下のとおりです。
左から順番に検証環境の作成、ステージング環境の更新、本番環境の更新と流れます。

f:id:hi1280:20211205153122p:plain

Pull Request作成時に検証環境が作成され、mainブランチにマージされた時にステージング環境が更新され、開発者の手動実行で本番環境が更新されます。
厳密には各環境の作成・更新はワークフローが分かれているため、処理の流れは繋がっていません。
そのため、ステージング環境更新の前に本番環境を更新するといったこともできてしまい、それを行うとエラーになります。
ただこのあたりは運用でカバーできる範囲と考えていますので、問題にはしていません。

AWSの環境構成

AWS上の環境構成は以下のとおりです。
バックエンドにDBが存在する標準的なWebアプリケーションを想定した構成です。

f:id:hi1280:20211205112639p:plain

検証環境ではAurora Serverlessを利用していて、ステージング環境、本番環境では通常のAuroraを利用しています。
検証環境は頻繁に作り直しが発生することが予想されるための配慮です。

構築方法

今回の構成を構築するにあたっての要点を解説したいと思います。

GitHub Actions環境でのAWS認証

前述のとおり、AWSとの認証にはGitHubのOIDCトークンを利用しています。
各ジョブにおいて、以下の通りにAWSと認証を行なっています。

create-qa.yml

...
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-role
        aws-region: ap-northeast-1
...

OIDCによる認証方式はaws-actions/configure-aws-credentialsのActionで対応されているので非常に簡単です。

認証を行うためのAWS側の設定は共通のTerraformリソースで作成しています。以下の内容になります。

iam.tf

resource "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com"
  ]

  thumbprint_list = [
    "a031c46782e6e6c662c2c87c76da9aa62ccabd8e"
  ]
}

resource "aws_iam_role" "github" {
  name = "github-role"

  assume_role_policy = <<-EOS
  {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Federated": "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
        },
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringLike": {
            "token.actions.githubusercontent.com:sub": [
              "repo:${var.repository}:*"
            ]
          }
        }
      }
    ]
  }
  EOS
}

resource "aws_iam_role_policy_attachment" "github" {
  role       = aws_iam_role.github.name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

内容を簡潔にするために、GitHub Actionsで利用するIAMロールにはAdministratorAccessのIAMポリシーを与えています。
実利用に合わせてIAMポリシーを見直してください。

GitHub Actionsのトリガー

各種GitHubのイベントをトリガーにすることで、パイプライン化しています。
検証環境への操作はPull Requestをトリガーにして、各種操作時にワークフローを実行します。

Pull Requestを作成した際にワークフローを実行します。

create-qa.yml

...
on:
  pull_request:
    types: [opened]
    branches:
      - main
...

Pull Requestを更新した際には、types: [synchronize]を指定することで、Pull Requestに対してのコミットで検証環境が更新されるようにします。
Pull Requestをマージした時やクローズした時には、types: [closed]を指定することで、検証環境が削除されるようにします。

ステージング環境の更新はPull Requestがマージされた時にmainブランチにPushされることをトリガーにします。

update-staging.yml

...
on:
  push:
    branches:
      - main
...

本番環境の更新はworkflow_dispatchで手動実行に対応します。

update-production.yml

...
on:
  workflow_dispatch
...

GitHub Actionsの画面から手動でワークフローを実行することもできますが、deploy-production.shという簡単なシェルスクリプトを用意して、ワークフローを実行できるようにしています。

Terraformの実行環境

検証環境、ステージング環境、本番環境といった各環境の構築には、CodeBuild上でTerraformを実行しています。
CodeBuildにVPCを設定することで、VPC上のAuroraに接続が可能になります。
これにより、DBへのクエリ実行をTerraformから可能になります。
CodeBuildに設定するサブネットはプライベートサブネットである必要があり、パブリックサブネットにNATゲートウェイを配置して、アクセスできるようにします。

以下が参考情報です。
Amazon Virtual Private Cloud での AWS CodeBuild の使用 - AWS CodeBuild

検証環境の構築

検証環境はPull Request毎に作成しています。これにはTerraformのWorkspaces機能で対応しています。
Workspacesを使うと、同じTerraformのリソースを元にして、別の環境を作成することができます。

以下のように、CodeBuildのbuildspecでTerraformを実行する際にWorkspacesを使っています。
terraform workspace newといったコマンドでWorkspacesで用意した別環境に対してTerraformを実行できます。

apply.yml

version: 0.2

phases:
  build:
    commands:
      - cd terraform/qa
      - terraform init -backend-config="bucket=$TFSTATE_BUCKET"
      - terraform workspace new $ENV || true
      - terraform workspace select $ENV
      - terraform apply -var tfstate_bucket=$TFSTATE_BUCKET -auto-approve

これにより、Pull Request毎に検証環境を作成することができています。

ECSへのデプロイ

ecspressoを利用しています。GitHub Actions上でのセットアップにActionが提供されており、GitHub Actionsで利用する場合には簡単に利用することができます。
ecspressoを使うと、環境変数の設定をコードで管理できますし、Terraformで作成した環境情報を参照できますので、非常に便利です。

GitHub Actionsの実行結果通知

検証環境への各種操作では、GitHubのPull Requestにコメントを行うことで通知としています。

create-qa.yml

...
    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Create comments
      env:
        GH_TOKEN: ${{ github.token }}
        GH_REPO: ${{ github.repository }}
        DOMAIN: ${{ secrets.DOMAIN }}
      run: |
        gh pr comment ${{ github.event.pull_request.number }} --body "検証環境を作成しました。
        https://$ENV.$DOMAIN"
...

GitHub CLI(ghコマンド)を使うとPull RequestへのコメントなどGitHubへの操作が簡単です。

ステージング環境、本番環境の更新では、Slackのチャンネルにメッセージすることで通知しています。

...
    steps:
    - name: Post to a Slack channel
      id: slack
      uses: slackapi/slack-github-action@v1.16.0
      env:
        SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
      with:
        channel-id: ${{ secrets.SLACK_CHANNEL }}
        slack-message: |
          ステージング環境を更新しました(${{ env.IMAGE_TAG }})
          https://staging.${{ secrets.DOMAIN }}
...

Slackへのメッセージにあたっては、以下のSlack App方式を採用しています。

GitHub - slackapi/slack-github-action: Send data into Slack using this GitHub Action!

各実行結果

Pull Requestを作成すると以下のように検証環境を作成して、コメントが投稿されます。
f:id:hi1280:20211205152451p:plain:w900

Pull Requestをクローズすると検証環境を削除します。
f:id:hi1280:20211205152535p:plain:w900

mainブランチにマージされてステージング環境が更新されるとSlackにメッセージされます。
また、本番環境が更新される場合でも同様です。
f:id:hi1280:20211205152615p:plain:w700

まとめ

今までECSのデプロイメントパイプラインを構築するにあたってはこれといった手法がなかったのですが、今回GitHub Actionsを使用してパイプラインを構築することで満足するものができたと思います。
GitHub OIDCトークンの話など各所で便利な仕組みが作られているので日々キャッチアップして、上手に使っていくことが必要だなと感じています。
厳密にパイプラインを管理したい場合、誰でも本番環境を更新できてしまうのでマズイところもあると思います。
ただ簡易的に運用したい場合には、今回のような仕組みで十分だと考えています。

参考

今回の内容を実施するにあたって、以下を参考にさせて頂きました。

GitHub Actions + AWS CodeBuildでPRごとの検証環境を作ってみた