はじめに
私はTerraformを用いたクラウドインフラ構築・GitHubによるコード管理や、IaaS上の仮想サーバー設定の業務を担当しています。
この記事では、私が担当する業務で発生する課題と、その対応策としてのGitHub Actionsを用いたCI/CD実装例をご紹介します。
※ ご紹介するGitHub Actionsのワークフロー実装例には、サードパーティ製のアクションが含まれています。
サードパーティ製のアクションはサプライチェーン攻撃の対象となるケースがあります。
利便性とセキュリティリスクのトレードオフを認識したうえで活用する必要があります。
課題
Terraformを用いたクラウドインフラ構築・GitHubによるコード管理やIaaS上の仮想サーバー設定の業務では、以下3つの課題が発生し得ます。
PullRequest(PR)でのコミュニケーション
PR上では、レビュアーとレビュイーがコミュニケーションを取りながら、コードの変更内容や意図について認識を合わせる必要があります。
PRのテンプレートを用意して記載ルールを整備する方法もありますが、担当者ごとに説明の粒度や精度が異なります。
レビュイーがどの観点でセルフチェックを行ったのか、レビュアーにどこを重点的に確認して欲しいのかが読み取れないケースも少なくありません。
その結果、コミュニケーションに摩擦が生じ、関係性が悪化してしまうこともあります。
terraform planの実行結果を取得する
PRを作成・レビューする際には、terraform planの実行結果が適切であることを確認することが必須となります。
しかし、PRのたびに手作業でterraform planを実行して結果を添付するのは大きな負担となりがちです。
作業の過程で誤ってterraform applyを実行してしまうといったヒューマンエラーのリスクも発生します。
サーバーに対する手動操作
特に運用フェーズでは、各種サーバーにログインして様々なコマンドを実行する必要があります。
事前に検証済みのコマンドであっても、接続先の指定ミスや実行順の誤りといったヒューマンエラーの余地が残されています。
また、サーバー管理のためにクライアントソフトなどのツールを利用する場合、多くの場合は接続元として踏み台サーバーを用意します。
踏み台サーバーは作業用環境であることから「必要なツールをすぐに導入したい」という要望も多く、勝手に設定を変更されてしまうこともあります。
災害対策やセキュリティ対策、保守運用などの維持コストも発生します。
以降、それぞれ以下を例に取って、各種課題の軽減につながるCI/CDの仕組みをご紹介します。
- Terraformによるインフラ構築:AWSを用いた基盤構築
- サーバーに対する手動操作:AWS環境でのDBマイグレーション作業
対応策
各課題に対して、それぞれ以下の対応策をご紹介します。
- 課題:PullRequest(PR)でのコミュニケーション
- 対応策:PRの内容の説明や確認観点の抽出をAIに補助してもらう
- 課題:
terraform planの実行結果を取得する- 対応策:PRを契機に自動的に
terraform planを実行する
- 対応策:PRを契機に自動的に
- 課題:サーバーに対する手動操作
- 対応策:git pushを契機に自動的にECSタスクを起動してコマンドを実行する
対応策の全体像
構成図

GitHub設定
ディレクトリ構成
GitHubリポジトリー/ ├── .github/ | ├── workflows/ | | ├── pr_agent.yml | | ├── tf_fmt.yml | | ├── tf_plan.yml | | └── db_migrate.yml | └── actions/ | ├── call_aws_step_functions | | └── action.yml | └── generate_aws_credential_string | └── action.yml └── environments/ └── dev/ ├── docker/ | ├── app/ | | ├── prisma/ | | | ├── .env | | | └── schema.prisma | | └── migrate.sh | └── Dockerfile ├── lambda_function/ | └── index.js ├── main.tf └── terraform.tfシークレット設定
Repository secrets ├── OPENAI_KEY ├── AWS_BACKEND_EXTERNAL_ID ├── AWS_BACKEND_ROLE_ARN ├── AWS_DEPLOY_EXTERNAL_ID ├── AWS_DEPLOY_ROLE_ARN ├── AWS_ROLE_ARN ├── DB_MGR_ECR_IMAGE_TAG ├── DB_MGR_ECR_REGISTRY ├── DB_MGR_ECR_REPOSITORY ├── DB_MGR_ROLE_ARN ├── DB_MGR_ROLE_EXTERNAL_ID └── DB_MGR_SFN_ARN以降、個々の対応策の詳細についてご紹介します。
対応策:PRの内容の説明や確認観点の抽出をAIに補助してもらう
PR-Agent ⧉というツールを使用して、AIによるフィードバックを実現します。
利用するAIは、OpenAI、Azure OpenAI、Amazon Bedrockなどの中からユーザーが選択できます。
AIの利用には別途料金が発生しますが、PR-Agent自体は無料で使用することができます。
レビュイーはAIによるフィードバックを受けて、セルフチェックの質を高めた上でレビューを依頼します。
レビュアーはAIによるフィードバックを参考にして、レビューの観点を考えることができます。
構成図

対応策導入後のシーケンス
展開
sequenceDiagram
actor レビュイー
participant GitHub as GitHub
Repository
participant pr_agent as pr_agent
(GHA)
participant OpenAI
actor レビュアー
%% 初回プッシュとPR作成
rect rgba(224,242,254,0.3)
note over レビュイー,GitHub: 初回プッシュとPR作成
レビュイー ->> GitHub: Terraformコードをgit push
レビュイー ->> GitHub: PRを作成
GitHub ->> pr_agent: PR作成イベント通知
end
%% PR-Agent によるレビュー
rect rgba(224,242,254,0.3)
note over pr_agent,OpenAI: PR-Agent によるレビュー
pr_agent ->> OpenAI: PRの内容を送信して分析
OpenAI ->> pr_agent: 概要・難易度・レビューコメント
Note left of pr_agent: PR内容の説明や確認観点をAIで補助
pr_agent ->> GitHub: レビューコメントを投稿
GitHub ->> レビュイー: コメント通知
loop 1, PR-Agentによる指摘数
レビュイー ->> GitHub: レビュー結果を確認
alt 修正が必要
レビュイー ->> レビュイー: Terraformコードを修正
レビュイー ->> GitHub: Terraformコードをgit push
GitHub ->> pr_agent: PR更新イベント通知
pr_agent ->> OpenAI: レビュー依頼
OpenAI ->> pr_agent: レビューコメント
pr_agent ->> GitHub: レビューコメントを更新
GitHub ->> レビュイー: コメント通知
else 修正不要
レビュイー ->> GitHub: 修正不要である旨コメントを投稿
end
end
end
%% レビュアーによるレビュー
rect rgba(224,242,254,0.3)
note over レビュイー,レビュアー: レビュアーによるレビュー
レビュイー ->> レビュアー: レビュー依頼
レビュアー ->> GitHub: PRを確認
alt 指摘事項あり
レビュアー ->> GitHub: レビュー指摘コメント投稿
GitHub ->> レビュイー: コメント通知
loop 1, 指摘事項の数
レビュイー ->> GitHub: レビュー結果を確認
レビュイー ->> レビュイー: Terraformコードを修正
レビュイー ->> GitHub: Terraformコードをgit push
レビュイー ->> レビュアー: レビュー依頼
レビュアー ->> GitHub: 指摘が解消されたことを確認
end
end
レビュアー ->> GitHub: PRを承認
end
導入方法
1. OpenAIのAPIキーを発行する@OpenAI
- 前提
- PR-AgentからAIにアクセスするためにAPIキーが必要です。
- Azure OpenAIやAmazon Bedrockも使用可能ですが、この記事ではOpenAIを使用することとします。
- 様々なモデルに対応していますが、以下のドキュメントに記載の通り、2025年10月現在ではデフォルトでGPT-5が使用されます。
- 以降の手順は、OpenAIのアカウントを取得済みであることを前提とします。
- 以下にてAPIキーを発行する
- OpenAIの支払い情報を設定する
- OpenAIのAPIキーを使用するためには、課金が必要なので以下の画面から支払い情報の設定を行います。
2. 取得したAPIキーをGitHubリポジトリーのシークレットに登録する@GitHub Repository
- 取得したAPIキーをGitHubリポジトリーのシークレット
OPENAI_KEYに設定する
3. PR-Agentを実行するワークフローを作成する@GitHub Repository
- 以下のドキュメントを参照し、PR-Agentを実行するワークフローを作成する
-
https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action ⧉
-
今回は、
.github/workflows/pr_agent.ymlを以下のように設定しました。.github/workflows/pr_agent.yml
.github/workflows/pr_agent.yml name: Pr Agenton:pull_request:issue_comment:jobs:pr_agent_job:if: ${{ github.event.sender.type != 'Bot' }}runs-on: ubuntu-latestpermissions:issues: writepull-requests: writecontents: writename: Run pr agent on every pull request, respond to user commentssteps:- name: PR Agent action stepid: pragentuses: qodo-ai/pr-agent@mainenv:OPENAI_KEY: ${{ secrets.OPENAI_KEY }}GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}github_action_config.auto_review: "true"github_action_config.auto_describe: "true"github_action_config.auto_improve: "true"github_action_config.pr_actions: '["opened", "reopened", "ready_for_review", "review_requested", "synchronize"]'
-
使用感
PR作成時には"example"というコメントしか記載していない

PR Agentによる変更内容の要約が追記される

タイポを含むコードをpushした場合

機密情報を含むコードをpushした場合

パラメーターを設定することで、PR-Agentによるコメントを日本語化することができます。
.github/workflows/pr_agent.ymlの設定で、PR-Agentによるコメントを日本語化する
name: Pr gent
on: pull_request: issue_comment:
jobs: pr_agent_job: if: ${{ github.event.sender.type != 'Bot' }} runs-on: ubuntu-latest permissions: issues: write pull-requests: write contents: write name: Run pr agent on every pull request, respond to user comments
steps: - name: PR Agent action step id: pragent uses: qodo-ai/pr-agent@main env: OPENAI_KEY: ${{ secrets.OPENAI_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} github_action_config.auto_review: "true" github_action_config.auto_describe: "true" github_action_config.auto_improve: "true" github_action_config.pr_actions: '["opened", "reopened", "ready_for_review", "review_requested", "synchronize"]' CONFIG.RESPONSE_LANGUAGE: 'ja-JP' # 日本語化のための設定日本語によるコメント

効果
本対応策を導入すると、開発効率を向上させるのに十分なクオリティのコメントが素早く自動的に生成されます。
結果として、レビュアーとレビュイーの間にAIが入り込み、コミュニケーションの摩擦が軽減されます。
対応策:PRを契機に自動的にterraform planを実行する
PRの作成や変更のタイミングで、GitHub Actionsから自動的にterraform planを実行します。
構成図

対応策導入後のシーケンス
展開
sequenceDiagram
actor 作業者
participant GitHub as GitHub
Repository
participant tf_fmt as tf_fmt
(GHA)
participant tf_plan as tf_plan
(GHA)
participant AWS
%% 初回プッシュとPR作成
rect rgba(224,242,254,0.3)
note over 作業者,GitHub: 初回プッシュとPR作成
作業者 ->> GitHub: Terraformコードをgit push
作業者 ->> GitHub: PRを作成
end
%% Terraform plan 自動実行
rect rgba(224,242,254,0.3)
note over GitHub,AWS: Terraform plan 自動実行
par terraform fmt
GitHub ->> tf_fmt: PR作成イベント通知
tf_fmt ->> tf_fmt: terraform fmt
tf_fmt ->> GitHub: terraform fmt結果コメントを投稿
and terraform plan
GitHub ->> tf_plan: PR作成イベント通知
tf_plan ->> AWS: terraform plan
Note left of tf_plan: PRを契機に自動的に
`terraform plan`を実行
tf_plan ->> GitHub: terraform plan結果コメントを投稿
end
end
%% Terraform plan 自動実行結果確認
rect rgba(224,242,254,0.3)
note over GitHub,作業者: 結果確認
GitHub ->> 作業者: コメント通知
作業者 ->> GitHub: コメント確認
end
導入方法
1. OIDC providerを作成する@AWSアカウント
- 以下のドキュメントを参照し、AWS IAMにてOIDC providerを作成する
-
今回は以下のように設定しました。

2. IAMロールを作成する@AWSアカウント
- GitHub ActionsにてAssumeRoleするIAMロールを作成する
- 以下のドキュメントを参考にIAMロールを作成します。
- GitHub ActionsからAssumeRoleするIAMロールには、Terraformのバックエンドへのアクセスやデプロイに使用するIAMロールとStepFunctions実行用IAMロールへのスイッチロールを許可します。
- GitHub Actionsのワークフロー内でAWS Profileを設定することで、それぞれのIAMロールにスイッチロールします。
- OIDC providerを作成するAWSアカウントとTerraformでAWSリソースをデプロイしたいAWSアカウントが異なる場合もあるかと思いますので、このような方式にしています。
- 今回は以下のように設定しました。
※ 各種ポリシーの内容は必要に応じて絞り込みや追加を行う必要があります。
IAM Role 信頼ポリシー 許可ポリシー AWS_ROLE
GitHub Actions が OIDC により AssumeRole展開
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWSAccountId}:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": "repo:${GitHubOrganizationName}/${GitHubRepositoryName}:*" } } } ] }展開
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "${AWS_BACKEND_ROLEのARN}", "${AWS_DEPLOY_ROLEのARN}", "${AWS_MGR_ROLEのARN}" ] } }AWS_BACKEND_ROLE
Terraform バックエンド用 S3展開
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "${AWS_ROLEのARN}" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "${外部ID}" } } } ] }展開
AmazonS3FullAccessAWS_DEPLOY_ROLE
AWS リソースデプロイ用展開
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "${AWS_ROLEのARN}" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "${外部ID}" } } } ] }展開
AmazonS3FullAccess
3. 作成したIAMロールの情報をGitHubリポジトリーのシークレットに登録する@GitHub Repository
- 各種IAMロールのARNと外部IDについて、以下のGitHubリポジトリーのシークレットに設定する
- AWS_ROLE_ARN:AWS_ROLEのARN
- AWS_BACKEND_ROLE_ARN:AWS_BACKEND_ROLEのARN
- AWS_BACKEND_EXTERNAL_ID:AWS_BACKEND_ROLEの外部ID
- AWS_DEPLOY_ROLE_ARN:AWS_DEPLOY_ROLEのARN
- AWS_DEPLOY_EXTERNAL_ID:AWS_DEPLOY_ROLEの外部ID
4. terraform fmtと修正提案を実行するワークフローを作成する@GitHub Actions
- 以下のページを参照して、
terraform fmt実行と提案を自動化します。
-
今回は、
.github/workflows/tf_fmt.ymlを以下のように設定しました。.github/workflows/tf_fmt.yml
.github/workflows/tf_fmt.yml name: Terraform fmton:pull_request:env:TF_VERSION: 1.12.1permissions:id-token: writecontents: readpull-requests: writejobs:fmt:name: Terraform fmtruns-on: ubuntu-lateststeps:- name: Check out repositoryuses: actions/checkout@v4with:ref: ${{ github.event.pull_request.head.sha }}- name: Setup Terraformuses: hashicorp/setup-terraform@v3with:terraform_version: ${{ env.TF_VERSION }}- name: Terraform fmtrun: terraform fmt -recursive- name: suggestuses: reviewdog/action-suggester@v1with:toolname: terraform fmtfail_on_error: true
5. terraform planを実行するワークフローを作成する@GitHub Actions
terraform planの実行を自動化します。
-
今回は、以下のように設定しました。
.github/workflows/tf_plan.yml
.github/workflows/tf_plan.yml name: Terraform planon:pull_request:env:TF_VERSION: 1.12.1AWS_DEFAULT_REGION: ap-northeast-1permissions:id-token: writecontents: readpull-requests: writeissues: writejobs:tf_plan:name: Terraform planruns-on: ubuntu-lateststrategy:matrix:workdir:- environments/devsteps:- name: Git checkoutuses: actions/checkout@v4with:ref: ${{ github.event.pull_request.head.sha }}- name: Generate aws Backend credential stringid: gen_aws_backend_cred_struses: ./.github/actions/generate_aws_credential_stringwith:profile: "terraform-backend"aws_role_arn: ${{ secrets.AWS_BACKEND_ROLE_ARN }}aws_role_external_id: ${{ secrets.AWS_BACKEND_EXTERNAL_ID }}- name: Generate aws deploy credential stringid: gen_aws_deploy_cred_struses: ./.github/actions/generate_aws_credential_stringwith:profile: "terraform-deploy"aws_role_arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}aws_role_external_id: ${{ secrets.AWS_DEPLOY_EXTERNAL_ID }}- name: Write aws credential stringrun: |mkdir -p $HOME/.awsecho "${{ steps.gen_aws_backend_cred_str.outputs.aws_credential }}" >> $HOME/.aws/configecho "${{ steps.gen_aws_deploy_cred_str.outputs.aws_credential }}" >> $HOME/.aws/config- name: Get aws credentialuses: aws-actions/configure-aws-credentials@v4with:role-to-assume: ${{ secrets.AWS_ROLE_ARN }}role-session-name: iar-gha-con-${{ github.run_id }}aws-region: ${{ env.AWS_DEFAULT_REGION }}- run: aws sts get-caller-identity- name: Setup Terraformuses: hashicorp/setup-terraform@v3with:terraform_version: ${{ env.TF_VERSION }}- name: Setup tfcmtenv:GH_TOKEN: ${{ github.token }}run: |gh release download -R suzuki-shunsuke/tfcmt v4.14.4 -p tfcmt_linux_amd64.tar.gztar -xzf tfcmt_linux_amd64.tar.gzsudo mv tfcmt /usr/local/bin/tfcmttfcmt --version- name: Terraform initrun: terraform initworking-directory: ${{ matrix.workdir }}- name: Terraform validaterun: terraform validateworking-directory: ${{ matrix.workdir }}- name: Terraform planrun: tfcmt -var "target:${{ matrix.workdir }}" plan -patch -- terraform plan -parallelism=30 -no-colorworking-directory: ${{ matrix.workdir }}env:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}.github/actions/generate_aws_credential_string/action.yml
.github/actions/generate_aws_credential_string/action.yml name: Generate aws credential stringinputs:profile:required: truetype: stringaws_role_arn:required: truetype: stringaws_role_external_id:required: truetype: stringoutputs:aws_credential:value: ${{ steps.gen_aws_cred_str.outputs.aws_credential }}runs:using: compositesteps:- name: Generate aws credential stringid: gen_aws_cred_strshell: bashrun: |CREDENTIAL=$(echo -e "[profile $PROFILE]credential_source = Environmentrole_arn = $AWS_ROLE_ARNexternal_id = $AWS_ROLE_EXTERNAL_ID")echo "aws_credential<<EOF" >> $GITHUB_OUTPUTecho -e "$CREDENTIAL" >> $GITHUB_OUTPUTecho "EOF" >> $GITHUB_OUTPUTenv:PROFILE: ${{ inputs.profile }}AWS_ROLE_ARN: ${{ inputs.aws_role_arn }}AWS_ROLE_EXTERNAL_ID: ${{ inputs.aws_role_external_id }}
使用感
自動的にterraform plan結果をコメント

formatされていないコードをpushした場合にはワンクリックで修正をコミットできる

効果
本対応策を導入すると、terraform planが成功するか否かを常に確認することができます。
結果として、Terraformファイルの作成が大幅に効率化されます。
対応策:git pushを契機に自動的にECSタスクを起動してコマンドを実行する
コンテナのビルドコンテキストが格納されているenvironments/dev/docker配下に変更をgit pushすると、自動的にdocker build・docker pushして、そのイメージを用いてECSタスクを起動します。
拡張性やセキュリティを考慮して、GitHub ActionsからStepFunctionsを経由してECSタスクを起動する構成としました。
StepFunctionsを経由することで、承認や通知の処理を簡単に追加できます。また、GitHub Actionsに許可する権限はStepFunctionsの呼び出し権限のみとシンプルになります。
構成図

対応策導入後のシーケンス
展開
sequenceDiagram
actor 作業者
participant GitHub as GitHub
Repository
participant db_migrate as db_migrate
(GHA)
participant StepFunctions
participant ECS
participant ECR
participant Servers
%% コンテナイメージ作成
rect rgba(224,242,254,0.3)
note over 作業者,ECR: コンテナイメージ作成
作業者 ->> 作業者: Dockerfileや
build contextを作成
作業者 ->> GitHub: git push
GitHub ->> db_migrate: Push作成イベント通知
db_migrate ->> ECR: docker login
db_migrate ->> db_migrate: docker build
db_migrate ->> ECR: docker push
end
%% ECSタスクからのコマンド実行
rect rgba(224,242,254,0.3)
note over db_migrate,Servers: ECSタスクからのコマンド実行
db_migrate ->> StepFunctions: ECSタスク起動用
ステートマシン実行
StepFunctions ->> ECS: ECSタスクの最新リビジョン取得
StepFunctions ->> ECS: ECSタスク実行
ECS ->> ECR: docker pull
ECS ->> ECS: コンテナ実行
ECS ->> Servers: 各種コマンド実行
Note left of ECS: git pushを契機に自動的にECSタスクを
起動してコマンドを実行
end
導入方法
1. OIDC providerを作成する@AWSアカウント
対応策:PRを契機に自動的にterraform planを実行するで作成済みのものを使用します。
2. IAMロールを作成する@AWSアカウント
- GitHub ActionsにてAssumeRoleするIAMロール(DB_MGR_ROLE)を作成する
-
今回は以下のように設定しました。
※ 各種ポリシーの内容は必要に応じて絞り込みや追加を行う必要があります。
信頼ポリシー
ECSタスクに関連づけるIAMロールの信頼ポリシー {"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"AWS": "${AWS_ROLEのARN}"},"Action": "sts:AssumeRole","Condition": {"StringEquals": {"sts:ExternalId": "${外部ID}"}}}]}許可ポリシー
ECSタスクに関連づけるIAMロールの許可ポリシー {"Version": "2012-10-17","Statement": [{"Sid": "PublishEcsLogs","Effect": "Allow","Action": ["logs:CreateLogStream","logs:CreateLogGroup","logs:PutLogEvents"],"Resource": ["arn:aws:logs:ap-northeast-1:${AWSAccountId}:log-group:/ecs/${TaskDefinitionName}:*"]},{"Sid": "ExecuteStepFunctions","Effect": "Allow","Action": ["states:ListStateMachines","states:DescribeStateMachine","states:StartExecution","states:DescribeExecution"],"Resource": "*"},{"Sid": "EcrPush","Effect": "Allow","Action": ["ecr:CompleteLayerUpload","ecr:UploadLayerPart","ecr:InitiateLayerUpload","ecr:BatchCheckLayerAvailability","ecr:PutImage","ecr:BatchGetImage"],"Resource": "arn:aws:ecr:ap-northeast-1:${AWSAccountId}:repository/${RepositoryName}"},{"Sid": "EcrLogin","Effect": "Allow","Action": "ecr:GetAuthorizationToken","Resource": "*"}]}
-
3. 作成したIAMロールの情報をGitHubリポジトリーのシークレットに登録する@GitHub Repository
- 各種IAMロールのARNと外部IDについて、以下のGitHubリポジトリーのシークレットに設定する
- DB_MGR_ROLE_ARN:AWS_MGR_ROLEのARN
- DB_MGR_ROLE_EXTERNAL_ID:AWS_MGR_ROLEの外部ID
4. ECSタスクを起動するVPCを作成する@AWSアカウント
- ECSタスクを起動するVPC・サブネットを作成する
- 前提として、VPCやサブネット、Aurora等のデータベースサーバーが存在している必要があります。
- ECSタスクで使用するコンテナレジストリにアクセスするための経路が必要になります。
- ECSタスクに関連付けるセキュリティグループを作成する
- ECSタスクからコマンドを実行する対象のデータベースサーバーにアクセスできるようにする必要があります。
5. ECS関連リソースを作成する@AWSアカウント
-
ECRリポジトリーを作成する
-
今回はDBマイグレーションを想定しているので、
dev/db-migrateというリポジトリーを作成しました。 -
latest運用を想定しているため、ミュータブルとしています。
- latest運用とは、コンテナイメージを変更する際常に
latestタグを付与することで、ECSタスク定義を更新せずとも常に最新のコンテナイメージを使用する運用を指しています。

- latest運用とは、コンテナイメージを変更する際常に
-
-
ECSクラスターを作成する
- 今回は、ECSクラスターのインフラとしてFargateを使用することとしました。

-
ECSタスクに関連づけるIAMロールを作成する
信頼ポリシー
ECSタスクに関連づけるIAMロールの信頼ポリシー {"Version": "2012-10-17","Statement": [{"Sid": "","Effect": "Allow","Principal": {"Service": "ecs-tasks.amazonaws.com"},"Action": "sts:AssumeRole"}]}許可ポリシー
AmazonEC2ContainerRegistryReadOnlyAmazonECSTaskExecutionRolePolicyRDSのDBユーザーのパスワードをシークレットマネージャで管理する場合には許可ポリシーを追加
シークレットマネージャアクセス用ポリシー {"Version": "2012-10-17","Statement": [{"Effect": "Allow","Action": ["secretsmanager:GetSecretValue"],"Resource": "*" # 必要に応じて制限する}]} -
ECSタスクを作成する
- ECRリポジトリー
dev/db-migrateのlatestタグのイメージを実行するECSタスクを作成します。

- ECRリポジトリー
6. ステートマシンを作成する@AWSアカウント
-
ステートマシンに関連づけるIAMロールを作成する
信頼ポリシー
ステートマシンに関連づけるIAMロールの信頼ポリシー {"Version": "2012-10-17","Statement": [{"Sid": "","Effect": "Allow","Principal": {"Service": "states.amazonaws.com"},"Action": "sts:AssumeRole"}]}許可ポリシー
ステートマシンに関連づけるIAMロールの許可ポリシー {"Version": "2012-10-17","Statement": [{"Effect": "Allow","Action": ["ecs:RunTask","ecs:StopTask","ecs:DescribeTasks","ecs:DescribeTaskDefinition"],"Resource": "*"},{"Effect": "Allow","Action": ["events:PutTargets","events:PutRule","events:DescribeRule"],"Resource": ["arn:aws:events:ap-northeast-1:${AWSAccountId}:rule/StepFunctionsGetEventsForECSTaskRule"]},{"Effect": "Allow","Action": ["iam:PassRole"],"Resource": ["arn:aws:iam::${AWSAccountId}:role/${ステートマシンに関連づけるIAMロール名}"]}]} -
ステートマシンを作成する
ステートマシン定義
ステートマシン定義 {"Comment": "A description of my state machine","StartAt": "DescribeTaskDefinition","States": {"DescribeTaskDefinition": {"Type": "Task","Arguments": {"TaskDefinition": "arn:aws:ecs:ap-northeast-1:${AWSAccountId}:task-definition/${ECSタスク名}"},"Resource": "arn:aws:states:::aws-sdk:ecs:describeTaskDefinition","Next": "ECS RunTask","Assign": {"ecsTaskDefArn": "{% $states.result.TaskDefinition.TaskDefinitionArn %}"}},"ECS RunTask": {"Type": "Task","Resource": "arn:aws:states:::ecs:runTask.sync","End": true,"Arguments": {"LaunchType": "FARGATE","Cluster": "arn:aws:ecs:ap-northeast-1:${AWSAccountId}:cluster/${ECSクラスター名}","TaskDefinition": "{% $ecsTaskDefArn %}","NetworkConfiguration": {"AwsvpcConfiguration": {"AssignPublicIp": "ENABLED", # コンテナリポジトリにアクセスするために付与しているが、NAT GatewayやVPC Endpointでアクセスできる場合には不要"SecurityGroups": ["${ECSタスクに関連付けるSGのID}"],"Subnets": ["${ECSタスクを配置するサブネットのID}"]}}}}},"QueryLanguage": "JSONata"}
7. DBマイグレーション用AWSリソースの情報をGitHubリポジトリーのシークレットに登録する@GitHub Repository
- ECSとStepFunctions関連の情報について、以下のGitHubリポジトリーのシークレットに設定する
- DB_MGR_ECR_IMAGE_TAG:latest
- DB_MGR_ECR_REGISTRY:ECRレジストリURL (
${AWSAccountId}.dkr.ecr.${Region}.amazonaws.com) - DB_MGR_ECR_REPOSITORY:ECRリポジトリー名
- DB_MGR_SFN_ARN:ステートマシンのARN
8. StepFunctionsを実行するワークフローを作成する@GitHub Actions
-
以下を参考に、コンテナイメージの準備とStepFunction実行自動化を行います。
-
今回は、以下のように設定しました。
.github/workflows/db_migrate.yml
.github/workflows/db_migrate.yml name: DB migrationon:push:branches:- mainpath:- 'environments/dev/docker/**'env:AWS_DEFAULT_REGION: ap-northeast-1permissions:id-token: writecontents: readjobs:tf_plan:name: DB Migrationruns-on: ubuntu-lateststrategy:matrix:build_context:- ./environments/dev/dockersteps:- name: Git checkoutuses: actions/checkout@v4with:ref: ${{ github.event.pull_request.head.sha }}- name: Get aws credentialuses: aws-actions/configure-aws-credentials@v4with:role-to-assume: ${{ secrets.AWS_ROLE_ARN }}role-session-name: iar-gha-con-${{ github.run_id }}aws-region: ${{ env.AWS_DEFAULT_REGION }}- run: aws sts get-caller-identity- name: Switch to db-migrate roleuses: aws-actions/configure-aws-credentials@v4with:role-to-assume: ${{ secrets.DB_MGR_ROLE_ARN }}role-external-id: ${{ secrets.DB_MGR_ROLE_EXTERNAL_ID }}role-session-name: iar-gha-mgr-${{ github.run_id }}role-chaining: trueaws-region: ${{ env.AWS_DEFAULT_REGION }}role-skip-session-tagging: true- run: aws sts get-caller-identity- name: Set up Docker Buildxuses: docker/setup-buildx-action@v3with:install: true- name: Login to Amazon ECRuses: aws-actions/amazon-ecr-login@v2- name: Build and push docker iamgeuses: docker/build-push-action@v2with:context: ${{ matrix.build_context }}push: trueno-cache: truetags: ${{ secrets.DB_MGR_ECR_REGISTRY }}/${{ secrets.DB_MGR_ECR_REPOSITORY }}:${{ secrets.DB_MGR_ECR_IMAGE_TAG }}- name: Call aws stepfunctionsuses: ./.github/actions/call_aws_step_functionswith:aws_sfn_arn: ${{ secrets.DB_MGR_SFN_ARN }}.github/actions/call_aws_step_functions/action.yml
.github/actions/call_aws_step_functions/action.yml name: Build and push docker imageinputs:aws_sfn_arn:required: truetype: stringruns:using: compositesteps:- name: Execute StepFunctionsshell: bashrun: |SFN_EXEC_ARN=$(aws stepfunctions start-execution \--state-machine-arn "$STATE_MACHINE" \--no-cli-pager \| jq -r '.executionArn')if [ -z "$SFN_EXEC_ARN" ]; thenecho "Error: 実行 ARN の取得に失敗しました。"exit 1fiecho "Step Functions 実行開始: $SFN_EXEC_ARN"while true; doSTATUS=$(aws stepfunctions describe-execution \--execution-arn "$SFN_EXEC_ARN" --no-cli-pager \| jq -r '.status')if [ "$STATUS" != "RUNNING" ]; thenbreakfisleep 5doneecho "実行完了。最終ステータス: $STATUS"env:STATE_MACHINE: ${{ inputs.aws_sfn_arn }}
※ dockerコンテナのbuild cacheについて
今回は運用時に必要なワンショットのコマンド実行が目的の、ビルドに長時間を要さないコンテナを想定しています。
そこで、build cacheの有無でビルド結果が変わることを避けるため、build cacheを使用しない設定としました。
ECRを用いたremote cacheには料金がかかりますし、GitHubのキャッシュ機能 ⧉は期限を過ぎると削除される仕様です。
ビルドに時間がかかりコストとメリットのバランスが取れる場合には、何らかの方法でbuild cacheを導入することが推奨されます。
使用感
GitHub Actionsワークフロー実行ログ

StepFunctions実行ログ

JavascriptのORMツールであるPrismaを用いて、ECSタスクからAuroraの情報を取得する例

効果
本対応策を導入すると、コンテナで実行するよう事前に記述したコマンドのみが実行されます。
結果として、踏み台サーバーの維持コストやオペレーションミスのリスクを軽減することができます。
おわりに
この記事では、GitHub Actionsを用いたCI/CDによるインフラ構築・運用の省力化についてご紹介しました。
PR-Agentの導入によって、AIによるレビューを手軽に導入することができます。
PRのテンプレートを充実させることやコーディング規約をチームに浸透させることも重要ですが、別視点でのアプローチとして有効であると感じています。
terraform planの自動実行は非常に便利で、手動実行にはもう戻れないと感じています。
「いざterraform applyを実行したところでエラーが発覚し、手戻りが発生してしまった」といった事態が起きにくくなります。
ECSタスク起動によるコマンドの実行については、セキュリティ強化やオペレーションミスへの有効な対策になります。
「作業者の判断で手順書外のオペレーションを実施し、システム障害を招いてしまった」といった事態が非常に起きにくくなります。
CI/CDの仕組みが整っていないケースは少ないかもしれませんが、この記事が快適な開発体験の一助となれば幸いです。