⚙️

ArgoCD Image Updaterで実現する環境別デプロイ戦略

に公開

はじめに

はじめまして、システム基盤チームでSREをしている安藤と申します。普段はDB負荷の改善、開発者プラットフォーム改善、AIOpsなどに取り組んでいます。

今回はEKS上で動く基盤にArgoCD Image Updaterを導入し、実際の運用を通じて得られた知見をご紹介します。

ArgoCD Image Updaterとは

ArgoCD Image UpdaterはコンテナレジストリへのイメージPushを検知し、Kubernetesマニフェストのイメージタグを自動更新、もしくはDeploymentを自動再起動できるツールです。

導入背景

ここでは導入に至った直近のトラブル事例と開発環境の要件についてご紹介します。

1. 本番環境リリース時のヒューマンエラーの発生

QAテスト完了後、本番リリース用のPRにてv1.1.2とすべきイメージタグを誤ってv.1.1.2と記載したままマージしてしまい、EKSが該当イメージを取得できずデプロイに失敗、最終的にロールバックされる事態が発生しました。

2. 開発環境更新時の作業負荷

開発(develop)環境では、イメージに developタグを打ってイメージを上書きしてデプロイしてます。マニフェストを変更せずに反映できる+コミットログが増えない反面、ArgoCDコンソールでDeploymentを毎回手動で再起動する必要があり、開発者の負担となっていました。

環境別の運用戦略

ArgoCD Image Updaterはさまざまな設定が用意されているため、本番環境と開発環境(dev/stg)で異なる運用を採用することで、安全性と効率性のバランスをとっています。

1. 本番環境: 自動PR作成

本番環境ではイメージの自動適用は避け、自動PR作成という方式を採用しました。
ECR(コンテナレジストリ)に新しいイメージがPushされると、ArgoCD Image Updaterがそれを検知し、マニフェストリポジトリのvalues.yamlファイル内のタグを書き換えたブランチを作成。その後、ブランチの作成をトリガーにGitHub ActionsがPRを自動作成するというものです。

リリース作業のような承認フローをそのまま活かしつつ、タグの打ち間違いのようなヒューマンエラーを防止することができます。

2. 開発環境: 自動再起動

一方、開発やステージング環境ではスピードを重視し、ArgoCD APIによるDeploymentの自動再起動を利用しています。

ECR(コンテナレジストリ)に新しいイメージがPushされると、ArgoCD Image Updaterがそれを検知し、ArgoCDのAPIを用いて対象のDeploymentを内部的に更新・再起動します。

マニフェスト自体の更新は発生しないため、PRや承認が不要である環境に向いていると考えています。

実装と運用の詳細

これまでに述べた導入背景や運用戦略を踏まえ、ここではArgoCD Image Updaterの具体的な設定や運用手順について詳しく解説します。特に、Helmを使用したEKSアプリケーションにおけるアノテーション設定や、GitHub Actionsを活用した自動PR作成の背景、そして実際に直面した課題とその解決策について紹介します。

ArgoCD Applicationへのアノテーション設定

ArgoCD Image Updaterを利用するには、対象のApplicationリソースに適切なアノテーションを追加する必要があります。

これらのアノテーションは、Image Updaterに対して「どのイメージを」「どのような条件で」「どのように更新するか」を指示する役割を果たします。
以下はそれぞれの環境ごとのアノテーション設定例です。

本番環境: 自動PR作成

弊社のEKSアプリケーションはHelmを使用しており、アノテーションは以下のように設定しました。
弊社のHelmチャートでは、basic-app.image.repositoryおよびbasic-app.image.tagといった命名規則でイメージ情報を管理しており、標準化されています。

argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-application
  annotations:
    argocd-image-updater.argoproj.io/write-back-method: git
    argocd-image-updater.argoproj.io/git-branch: main:release/image-updater{{range .Images}}-{{.Name}}/{{.NewTag}}{{end}} 
    argocd-image-updater.argoproj.io/image-list: my-image=<AWSアカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<リポジトリ>
    argocd-image-updater.argoproj.io/my-image.helm.image-name: "basic-app.image.repository"
    argocd-image-updater.argoproj.io/my-image.helm.image-tag: "basic-app.image.tag"
    argocd-image-updater.argoproj.io/my-image.update-strategy: semver
    argocd-image-updater.argoproj.io/write-back-target: "helmvalues:../../../prod/my-application/values.yaml"
    argocd-image-updater.argoproj.io/my-image.ignore-tags: latest
  • write-back-method: 更新情報の書き戻し方法を指定します。gitを指定することで、GitHubリポジトリに変更をコミットします。これによりGitOpsを実現できます。
  • git-branch: 更新時に作成されるブランチの命名規則を指定します。
  • image-list: 更新対象のイメージを指定します。my-imageはエイリアスで、ECRのリポジトリを指しています。
  • helm.image-name: Helmのvalues.yaml内でのイメージリポジトリのパスを指定します。
  • helm.image-tag: Helmのvalues.yaml内でのイメージタグのパスを指定します。
  • update-strategy: イメージの更新戦略を指定します。本番環境ではセマンティックバージョニング(semver)を使用しています。
  • write-back-target: 更新されたイメージタグの情報を書き戻す対象のvalues.yamlファイルを指定します。
    • helmvaluesを指定することで、Helmのvalues.yamlに直接書き戻すことを可能にします。
  • ignore-tags: 更新対象から除外するタグを指定します。ここではlatestタグを設定しています。

これらのアノテーションにより、Image Updaterは指定されたECRリポジトリのイメージを監視し、Helmのvalues.yamlファイル内のbasic-app.image.tagを更新します。

開発環境: 自動再起動

開発環境ではイメージの更新をよりスピーディに反映されるよう自動アップデートを採用しています。以下は開発環境に設定したArgoCD Applicationのアノテーションです。

argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-application
  namespace: argocd
  annotations:
    argocd-image-updater.argoproj.io/write-back-method: argocd
    argocd-image-updater.argoproj.io/image-list: my-image=<AWSアカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<リポジトリ>:<タグ>
    argocd-image-updater.argoproj.io/my-image.update-strategy: digest
    argocd-image-updater.argoproj.io/my-image.ignore-tags: latest
    argocd-image-updater.argoproj.io/my-image.helm.image-name: "basic-app.image.repository"
    argocd-image-updater.argoproj.io/my-image.helm.image-tag: "basic-app.image.tag"
  • write-back-method: 開発環境ではargocdを指定しており、Argo CD API経由で直接Applicationリソースに変更が反映されるため、PR作成やマージを介さずコンテナレジストリの変更を検知すると即時にデプロイが行われます。
  • image-list: 本番環境と同様です。タグにはdevのような環境識別子が使用し、開発環境専用のイメージを指しています。
  • update-strategy: ここではdigestを使用しており、イメージのダイジェスト(内容のハッシュ値)が変わった場合に限りアップデートが実行されます。これにより、タグの再利用(例: 同じタグでビルドし直す)でも変更を検知することができています。
  • ignore-tags: 本番環境と同様です。
  • helm.image-name / helm.image-tag:本番環境と同様です。

GitHub ActionsによるPR作成時の課題と対策

ここではGitHub ActionsでPRを自動作成する際に直面した課題とどのように解決したかを紹介します。

1. Helm構造に起因するYAML構造の崩れ

弊社のHelmチャート構造に起因する問題で、EKSアプリケーションは以下のように basic-appというトップレベルキーの下にまとめています。

values.yaml
basic-app:
  image:
    repository: xxxxx
    tag: v1.0.0
  env:
    - name: TZ
      value: Asia/Tokyo
    - name: ENV
      value: stg

そのためImage UpdaterがYAMLの階層を正しく認識できず、以下のようにインデントが崩れてしまう問題がありました。

values.yaml
  env:
-    - name: TZ
-      value: Asia/Tokyo
-    - name: ENV
-      value: stg
+  - name: TZ
+    value: Asia/Tokyo
+  - name: ENV
+    value: stg

これはPRの差分が大きくなりすぎたり、キーのインデントがずれることでデプロイが失敗する可能性がありました。

✅ 解決策: yqを使ってvalues.yamlを再フォーマット
この問題に対し、私たちはPRを作成するワークフローで、対象の values.yaml をyqコマンドを使用してフォーマットを整えるというアプローチを取りました。

yqはYAMLファイルを操作するためのCLIツールで、jqのYAML版とも言えます。

以下はワークフローの抜粋です。

yaml
- name: Reformat YAML files
  run: |
    if [[ -n "${{ env.VALUES_FILES }}" ]]; then
      for FILE in ${{ env.VALUES_FILES }}; do
        echo "Reformatting $FILE"
        yq eval -P -i "$FILE"
      done
    else
      echo "No values.yaml files found."
    fi

これにより、インデントや空白が整えられ、無駄な差分がない綺麗なPRが作成されました。

2. 冗長なコミットが発生&コメント・クォートが消える

前節のインデント問題を yq によって修正できたことで、PRの見た目は大きく改善されましたが次は二つの問題に直面しました。

  1. PRにコミットが2件積まれてしまう問題
    • ArgoCD Image Updaterによるイメージタグ更新のコミット
    • yqによる再フォーマットのコミット
  2. コメントやダブルクォートが消えてしまう問題
    これはImage Updaterの仕様でありyqで整形する以前にvalues.yamlに含まれるコメントや文字列に付けられたクォートが削除されてしまい、ファイルの保守性が損なわれてしまいました。

✅ 解決策: コミットを一度巻き戻し、必要な部分のみ再編集
初めはタグ部分のみ別ファイルに切り出して、それをImage Updaterで書き戻すというフローを採用しようと考えていましたが、運用面での複雑さを考慮し現在のアプローチを選択しました。
具体的には、ファイル管理の手間が増えることや、既存の運用ルールとの整合性を保つことなどを総合的に判断し、不採用としました。

そのため、以下のようなアプローチを取りました。

  1. Image Updaterによって自動生成されたコミットから、更新されたイメージタグを抽出
  2. そのコミットをgit revertで巻き戻し、差分を消す
  3. その上でvalues.yaml内の該当するimage.tagの値だけを、抽出したタグに差し替える

以下はワークフローの抜粋です。

yaml
# 3. Image Updater が書いたタグを抽出 (basic-app キーを持つ values.yaml のみ)
      - name: Extract new image tag and changed files
        id: get_tag_and_files
        run: |
          # 変更された values.yaml ファイル一覧
          ALL_CHANGED=$(git diff --name-only HEAD~1 HEAD | grep 'values.yaml$')
          FILES=""
          # basic-app キーを持つファイルのみフィルタ
          for FILE in $ALL_CHANGED; do
            if yq eval 'has("basic-app")' "$FILE" | grep -q true; then
              FILES="$FILES $FILE"
            fi
          done
          echo "files=$FILES" >> "$GITHUB_OUTPUT"
          # 最初に見つかった tag: 行からタグを抽出
          for FILE in $FILES; do
            TAG=$(git diff HEAD~1 HEAD -- "$FILE" \
              | grep '^\+ *tag:' \
              | head -1 \
              | sed 's/^[+[:space:]]*tag:[[:space:]]*//')
            if [[ -n "$TAG" ]]; then
              echo "IMAGE_TAG=$TAG" >> "$GITHUB_ENV"
              break
            fi
          done

      # 4. 最新コミットを取り消し
      - name: Revert to previous commit
        run: |
          BRANCH="${{ steps.extract_branch.outputs.branch }}"
          git checkout "$BRANCH"
          git reset --hard HEAD~1
          git push --force origin "$BRANCH"

      # 5. 保存したタグ値だけを再適用 (basic-app キーを持つ変更ファイルのみ)
      - name: Reapply image tag only
        env:
          BRANCH: ${{ steps.extract_branch.outputs.branch }}
          IMAGE_TAG: ${{ env.IMAGE_TAG }}
          FILES: ${{ steps.get_tag_and_files.outputs.files }}
        run: |
          for FILE in $FILES; do
            echo "Updating basic-app.image.tag in $FILE"
            # basic-app.image.tag パスを指定して更新
            yq eval -i '."basic-app".image.tag = strenv(IMAGE_TAG)' "$FILE"
          done
          git add $FILES
          if git diff-index --quiet HEAD --; then
            echo "No changes to commit"
          else
            git commit -m "Reapply image tag to ${IMAGE_TAG}"
            git fetch origin "$BRANCH"
            git rebase origin/"$BRANCH"
            git push origin "$BRANCH"
          fi

これにより、コメントやクォートを保持しつつ、PRのコミットは1件のみに抑えたまま、タグを更新できるため理想的な形でPRを生成することが出来ました。


導入後の効果

イメージタグの更新作業を手動で行う必要がなくなったため、ヒューマンエラーの発生を少なくすることができました。
開発環境では自動的に最新状態へ反映されるようになったため、開発者の作業負担を少なくし開発体験の向上にもつながる結果となりました。

開発者からの声🙌

今後の展望

  • GitHub Actionsワークフローの一括管理
    現在は各プロダクトリポジトリごとにワークフローファイルが定義されているため共通のワークフローを一括管理できるようなリポジトリの整備を進めたいです。
  • ArgoCD Status Badgeの導入
    ArgoCDのStatus Badgeを使うことで、同期状態やヘルスステータスなどをリポジトリのREADMEに可視化することができます。これにより「最新のマニフェストが適用されているか」や「サービスが正常に稼働しているか」を一目で確認できるようにし、運用効率の向上を目指していきます。

おわりに

本記事ではArgoCD Image Updaterの導入背景から、環境別の運用戦略まで、実際の運用事例を交えて解説しました。
KubernetesやCI/CDに関心のある方にとって、参考になれば幸いです。

著者プロフィール
システム基盤グループ システム基盤チーム
安藤(あんどう)

WealthNavi Engineering Blog

Discussion