GitHub ActionsでECSのPR環境を作る
はじめに
久しぶりにAWSを触る機会があったので、キャッチアップも兼ねて、ECS/Fargateを使ったPR環境(PRごとに独立した環境)を構築するワークフローを試しに作ってみました。
CloudRunやVercelのようにPR環境を簡単に作れるサービスもありますが、ECSではそのような機能がないため、自前で実装する必要があります。
この記事では、GitHub ActionsとTerraformを使ってPR環境を構築する流れを雑にまとめます。
前提
今回はとりあえずミニマムに検証したかったので、以下のようなシンプルなインフラ構成を作って検証してみました(面倒なのでデータストアは省略しています)。
-
Route53
- PR環境ごとにサブドメインを発行
-
CloudFront
- 代替ドメインにワイルドカードを指定して、PR環境ごとのサブドメインを利用可能にしている
-
ALB
- VPC Originとしてプライベートサブネットに配置
-
ECS
- Nginxをデプロイ
上記の構成に対して、PRごとにGitHub ActionsとTerraformを使って以下のリソースを作成します。
-
Route53のAレコード
- PR環境のドメインのAliasレコードを作りCloudFrontに紐付け
-
ALBのターゲットグループ
- PR環境のECSサービスに紐付けるやつ
-
ALBのリスナールール
- ホストヘッダベースのルールで、PR環境のドメイン宛のリクエストをPR環境のターゲットにルーティングする
-
ECSサービス
- PRブランチのコードをデプロイして動かすECSサービス
PR環境作成用のTerraformディレクトリ構成
PR環境用のリソースを定義するTerraformコードを以下のように配置しています。
terraform/pr-env/
├── main.tf # PR環境作成のためのリソースを定義
├── pr-env.tfbackend # ステートファイルの保存先を指定
├── pr-env.tfvars
├── providers.tf
└── variables.tf
各PR環境のリソースを単一のステートファイルで管理するのは面倒なので、PR環境ごとにステートファイルを分けるためにステートファイルの保存先を動的に切り替えられるようにしています。
具体的には、以下のようにpr-env.tfbackend
ファイル内のkey
の値にプレースホルダー(PR_NUMBER
)を設定し、GitHub Actionsの処理内でTerraformで各PR環境を構築する際にプレースホルダーをPR番号に置換するようにします。
bucket = "xxxx"
key = "PR_NUMBER/terraform.tfstate"
encrypt = true
region = "ap-northeast-1"
use_lockfile = true
GitHub Actionsのワークフロー
pr-env-create.yml
)
PR環境作成用ワークフロー (PRにpr-deploy
ラベルを付与、またはラベル付与後にコードをpushすると以下の処理を実行するようにしています。
-
コンテナイメージのビルドとプッシュ
PRのコードを元にDockerイメージをビルドし、Amazon ECRにプッシュします。 -
ECSタスク定義の登録
ビルドしたDockerイメージを使用してECSタスク定義を登録します。 -
TerraformでPR環境用リソースを作成
Terraformを使ってPR環境用のリソースを作成します。 -
ECSサービスにデプロイ
ecspresso
を使ってECSサービスにデプロイします。
上記の処理のうち、ポイントについて以下で解説します。
ワークフローのトリガー
pr-deploy
ラベル付与とラベル付与後のpushをトリガーにするために、トリガーは以下のように設定しています。
on:
pull_request:
types: [opened, synchronize, labeled]
また、各ジョブの実行条件を以下のように設定することで、ラベル付与時とラベル付与後のpush時ににワークフローが実行されるようにしています。
if: contains(github.event.pull_request.labels.*.name, 'pr-deploy') && (github.event.action == 'labeled' || github.event.action == 'synchronize')
TerraformでPR環境用リソースを作成
pr-env.tfbackend
内のPR_NUMBER
をPR番号に置換します。
- name: Replace PR_NUMBER
working-directory: terraform/pr-env
run: |
sed -i -e "s/PR_NUMBER/${{ github.event.pull_request.number }}/g" pr-env.tfbackend
その後、Terraformのinit
実行時に-backend-config
オプションでpr-env.tfbackend
を指定してapplyすることでPR環境ごとにステートファイルを独立させています。
- name: Terraform Init
working-directory: terraform/pr-env
run: terraform init -backend-config=pr-env.tfbackend
- name: Terraform Apply
working-directory: terraform/pr-env
run: |
terraform apply \
-auto-approve \
-var-file=pr-env.tfvars \
-var "pr_number=${{ github.event.pull_request.number }}" \
-var "ecs_cluster_name=${{ env.ECS_CLUSTER_NAME }}" \
-var "ecs_task_definition_arn=${{ needs.build.outputs.task_definition_arn }}" \
-var "ecs_service_name=${{ env.ECS_SERVICE_NAME }}" \
-var "ecs_container_name=${{ env.ECS_CONTAINER_NAME }}"
pr-env-destroy.yml
)
PR環境削除用ワークフロー (PRのpr-deploy
ラベルを外す、またはPRをクローズすると以下の処理が実行されます。
-
TerraformでPR環境用リソースを削除
Terraformを使ってPR環境用リソースを削除します。
ポイントはPR環境作成の時とほぼ同じですが、以下で解説します。
ワークフローのトリガー
以下の条件でワークフローが実行されます。
on:
pull_request:
types: [unlabeled, closed]
また、各ジョブの実行条件を以下のように設定することで、ラベル除外時とPRクローズ時にワークフローが実行されるようにしています。
if: >
github.event.label.name == 'pr-deploy' && github.event.action == 'unlabeled' ||
github.event.action == 'closed'
TerraformでPR環境用リソースを削除
PR環境用リソース作成時と同じように、pr-env.tfbackend
内のPR_NUMBER
をPR番号に置換し、PR環境ごとにbackendを切り替えるようにしています。
- name: Replace PR_NUMBER
working-directory: terraform/pr-env
run: |
sed -i -e "s/PR_NUMBER/${{ github.event.pull_request.number }}/g" pr-env.tfbackend
上記のbackendを指定して、Terraformのinit
とdestroy
を行うことでPRに紐づくPR環境を削除するようにしています。
- name: Terraform Init
working-directory: terraform/pr-env
run: terraform init -backend-config=pr-env.tfbackend
- name: Terraform Destroy
working-directory: terraform/pr-env
run: |
terraform destroy \
-auto-approve \
-var-file=pr-env.tfvars \
-var "pr_number=${{ github.event.pull_request.number }}" \
-var "ecs_cluster_name=" \
-var "ecs_task_definition_arn=" \
-var "ecs_service_name=" \
-var "ecs_container_name="
所感
とりあえずミニマムにPR環境をザクッと作るワークフローを試してみて、割と楽に作れそうなイメージが掴めました。
また、時間がある時に以下なども実装してみようと想います。
- PR環境ごとにDBも作る
- 通知周り
Discussion