この記事では、GitHub Actionsを使ってAmazon ECSへの自動デプロイを行う手順を解説する。
GitHub ActionsからAWSへの安全な接続方法、ECRへのコンテナイメージのプッシュ、ECSサービスの更新までの一連のプロセスを具体的に示し、効率的なCI/CDパイプラインの構築方法を紹介する。デプロイの自動化を考えている方の参考になれば幸いだ。
(2024年6月29日に動作確認)
前提条件
- ECSクラスター、サービス、タスク定義を作成済みであること
- ECRリポジトリURIを取得済みであること
手順
GitHub Actionsでは大きく分けて以下のステップでワークフローを記述する。
- OpenId ConnectによるAWS連携
- AWSの一時クリデンシャルを取得する
- AWS ECRへログインする
- コンテナビルドを実行し、コンテナイメージをECRにPushする
- コンテナデプロイを実行し、ECSのタスク定義、サービスを更新する
1. OpenId ConnectによるAWS連携
まずは、OpenId ConnectによるAWS連携を行う必要がある。このフローを飛ばして先に進むことはできない。
OpenID Connectを使うと、長期的なアクセスキーやシークレットキーを使用する必要がなくなる。つまり、短期的なトークンを使用することで、セキュリティリスクを低減できるのだ。
GitHub ActionsとAWSを連携させるための設定
GitHubのOIDCトークンとAWSの一時クリデンシャルを交換するために必要なステップだ。
AWS CloudShellを使用して、以下のaws iam create-open-id-connect-provider
コマンドを実行する。
$ aws iam create-open-id-connect-provider \
--url <https://token.actions.githubusercontent.com> \
--client-id-list sts.amazonaws.com \
--thumbprint-list 1234567890123456789012345678901234567890
このコマンドを実行することで、AWSはGitHub ActionsからのOIDCトークンを信頼し、そのトークンを使用してAWSリソースにアクセスすることができるようになる。これにより、GitHub ActionsのワークフローでAWSのリソース(例えばECRやECS)にアクセスするための一時的なクレデンシャルを取得できるようになる。
- –url
--url
には、Identity ProviderのURLを指定する。Identity Providerは、GitHub ActionsがOIDCトークンを発行するエンドポイントのURLである。URLはhttps://token.actions.githubusercontent.com
が該当する。公式ドキュメントに記載がある(https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-awsを参照)。
- –client-id-list
--client-id-list
には、OIDCトークンを利用するシステムの識別子を設定する。この識別子は、AWSが受け入れるクライアントIDを指定するもので、GitHub Actionsの場合はsts.amazonaws.com
を設定する。これにより、AWSがGitHub Actionsからのリクエストを正しく認識して処理できるようになる。
- –thumbprint-list
- –thumbprint-listには、OIDCプロバイダーのSSL証明書のフィンガープリント(拇印)を設定する。フィンガープリントは証明書のハッシュ値であり、AWSがOIDCプロバイダーの証明書の正当性を確認するために使用される。しかし、GitHubのOIDCプロバイダーの場合、検証はAWS側で行われる仕様になっているため40文字のダミー値を指定している。
Assume Roleポリシーを定義する
Assume Roleポリシーは、アクセス元(IAMの利用者は誰か)を定義するために使用する。この定義により、GitHub ActionsからAWSにアクセスしようとしているユーザーが、本当に信頼できるユーザーなのかを明確にできる。まずは、必要な変数を定義する。
# Assume Roleポリシーに必要なデータ
export GITHUB_REPOSITORY="${OWNER}/${REPOSITORY_NAME}"
export PROVIDER_URL=token.actions.githubusercontent.com
export AWS_ID=$(aws sts get-caller-identity --query Account --output text)
export ROLE_NAME=deploy-to-ecs-github-actions
次に、Assume Roleポリシーを定義したJSONファイルを作成する。Assume Roleポリシーを定義することで、特定のサービスやユーザー(ここではGitHub Actions)が、特定の条件下でAWSのIAMロールを一時的に引き受ける(つまり、そのロールの権限を一時的に借りる)ことができるようになる。
# assume_role_policy.jsonの作成
$ cat <<EOF > assume_role_policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRoleWithWebIdentity",
"Principal": {
"Federated": "arn:aws:iam::${AWS_ID}:oidc-provider/${PROVIDER_URL}"
},
"Condition": {
"StringLike": {
"${PROVIDER_URL}:sub": "repo:${GITHUB_REPOSITORY}:*"
}
}
}
]
}
EOF
次に、上記で定義したJSONファイルを使用してIAMロールを作成する。
# IAMロールの作成
$ aws iam create-role \
--role-name $ROLE_NAME \
--assume-role-policy-document file://assume_role_policy.json
新しく作成されたIAMロールは、指定されたAssume Roleポリシーに基づいて、GitHub Actionsがロールを引き受けることを許可する。このロールを使用して、GitHub Actionsは一時的なAWS認証情報を取得し、AWSリソースにアクセスすることができるようになる。
IAMポリシーのアタッチ
あと少しで完了する。
ここでは、ECRにDockerイメージをデプロイするためにAmazonEC2ContainerRegistryPowerUser
というポリシーをアタッチしている。
# IAMポリシーのアタッチ
$ aws iam attach-role-policy \
--role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
AmazonEC2ContainerRegistryPowerUserポリシーをIAMロールにアタッチすることで、GitHub ActionsのワークフローからECRに対する操作が可能になる。これにより、CI/CDパイプラインを通じて自動的にDockerイメージのビルド、プッシュ、デプロイが行える。
これにて、OpenId ConnectによるAWS連携が終了だ。ここから、ワークフローを実装していく。
ワークフローの全体像
# デプロイワークフロー
# 1. ソースコードのチェックアウト
# 2. AWSの一時クリデンシャルを取得
# 3. コンテナビルドアクションを実行し、コンテナイメージをPush
# 4. コンテナデプロイアクションを実行し、コンテナを入れ替え
name: Deploy
on:
pull_request:
branches:
- main
types:
- closed
env:
ROLE_ARN: arn:aws:iam::${{ secrets.AWS_ID }}:role/${{ secrets.ROLE_NAME }}
SESSION_NAME: gh-oidc-${{ github.run_id }}-${{ github.run_attempt }}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
# ソースコードのチェックアウト
- uses: actions/checkout@v4
# 2. AWSの一時クリデンシャルを取得する
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ROLE_ARN }}
role-session-name: ${{ env.SESSION_NAME }}
aws-region: ap-northeast-1
# 3. AWS ECRへログインする
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
# 4. コンテナビルドアクションを実行し、ECRにPushする
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
# ビルド
docker build --platform linux/amd64 -t ${{ vars.ECR_REPOSITORY_URI }}:$IMAGE_TAG .
# ECRにPUSH
docker push ${{ vars.ECR_REPOSITORY_URI }}:$IMAGE_TAG
# 5. コンテナデプロイアクションを実行し、ECSのタスク定義、サービスを更新する
- name: Deploy to ECS
uses: ./.github/actions/container-deploy/
with:
ecs-cluster: ${{ vars.ECS_CLUSTER_NAME }}
ecs-service: ${{ vars.ECS_SERVICE_NAME }}
task-definition: ${{ vars.TASK_DEFINITION_NAME }}
container-name: ${{ vars.CONTAINER_NAME }}
container-image: ${{ steps.build-image.outputs.image }}
※環境変数
以下の環境変数は、GitHub Variablesに保存する必要があるが、
ECS_CLUSTER_NAME
、ECS_SERVICE_NAME
、TASK_DEFINITION_NAME
、CONTAINER_NAME
それぞれ、次のコマンドで取得できる。取得したらVariablesとしてセットする。
# ECSクラスター名を取得
aws ecs list-clusters --output text \\
--query "clusterArns[?contains(@, '${APP_NAME}-${ENV_NAME}')]" \\
| cut -d/ -f2
# ECSサービス名を取得
aws ecs list-services --cluster "${ECSクラスター名}" --output text \\
--query "serviceArns[?contains(@, '${APP_NAME}-${ENV_NAME}')]" \\
| cut -d/ -f3
# タスク定義名を取得
aws ecs list-task-definitions --status ACTIVE --sort DESC --output text \\
--query "taskDefinitionArns[?contains(@, '${APP_NAME}-${ENV_NAME}')]" \\
| cut -d/ -f2 | cut -d: -f1
# ECRリポジトリ名を取得
aws ecr describe-repositories --output text \\
--query "repositories[?contains(repositoryUri, '$APP_NAME')].repositoryUri"
# コンテナ名を取得
# 以下のコマンドを実行し taskDefinition > containerDefinitions > nameを参照
aws ecs describe-task-definition --task-definition ${取得したタスク定義名}
secrets.AWS_ID
= AWSアカウントID secrets.ROLE_NAME
については、上記で作成したロール名(deploy-to-ecs-github-actions
)を環境変数としてGitHub Secretsにセットする。
2. AWSの一時クリデンシャルを取得
以下のワークフローで、AWSの一時クリデンシャルの取得を実行している。
env:
ROLE_ARN: arn:aws:iam::${{ secrets.AWS_ID }}:role/${{ secrets.ROLE_NAME }}
SESSION_NAME: gh-oidc-${{ github.run_id }}-${{ github.run_attempt }}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
# ソースコードのチェックアウト
- uses: actions/checkout@v4
# 1. AWSの一時クリデンシャルを取得する
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ROLE_ARN }}
role-session-name: ${{ env.SESSION_NAME }}
aws-region: ap-northeast-1
3. AWS ECRへログインする
OpenId ConnectによるAWS連携が正常に行えている場合、以下のスクリプトでECRにログインできる。
# 3. AWS ECRへログインする
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
4. コンテナビルドを実行し、コンテナイメージをECRにPushする
# 4. コンテナビルドアクションを実行し、ECRにPushする
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
# ビルド
docker build --platform linux/amd64 -t ${{ vars.ECR_REPOSITORY_URI }}:$IMAGE_TAG .
# ECRにPUSH
docker push ${{ vars.ECR_REPOSITORY_URI }}:$IMAGE_TAG
5. コンテナデプロイを実行し、ECSのタスク定義、サービスを更新する
こちらは、モジュール化したため別ファイルで定義している。
./.github/actions/container-deploy/action.yml
# コンテナデプロイアクション
# 1. 現在のタスク定義を取得
# 2. タスク定義のimage部分を新しいコンテナイメージに書き換え
# 3. 新しいタスク定義でECSサービスを更新し、コンテナを入れ替え
name: Container Deploy
description: ECSサービスを更新し、コンテナをデプロイする。
inputs:
ecs-cluster:
required: true
description: ECSクラスター
ecs-service:
required: true
description: ECSサービス
task-definition:
required: true
description: タスク定義
container-name:
required: true
description: コンテナ名
container-image:
required: true
description: コンテナイメージ
runs:
using: composite
steps:
# 1. 現在のタスク定義を取得
- run: | # 次のステップで使用するため、取得したタスク定義をファイルへ保存する
aws ecs describe-task-definition --task-definition "${TASK_DEFINITION}" \\
--query taskDefinition --output json > "${RUNNER_TEMP}/task-def.json"
env:
TASK_DEFINITION: ${{ inputs.task-definition }}
shell: bash
# 2. タスク定義のimage部分を新しいコンテナイメージに書き換え
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ runner.temp }}/task-def.json
container-name: ${{ inputs.container-name }}
image: ${{ inputs.container-image }}
# 3. ECSサービスの更新
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
cluster: ${{ inputs.ecs-cluster }}
service: ${{ inputs.ecs-service }}
task-definition: ${{ steps.task-def.outputs.task-definition }}
まとめ
GitHub Actionsを使ったECSへの自動デプロイは、手動作業を減らし、デプロイの効率化とセキュリティの向上を実現する。OpenID Connectを利用してAWSと連携し、ECRにコンテナイメージをプッシュ、ECSサービスを更新する流れを通して、CI/CDパイプラインの自動化をシンプルに構築できることがわかる。
コメント