😆

ArgoCD Image Updaterを導入しました(導入方法、メリット、デメリット)

2021/04/19に公開

ArgoCD Image Updater とは

  • ArgoCDによって管理されているkubernetesのワークロードのイメージを自動的に更新してくれるツール
  • 新しいイメージの更新を自動的に検知して、kubernetesのワークロードに適応してくれる
    • kubernetesのyamlをいちいち更新する必要なし
  • ArgoCDによって管理さているのApplicationリソースに annotation を付与することで細かい設定を行う
  • KustomizeHelmによって構築されているApplicationに対してのみ利用可能

https://github.com/argoproj-labs/argocd-image-updater

導入方法

詳しい導入方法は公式ドキュメントを参考にしてください。
https://argocd-image-updater.readthedocs.io/en/stable/install/start/

ここでは僕の行った導入例を紹介します。
僕の環境では以下のような構成で管理しています

  • KubernetesクラスタはGKE(v1.18)
  • KubernetesのリソースはArgoCD(v2.0.0) + Kustomize
  • dockerイメージはGCR
  • GCPリソースはTerraform
  • KubernetesのsercetはGoogle Secret ManagerExternal Secret

導入手順は主に以下の3ステップです

  • 1,argocd-image-updaterのmanifestをapplyする
  • 2,GCRへのアクセス権限を付与する
  • 3,ArgoCDのapplicationにannotationを追記する

①manifestをapplyする

以下の2つのファイルを作成し、kustomizeで行いました。

  • some_dir/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- github.com/argoproj-labs/argocd-image-updater/manifests/base?ref=v0.9.2
patches:
- config_map.yaml
  • some_dir/config_map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config
data:
  log.level: debug

あとは以下のコマンドを叩けば、kubernetesにインストールされます

kustomize -n argocd build some_dir | kubectl apply -f -

kustomizeのremote targetsという機能を使うことで、外部のGitリポジトリのURLをbaseに指定することができます。
あとは変更したい箇所でpatchを当てればよいので、kubernetesにツールをインストールする時のバージョン管理、パラメータ管理を楽に行うことができます。

実際はArgoCDのApplicationとして管理していますが、ここでは簡略化しています。
また、argocd-image-updaterはArgoCDのApplicationと同じnamespaceであるargocdにデプロイしています。それが公式にも推奨されていますし、そうできればややこしい設定をほぼする必要がなくなるからです。

②Container Registriesへのアクセス権限を付与する

ここがちょっとめんどくさかったです。
GCRへアクセスするためのsecretを作成し、argocd-image-updaterのconfigmapに記載してあげる必要があります。

何パターンが実装方法があり、詳しくはこちらをみてください。
https://argocd-image-updater.readthedocs.io/en/stable/configuration/registries/#specifying-credentials-for-accessing-container-registries

今回僕は.dockerconfigjsonフィールドを持つsecretを作成する方法にしました。(GCRを使う場合はおそらくこの方法しかない?)

やるべきことは以下の4つです

  • 1, GCRへのアクセス権限を持ったServiceAccountを作成する
  • 2, ServiceAccountのkeyを発行する(xxx.json)
  • 3, .dockerconfigjsonフィールドを持つsecretを作成する
  • 4, argocd-image-updater-configregistries.confフィールドを追記する

まずは①と②について、以下のようにTerraformで行いました。

# GCRを使うと自動的に作成されるstorage
# すでに作成されている場合は、以下のようなimportコマンドを実行する必要がある
# terraform import google_storage_bucket.gcr_storage <バケット名>
resource "google_storage_bucket" "gcr_storage" {
  name = "asia.artifacts.${var.gcp_project[terraform.workspace]}.appspot.com"
  location = "ASIA"
}

# サービスアカウントを作成する
resource "google_service_account" "argocd_image_updater" {
  account_id   = "argocd-image-updater"
  display_name = "argocd-image-updater account"
}

# オブジェクトレベルで参照権限を付与する
resource "google_storage_bucket_iam_binding" "gcr_storage_binding" {
  bucket = google_storage_bucket.gcr_storage.name
  role   = "roles/storage.objectViewer"
  members = ["serviceAccount:${google_service_account.argocd_image_updater.email}"]
}

# サービスアカウントのKeyを生成する
resource "google_service_account_key" "argocd_image_updater" {
  service_account_id = google_service_account.argocd_image_updater.name
}

③のsercetの生成について、kubectlコマンドを使う場合は

kubectl create secret docker-registry hoge --docker-server="https://asia.gcr.io" --docker-username=_json_key --docker-email=test-serviceaccount@some-gcp-project.iam.gserviceaccount.com --docker-password="$(cat xxxx.json)"

のように行うこともできます。
しかし、手運用はしたくなかったので、TerraformでGoogleSecretManagerに必要な機密情報を登録し、ExternalSecretを使ってKubernetesのSecretリソースを作成しました。

Terraformでは以下のようにして、GoogleSecretManagerに機密情報を作成しました。

resource "google_secret_manager_secret" "argocd_image_updater_secret_for_docker_registry" {
  secret_id = "argocd_image_updater_secret_for_docker_registry"

  replication {
    automatic = true
  }
}

resource "google_secret_manager_secret_version" "argocd_image_updater_secret_for_docker_registry" {
  secret = google_secret_manager_secret.argocd_image_updater_secret_for_docker_registry.id

  secret_data = jsonencode({
    auths = {
      "https://asia.gcr.io" = {
        username = "_json_key"
        password = base64decode(google_service_account_key.argocd_image_updater.private_key)
        email = google_service_account.argocd_image_updater.email
        auth = base64encode("_json_key:${base64decode(google_service_account_key.argocd_image_updater.private_key)}")
      }
    }
  })
}

ExternalSecretは次のように定義しました。

apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
  name: argocd-image-updater-docker-registry-secret
  namespace: argocd
spec:
  backendType: gcpSecretsManager
  projectId: xxxxxxx
  template:
    type: kubernetes.io/dockerconfigjson
  data:
  - name: .dockerconfigjson
    key: argocd_image_updater_secret_for_docker_registry
    version: latest

④のargocd-image-updater-configregistries.confフィールドを追記は以下のようにして行いました

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config
data:
  log.level: debug
  registries.conf: |
    registries:
    - name: Google Container Registry Asia
      api_url: https://asia.gcr.io
      prefix: asia.gcr.io
      ping: no
      credentials: pullsecret:argocd/argocd-image-updater-docker-registry-secret

これでargocd-image-updaterがGCRにアクセスする準備が整いました。

③ArgoCDのapplicationにannotationを追記する

僕が追記したAnnotationは以下になります

  • 開発環境
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/image-list: my-image=asia.gcr.io/some-gcp-project/some-image-name
argocd-image-updater.argoproj.io/my-image.update-strategy: latest
argocd-image-updater.argoproj.io/my-image.allow-tags: 'regexp:^[0-9a-f]{5,40}$'
argocd-image-updater.argoproj.io/my-image.ignore-tags: latest
  • 本番環境
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/image-list: my-image=asia.gcr.io/some-gcp-project/some-image-name
argocd-image-updater.argoproj.io/my-image.update-strategy: semver
argocd-image-updater.argoproj.io/my-image.ignore-tags: latest

argocd-image-updater.argoproj.io/write-back-method: git

write-back-methodargocdgitを指定できます。gitを指定すると、対象のgitリポジトリに適応中のimageタグを自動でコミットしてくれます。
GitOpsとして、Gitリポジトリを見れば現在のインフラの状態がわかる、という状況を作るためには、write-back-methodgitにすべきです。(GitOpsしたいならgit一択です)

argocd-image-updater.argoproj.io/image-list: my-image=asia.gcr.io/some-gcp-project/some-image-name

argocd-image-updaterが取得すべき対象のimage名の一覧を登録します。
=を使うと、それぞれのimage名にaliasを作成することもできるので、しておいた方が良いでしょう。

argocd-image-updater.argoproj.io/my-image.update-strategy: latest

imageのアップデート戦略を指定することができます。
単純に最新のものをイメージタグを使う場合はlatest、セマンティックバージョニングで最新のバージョンを使う場合はsemverを指定します。
開発環境では、コミット毎にCOMMIT_SHAのイメージタグを作成しているので、作成するたびにデプロイしてもらいます。
本番環境では、タグを打つとタグ名のイメージタグを作成しているので、最新のバージョンをデプロイしてほしいもらいます。

argocd-image-updater.argoproj.io/my-image.allow-tags: 'regexp:^[0-9a-f]{5,40}$'

開発環境では、update戦略がlatestの指定になっているので、たとえばtestというイメージタグを作成した場合にも最新のイメージタグと認識されデプロイされてしまいます。それを避けるために、GitのコミットハッシュであるSHA1を正規表現で定義してあります。

argocd-image-updater.argoproj.io/my-image.ignore-tags: latest

イメージをビルドする際、COMMIT_SHAのイメージタグと同時にlatestというイメージタグも同時に作成しています。argocd-image-updaterがlatestを最新と認識してしまい、新しいイメージをビルドしてもlatestと認識してしまうと、Deploymentの書き換えが発生しないので最新のイメージがデプロイされなくなってしまいます。
それを避けるために、latestタグは無視するようにしてます。

Kustomize使ってる人のハマりポイント

Kustomizeのイメージの上書き機能を使っている場合、argocd-image-updaterによるイメージタグの上書きが正しく行われません!
なので、DeploymentなどでKustomizeのイメージの上書き機能は使わず、イメージ名を直接書いてあげる必要がありますので、ご注意ください!

導入してみて

デプロイフローの変化

argocd-image-updaterを導入する前には以下の図のようにデプロイしていました。

  1. 各サービスのリポジトリでプッシュ
  2. CIでテスト、イメージのビルド、ArgoCDのアプリケーションを管理しているリポジトリへプッシュ
  3. ArgoCDが自動でデプロイ

argocd-image-updaterを導入する後は以下の図のようになり、CIからのプッシュがなくなりました。

  1. 各サービスのリポジトリでプッシュ
  2. CIでテスト、イメージのビルドのみ
  3. ArgoCDが自動でデプロイ

よかったこと

デプロイフローがシンプルになったことです。他リポジトリへのコミットは、やや複雑なシェルを書く必要がありましたし、アクセス権限を管理する必要があったのですが、それらを捨てることができたのでかなりすっきりしました。

よくなかったこと

デプロイをプルリクエストで管理できなくなりました。
ArgoCDのアプリケーションを管理しているGitリポジトリで、新しいイメージタグに変更するプルリクエストを作成し、それをマージすることでデプロイを管理するという方法はできません。
なので、慎重なデプロイが求められる開発環境下ではargocd-image-updaterを導入しない方がよいでしょう。

古いバージョンのイメージをデプロイできなくなりました。
古いバージョンのイメージを指定してあげたとしても、argocd-image-updaterが自動で最新のものに書き換えてしまうからです。
なので、「最新バージョンをリリースしたけど、バグってしまった!前のバージョンにロールバックするぞ!」っといったことが、ArgoCDのアプリケーションを管理しているGitリポジトリ上でできなくなります。
それを行う方法としては、以下のパターンが考えられます

  • ArgoCD-UI上から、もしくは、ArgoCD-CLIからRollback機能使う
  • annotationを消すなどして、一時的にargocd-image-updaterを利用しないようにする
  • 最新のイメージを消してから、ArgoCDのアプリケーションを管理しているGitリポジトリにコミットする

いずれの方法も様々なデメリットがあり、行いたくないですが、リリースミスってしまった場合には妥協するしかなさそうです😅

最後に

導入してみて、一番難しかったのでは、docker registryへのアクセス権限を付与する部分でした。
またKustomizeのイメージの上書き機能によるargocd-image-updaterが機能しないハマりポイントにはわりと苦しめられました。

リリースをミスった時のロールバックをいい感じにできないというデメリットは大きいですが、CIがすっきりしたメリットも大きいと感じているので、いったんこのまま運用してみます。
まだargocd-image-updaterを使った運用はそんなにしてませんが、今後運用していく中でハマりポイントなど発見したら、紹介できればなと思います!

Discussion