GitHub Actions + Argo CDでプッシュ型のGitOpsを構築してみた
1. はじめに
CIとしてのGitHub ActionsとCDとしてのArgo CDを利用して、下図のようなプッシュ型のGitOpsを構築してみました。この記事では、特にGitHub Actionsワークフローに焦点を当てて、実装例を紹介していきます。

2. GitHub Actionsのワークフロー
今回構築したプッシュ型のGitOpsでは、以下の2点をGitHub Actionsが実行します。
- アプリケーションイメージのビルド&ECRリポジトリへのプッシュ
- マニフェストに記述されたイメージタグを書き換えて、コミット&プッシュ
上記を実現するのが以下のYAML定義であり、これをアプリケーションリポジトリ内のワークフローとして配置しています。
name: Build and Push Docker Images to ECR Repositories
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ap-northeast-1
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT }}:role/role-github
role-duration-seconds: 1800
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
docker build -t $ECR_REGISTRY/app-repo:$IMAGE_TAG -f ./build/app/Dockerfile.prod .
docker build -t $ECR_REGISTRY/web-repo:$IMAGE_TAG -f ./build/web/Dockerfile .
docker build -t $ECR_REGISTRY/migration-repo:$IMAGE_TAG -f ./build/migration/Dockerfile .
docker push $ECR_REGISTRY/web-repo:$IMAGE_TAG
docker push $ECR_REGISTRY/app-repo:$IMAGE_TAG
docker push $ECR_REGISTRY/migration-repo:$IMAGE_TAG
- name: Checkout config repository
uses: actions/checkout@v3
with:
repository: your_account/your_repository
ref: branch_name
token: ${{ secrets.PAT }}
path: your_repository
- name: Update image tag
id: update-image-tag
working-directory: your_repository
continue-on-error: true
run: |
yq -i '.front.imageTag = "'`printenv IMAGE_TAG`'"' ./main/values.yaml
git diff --name-only --exit-code
- name: Commit and push
working-directory: your_repository
if: steps.update-image-tag.outcome == 'failure'
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add .
git commit --author=. -m "Update image tag to "`printenv IMAGE_TAG`
git push
3. 解説
3-1. OIDCを利用したAWSへのアクセス[1]
#~ 省略 ~
permissions:
id-token: write
contents: read
#~ 省略 ~
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ap-northeast-1
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT }}:role/role-github
role-duration-seconds: 1800
GitHub ActionsからOIDCを利用してAWSへアクセスさせる方法は簡単で、Configure AWS Credentialsアクションでaws-access-key-idとweb-identity-token-fileを指定せずにrole-to-assumeを指定すれば、これが合図となります[2]。ですので、OIDC認証に成功したGitHubが引き受けるIAMロールを用意してrole-to-assumeに指定するだけで、GitHubからAWSへOIDCを利用したアクセスが可能となります。注意点としては、ワークフローのジョブ自体にid-token: writeとcontents: readの権限を与えることです。ワークフローのジョブ内でOIDC認証を利用してAWSへアクセスするにはこれらの権限が必要なので、前述した宣言を忘れずに追加しておきましょう。
ちなみに、GitHubが引き受けるIAMロール(role-github)は以下のように定義しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWSアカウントID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:sub": "repo:アカウント名/*",
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
}
}
]
}
また、role-githubにアタッチしているポリシーは以下の通りで、DockerイメージをECRリポジトリへプッシュするための権限のみを付与しています。[3]
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:CompleteLayerUpload",
"ecr:GetAuthorizationToken",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
],
"Resource": "*"
}
]
}
3-2. イメージのビルドとECRリポジトリへのプッシュ
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
docker build -t $ECR_REGISTRY/app-repo:$IMAGE_TAG -f ./build/app/Dockerfile.prod .
docker build -t $ECR_REGISTRY/web-repo:$IMAGE_TAG -f ./build/web/Dockerfile .
docker build -t $ECR_REGISTRY/migration-repo:$IMAGE_TAG -f ./build/migration/Dockerfile .
docker push $ECR_REGISTRY/web-repo:$IMAGE_TAG
docker push $ECR_REGISTRY/app-repo:$IMAGE_TAG
docker push $ECR_REGISTRY/migration-repo:$IMAGE_TAG
公式ドキュメントのExamples of Usageとほぼ同じ構成ですので、詳しくはそちらをご参照ください。
3-3. マニフェストリポジトリへのチェックアウト[4]
- name: Checkout config repository
uses: actions/checkout@v3
with:
repository: your_account/your_repository
ref: branch_name
token: ${{ secrets.PAT }}
path: your_repository
ここでは、Checkout V3アクションに対してrepositoryとrefパラメータを指定し、チェックアウトするk8sマニフェスト用のリポジトリとブランチを指定しています。このとき、tokenパラメータにPersonal Access Token (= PAT)を指定しておくことで、プライベートリポジトリへのアクセスが可能となります。そして、pathパラメータを指定することによって、チェックアウトしたリポジトリを指定したパスに配置しています。
3-4. マニフェストのイメージタグを書き換えてコミット&プッシュ[5]
- name: Update image tag
id: update-image-tag
working-directory: your_repository
continue-on-error: true
run: |
yq -i '.front.imageTag = "'`printenv IMAGE_TAG`'"' ./main/values.yaml
git diff --name-only --exit-code
- name: Commit and push
working-directory: your_repository
if: steps.update-image-tag.outcome == 'failure'
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add .
git commit --author=. -m "Update image tag to "`printenv IMAGE_TAG`
git push
まず、Update image tagステップでは、working-directory: your_repositoryを指定して、前のステップでチェックアウトしたk8sマニフェスト用リポジトリを作業ディレクトリに指定しています。次に、YAMLファイルを良い感じに編集してくれるyqコマンドを利用して、main/values.yamlの.front.imageTagの値を書き換えています。そして、git diffコマンドを実行することで、yqコマンドにより差分が生じているかどうかを確認しています。このとき、--exit-codeオプションを指定しているので、差分がある場合には終了ステータスが1となる一方で、差分がない場合には終了ステータスが0となり、差分の有無が判定可能となります。ただし、このままでは差分が生じたときの終了ステータスが1となってGitHubワークフロー自体がエラー判定となって停止してしまうので、continue-on-error: trueというワークフロー構文を宣言して、エラー判定となった場合でもワークフローを継続するように設定しています。ちなみに、git diffコマンドの--name-onlyオプションはなくても構わないのですが、yqコマンドにより差分が生じたファイル名をログ出力させるために、ここでは指定しています。
次に、Commit and pushステップはUpdate image tagステップの終了ステータスが1となって出力がfailureとなっている場合、つまり、yqコマンドによりファイル差分が発生している場合に実行されます。このステップも、引き続きk8sマニフェスト用リポジトリで作業していくので、working-directory: your_repositoryを指定しています。そして、通常のコミット&プッシュと同様のフローでコマンドを実行して処理を進めていきます。ここで、git config user.name github-actions[bot]やgit config user.email 41898282+github-actions[bot]@users.noreply.github.comを指定していますが、これはちょっとしたオシャレのためです。これらを指定することでコミットする主体をGitHub Actionsと明記した上で、アイコンまで付けてくれるようになります。こんな感じで。

4. まとめ
ここでは、GitHub Actions + Argo CDでプッシュ型のGitOpsを構築してみたので、GitHub Actionsにフォーカスして構築したワークフローを紹介しました。GitOpsを構築する中で、GitHub Actionsワークフロー全体を紹介する記事はあまり多くないように感じたので、記録として残しておこうと思った次第です。誰かの何かのお役に立てれば幸いです^^
-
詳しくはRequired IAM permissions for pushing an imageをご参照ください。 ↩︎
-
ワークフローを配置しているリポジトリとは異なるプライベートリポジトリへチェックアウトする方法は公式ドキュメントのCheckout multiple repos (private)が参考になります。 ↩︎
-
この部分を実装するにあたっては、変更があるときだけコミット [GitHub Actions]を参考にさせていただきました。 ↩︎
Discussion