この記事は、リレーブログ企画「CI/CD」の記事です。
はじめに
おはようございます。IWSです。
今回は「CI/CDリレーブログ」ということで私のチームで使われている GitHub Actions を使った開発環境へのCDを紹介しようと思います。
イメージ
この構成のWebアプリに対してCDする GitHub Actions WF を作成します。ECR にあるイメージを使ってECS タスクが動いているよくある構成ですね。

この構成に対して、developブランチにmergeされたときやGitHub Actionsのページから好きなブランチを指定して開発環境にデプロイできるようになるのがゴールです。

IAM Role の用意
WFを作成する前に、まずはAWSのリソースを操作するための権限周りの準備をします。
IAM Userでアクセスキーを取得して使う方法もありますが、IAM Role を使えば一時的なクレデンシャルを発行することができセキュリティ的にも管理のしやすさ的にも楽になるのでこちらがおすすめです。
ID プロバイダの作成
IAM の IDプロバイダ のページから作成していきます。
設定内容については GitHub Docs にも書かれていますが以下のように設定していけばOKです。
- プロバイダのタイプ
OpenID Connect
- プロバイダのURL
https://token.actions.githubusercontent.com
- 対象者
sts.amazonaws.com

IAM Role と Policy の作成
Policyを準備します。
操作したいリソースに合わせてActionの内容は変更してください。 今回はECSの更新やECRへのPushなどをするのでそのあたりを追加しています。
"sts:AssumeRoleWithWebIdentity"
がないとWFが認証情報を受け取れないのでそこだけ注意。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ecs:UpdateService", "ecs:DescribeServices" ], "Resource": <ECS Sercice ARN> }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "elasticloadbalancing:RegisterTargets", "ecs:RegisterTaskDefinition", "elasticloadbalancing:DescribeTargetHealth", "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DeregisterTargets", "ecs:TagResource", "ecs:UntagResource", "ecs:DescribeTaskDefinition", "ecr:GetAuthorizationToken", "ecr:CompleteLayerUpload", "ecr:UploadLayerPart", "ecr:InitiateLayerUpload", "ecr:BatchCheckLayerAvailability", "ecr:PutImage", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "sts:AssumeRoleWithWebIdentity" ], "Resource": "*" } ] } |
Roleも作っていきましょう。
- 信頼されたエンティティタイプ
- ウェブアイデンティティ
- アイデンティティプロバイダー
token.actions.githubusercontent.com
- Audience
sts.amazonaws.com
- GitHub 組織、GitHub リポジトリ、GitHub ブランチ
- 自分のものを設定
- ここで設定したリポジトリにあるWFにのみ認証情報を渡せるようになります。

次へ進んで先ほど作成した Policy を設定すればRoleの完成です。
ウェブアイデンティティで設定した内容は信頼ポリシーに反映されています。
Condition の StringLike の部分で Policy を渡してもいい相手を設定しています。↓の場合は test-repository
内の WF に対しては渡していいよ。という感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRoleWithWebIdentity", "Principal": { "Federated": "arn:aws:iam::xxxxxxxxxxxxxxx:oidc-provider/token.actions.githubusercontent.com" }, "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": [ "sts.amazonaws.com" ] }, "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:test-user/test-repository:*" ] } } } ] } |
間違っても "repo:*"
みたいな設定はしないようにしましょう!どのリポジトリに対しても権限を渡してしまうゆるゆる設定になります!
1 2 3 4 5 6 |
// すべてのユーザー、リポジトリに対しても権限を渡してしまう設定! "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:*" ] } |
ここまででIAM Roleを使ってWFがリソースの操作をできるようにする準備が終わりました!
あとはWF側でこのRoleを受け取るだけですが
1 2 3 4 5 |
- name: Configure AWS credentials from IAM Role uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} |
公式のActionがあるのでこれだけで受け取ることができます🥳
WF の作成
ここからはWFを作成します。
以下の通りに処理を行っていきます。
- IAM Role の取得など初期準備
- DockerfileのBuild, ECRへのPush
- ECSの更新
- Slackへの通知
コード全体
先にコード全体を貼っておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
name: deploy ECS development on: workflow_dispatch: inputs: no-cache: description: "Build docker images with no cache" default: false required: false type: boolean push: branches: - develop env: AWS_REGION: "ap-northeast-1" ECR_REPOSITORY: "test-repository" ECS_SERVICE: "test-ecs-service" ECS_CLUSTER: "test-cluster" DESIRE_COUNT: "1" DOCKERFILE: "Dockerfile" SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} jobs: deploy: name: Deploy runs-on: ubuntu-latest timeout-minutes: 15 environment: development # GitHubのOIDCトークンエンドポイントとやり取りするために必要 permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Configure AWS credentials from IAM Role uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 # image につけるタグを作成 # GITHUB_SHA の先頭7文字をタグにしています - name: Prepare IMAGE_TAG run: | IMAGE_TAG=$(echo ${GITHUB_SHA} | cut -c 1-7) echo "IMAGE_TAG=$(echo $IMAGE_TAG)" >> $GITHUB_ENV echo "build IMAGE_TAG: $IMAGE_TAG" - name: No Cache Option Check run: | # 指定した場合とpushでトリガーされた場合はキャッシュを無効にする if [ ${{ inputs.no-cache }} -o ${{ github.event_name }} -eq 'push' ]; then echo "NO_CACHE=true" >> $GITHUB_ENV else echo "NO_CACHE=false" >> $GITHUB_ENV fi # image を build し latest と github_sha 7桁をタグにして ECR に push - uses: docker/build-push-action@v6 id: build-image env: ECR_PATH: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }} with: file: ${{ env.DOCKERFILE }} push: true # ECRにpushするか tags: | ${{ env.ECR_PATH }}:${{ env.IMAGE_TAG }} ${{ env.ECR_PATH }}:latest cache-from: type=gha # キャッシュのソース, GitHub Actionsにあるのを使用 cache-to: type=gha,mode=max # キャッシュを GitHub Actionsに保管する。 max:中間ステップのすべてのレイヤーをエクスポート no-cache: ${{ env.NO_CACHE }} # キャッシュを使用するか provenance: false # Image indexを生成しない # --force-new-deployment で強制的にデプロイ - name: ECS Update id: update-ecs run: | # ECS更新 aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --desired-count $DESIRE_COUNT --force-new-deployment # Slack通知 # 成功 - name: Slack Notification on Success if: ${{ success() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Success SLACK_COLOR: good # 失敗 - name: Slack Notification on Failure if: ${{ failure() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Failure SLACK_COLOR: danger |
トリガー、初期準備
トリガーにはdevelopブランチにpushされた時と手動実行を入れています。inputsについてはのちほど……。
envにはECRやECS指定のための値やIAM RoleのARNを、steps部分については初期準備として actions/checkout
やECRにログインするための aws-actions/amazon-ecr-login
などをしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
name: deploy ECS development on: workflow_dispatch: inputs: no-cache: description: "Build docker images with no cache" default: false required: false type: boolean push: branches: - develop env: # 更新する ECS の Cluster,Service などの名前を指定 AWS_REGION: "ap-northeast-1" ECR_REPOSITORY: "test-repository" ECS_SERVICE: "test-ecs-service" ECS_CLUSTER: "test-cluster" DESIRE_COUNT: "1" # 開発環境のためタスク起動数は "1" DOCKERFILE: "Dockerfile" # build する Dockerfile を指定 SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} # 上記で作った IAM Role のARN jobs: deploy: name: Deploy runs-on: ubuntu-latest timeout-minutes: 15 environment: development permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # 権限を受け取る - name: Configure AWS credentials from IAM Role uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 |
imageのBuildとECRへのPush
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
jobs: deploy: steps: # 〜省略〜 # image につけるタグを作成 # github sha の先頭7文字をタグにしています - name: Prepare IMAGE_TAG run: | IMAGE_TAG=$(echo ${GITHUB_SHA} | cut -c 1-7) echo "IMAGE_TAG=$(echo $IMAGE_TAG)" >> $GITHUB_ENV echo "build IMAGE_TAG: $IMAGE_TAG" - name: No Cache Option Check run: | # 指定した場合とpushでトリガーされた場合はキャッシュを無効にする if [ ${{ inputs.no-cache }} -o ${{ github.event_name }} -eq 'push' ]; then echo "NO_CACHE=true" >> $GITHUB_ENV else echo "NO_CACHE=false" >> $GITHUB_ENV fi # image を build し latest と github_sha 7桁をタグにして ECR に push - uses: docker/build-push-action@v6 id: build-image env: ECR_PATH: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }} with: file: ${{ env.DOCKERFILE }} push: true # ECRにpushするか tags: | ${{ env.ECR_PATH }}:${{ env.IMAGE_TAG }} ${{ env.ECR_PATH }}:latest cache-from: type=gha # キャッシュのソース, Github Actionsにあるのを使用 cache-to: type=gha,mode=max # キャッシュを Github Actionsに保管する。 max:中間ステップのすべてのレイヤーをエクスポート no-cache: ${{ env.NO_CACHE }} # キャッシュを使用するか provenance: false # Image indexを生成しない |
BuildとPushは docker/build-push-action
を使ってやっています。このactionを使うだけでDockerfileのBuild、Push、キャッシュ保存までやってくれるためとりあえずこれを使っておけばいいと思います。
開発環境ですと確認のために何回もデプロイする……ということもあるのでキャッシュを使ってBuildにかかる時間を減らせるのはかなり助かります。そして面倒くさいキャッシュ設定がcache-from
, cache-to
だけで済むのもありがたいところですね。
キャッシュの使用は no-cache
に bool で選べます。なので手動実行の際は inputs でチェックボックスを用意してチェックされたら使用しない。というふうにしています。
1 2 3 4 5 6 7 8 |
on: workflow_dispatch: inputs: no-cache: description: "Build docker images with no cache" default: false required: false type: boolean |

ECS の更新
開発環境用のWFなのでECSの更新も行っています。難しいことはしておらず AWS CLI で aws ecs update-service --force-new-deployment
をして強制更新をかけているだけです。
使用するイメージについてはタスク定義で latest を使うようにしているため、更新をかけるだけで切り替わるようになっています。
1 2 3 4 5 6 |
# --force-new-deployment で強制的にデプロイ - name: ECS Update id: update-ecs run: | # ECS更新 aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --desired-count $DESIRE_COUNT --force-new-deployment |
Slack通知
最後に action-slack-notify というactionを使ってWFが成功したかどうかを通知しています。GitHub Actionsは if: ${{ success() }}
とするだけで 成功時/失敗時 の分岐ができるのが楽で好きです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
jobs: deploy: steps: # 〜省略〜 # Slack通知 # 成功 - name: Slack Notification on Success if: ${{ success() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Success SLACK_COLOR: good # 失敗 - name: Slack Notification on Failure if: ${{ failure() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Failure SLACK_COLOR: danger |

ちなみにここは Slack の GitHub App を使っても同じことができるのでやらなくても大丈夫です。お好きなやり方で通知してみてください。
まとめ
GitHub ActionsでのCDについて書かせていただきました。
いままでは、いちいちAWSのコンソールから CodePipeline や CodeBuild などの設定を変更してから CodePipelineを実行、というふうにしていたのでこのWFが完成してからは格段に楽にデプロイできるようになりました。AWSにログインして設定をいじって実行なんて面倒くさいこと、いままでよくやっていたなと思います……
いまでも充分便利だなとは感じてはいますが、今度はGitHubのページを開くのが面倒くさいと思ってきたためSlackから発火できるようにできないかなと考えています。 もし完成したらこちらもまたブログにできたらと思っているので、その時はよろしくお願いします。
次回は、ブログ運営チームの投稿です。
おたのしみに!