好きな技術を布教したい 😗

〜 沼のまわりに餌をまく 〜

【ArgoCD🐙】ArgoCDのマイクロサービスアーキテクチャと自動デプロイの仕組み


この記事から得られる知識

この記事を読むと、以下を "完全に理解" できます✌️



01. はじめに


ロケットに乗るタコのツラが腹立つわー。

argocd_rocket
画像引用元:Argo Project


さて最近の業務で、全プロダクトの技術基盤開発チームに携わっており、全プロダクト共有のArgoCD🐙とAWS EKSをリプレイスしました。

今回は、採用した設計プラクティスの紹介も兼ねて、ArgoCDのマイクロサービスアーキテクチャと自動デプロイの仕組みを記事で解説しました。

ArgoCDは、kubectlコマンドによるマニフェストのデプロイを自動化するツールです。

ArgoCDのアーキテクチャには変遷があり、解説するのは執筆時点 (2023/05/02) で最新の 2.6 系のArgoCDです。

アーキテクチャや仕組みはもちろん、個々のマニフェストの実装にもちょっとだけ言及します。

それでは、もりもり布教していきます😗

記事中のこのボックスは、補足情報を記載しています。

飛ばしていただいても大丈夫ですが、読んでもらえるとより理解が深まるはずです👍


02. 概要

アーキテクチャ

レイヤー

まずは、ArgoCDのアーキテクチャのレイヤーがどのようになっているかを見ていきましょう。

ArgoCD公式から、コンポーネント図が公開されています。

図から、次のようなことがわかります👇

  • 下位レイヤー向きにしか依存方向がなく、例えばコアドメインとインフラのレイヤー間で依存性は逆転させていない。
  • レイヤーの種類 (UI、アプリケーション、コアドメイン、インフラ) とそれらの依存方向から、レイヤードアーキテクチャのようなレイヤーに分けている。
  • 特にコアドメインレイヤーが独立したコンポーネントに分割されており、マイクロサービスアーキテクチャを採用している。

argocd_architecture_layer.png


▶ ArgoCDのマイクロサービスアーキテクチャの分割単位について

ArgoCDのマイクロサービスアーキテクチャは、機能単位の分割方法を採用していると推測しています。

本記事では詳しく言及しませんが、マイクロサービスアーキテクチャの分割方法には大小いくつかの種類があり、境界付けられたコンテキストで分割することがベタープラクティスと言われています😎

(境界付けられたコンテキストについても、ちゃんと記事を投稿したい...)

機能単位による分割は、境界付けられたコンテキストのそれよりも粒度が小さくなります。


▶ ArgoCDのマイクロサービスアーキテクチャの設計図について

ArgoCDでは、マイクロサービスアーキテクチャの設計図にコンポーネント図を使用しています。

コンポーネント図では、依存方向 (そのコンポーネントがいずれのコンポーネントを使用するのか) に着目できます。

そのため、これはマイクロサービス間の依存方向を視覚化するために有効なUML図です🙆🏻‍

コンポーネント

次に、コンポーネントの種類を紹介します。

ArgoCDの各コンポーネントが組み合わさり、マニフェストの自動的なデプロイを実現します。

ArgoCD (2.6系) のコンポーネントはいくつかあり、主要なコンポーネントの種類とレイヤーは以下の通りです👇

コンポーネント レイヤー 機能
argocd-server
(argocd-apiserver)
UI・アプリケーション みんながよく知るArgoCDのダッシュボードです。
また、ArgoCDのAPIとしても機能します。
現在、複数のレイヤーの責務を持っており、将来的にUIとアプリケーションは異なるコンポーネントに分割されるかもしれません。
application-controller コアドメイン Clusterにマニフェストをデプロイします。
また、ArgoCD系カスタムリソースのカスタムコントローラーとしても機能します。
repo-server コアドメイン マニフェスト/チャートリポジトリからクローンを取得します。
また、クローンからマニフェストを作成します。
redis-server インフラ application-controllerの処理結果のキャッシュを保管します。
dex-server インフラ SSOを採用する場合、argocd-serverの代わりに認可リクエストを作成し、またIDプロバイダーに送信します。
これにより、argocd-server上の認証フェーズをIDプロバイダーに委譲できます。


以降の図の凡例です。

ArgoCDの各コンポーネント (application-controller、argocd-server、dex-server、repo-server) と各リソース (Application、AppProject) を区別しています。

argocd_architecture_legend.png

仕組み

それでは、ArgoCDは、どのようにコンポーネントを組み合わせて、マニフェストをデプロイするのでしょうか。

ここではプロダクト用Cluster管理者 (デプロイ先となるClusterを管理するエンジニア) は、ArgoCDのダッシュボードを介してマニフェストをデプロイするとしましょう。

まずは、概要を説明していきます。

argocd_architecture_introduction.png

(1) repo-serverによるクローン取得

ArgoCDのCluster上で、repo-serverがマニフェスト/チャートリポジトリのクローンを取得します。

(2) application-controllerによるマニフェスト取得

application-controllerは、repo-serverからマニフェストを取得します。

(3) application-controllerによるCluster確認

application-controllerは、プロダクト用Clusterの現状を確認します。

(4) application-controllerによる処理結果保管

application-controllerは、処理結果をredis-serverに保管します。

(5) argocd-serverによるキャッシュ取得

argocd-serverは、redis-serverからキャッシュを取得します。

(6) 管理者のログイン

プロダクト用Cluster管理者は、argocd-serverにログインしようとします。

(7) IDプロバイダーへの認証フェーズ委譲

argocd-serverは、ログイン時にIDプロバイダーに認証フェーズを委譲するために、dex-serverをコールします。


▶ argocd-serverのログイン手法について

プロダクト用Cluster管理者のログインには、利便性と安全性を兼ね備えたSSOの採用がオススメです。

今回の記事では、SSOを採用した場合の仕組みを紹介しています🙇🏻‍

(8) dex-serverによる認証リクエスト送信

dex-serverは、IDプロバイダーに認可リクエストを作成し、これをIDプロバイダーに送信します。

(9) argocd-serverによる認可フェーズ実行

argocd-serverで認可フェーズを実施します。

ログインが完了し、プロダクト用Cluster管理者は認可スコープに応じてダッシュボードを操作できます。


▶ ArgoCDをどのClusterで管理するかについて

プロダクト用Clusterの障害の影響範囲を受けないように、ArgoCDは、プロダクト用Clusterとは独立したClusterで作成した方がよいです。

今回の記事では、ArgoCD用Clusterを採用した場合の仕組みを紹介しています🙇🏻‍

(10) application-controllerによるマニフェストデプロイ

application-controllerは、Clusterにマニフェストをデプロイします。

マニフェストのデプロイの仕組みをざっくり紹介しました。

ただこれだと全く面白くないため、各コンポーネントの具体的な処理と、各々がどのように通信しているのかを説明します✌️


03. repo-server

repo-serverとは

まずは、コアドメインレイヤーにあるrepo-serverです。

マニフェスト/チャートリポジトリ (例:GiHub、GitHub Pages、Artifact Hub、AWS ECR、Artifact Registry、など) からクローンを取得します。

repo-serverを持つPodには、他に軽量コンテナイメージからなるInitContainerとサイドカー (cmp-server) がおり、それぞれ機能が切り分けられています👍

仕組み

argocd_architecture_repo-server.png

(1) InitContainerによるお好きなツールインストール & argocd-cliバイナリコピー

repo-serverの起動時に、InitContainerでお好きなマニフェスト管理ツール (Helm、Kustomize、など) やプラグイン (helm-secrets、KSOPS、SOPS、argocd-vault-plugin、など) をインストールします。

また、サイドカーのcmp-serverでは起動時に/var/run/argocd/argocd-cmp-serverコマンドを実行する必要があり、InitContainer (ここではcopyutilコンテナ) を使用して、ArgoCDのコンテナイメージからargocd-cliのバイナリファイルをコピーします。

repo-serverのざっくりした実装例は以下の通りです👇

ここでは、ArgoCDで使いたいツール (Helm、SOPS、helm-secrets) をInitContainerでインストールしています。

apiVersion: v1
kind: Pod
metadata:
  name: argocd-repo-server
  namespace: argocd
spec:
  containers:
    - name: repo-server
      image: quay.io/argoproj/argocd:latest

  initContainers:
    # HelmをインストールするInitContainer
    - name: helm-installer
      image: alpine:latest
      command:
        - /bin/sh
        - -c
      args:
        - |
          # インストール処理
      volumeMounts:
        - mountPath: /custom-tools
          name: custom-tools
    # SOPSをインストールするInitContainer
    - name: sops-installer
      image: alpine:latest
      command:
        - /bin/sh
        - -c
      args:
        - |
          # インストール処理
      volumeMounts:
        - mountPath: /custom-tools
          name: custom-tools
    # helm-secretsをインストールするInitContainer
    - name: helm-secrets-installer
      image: alpine:latest
      command:
        - /bin/sh
        - -c
      args:
        - |
          # インストール処理
      volumeMounts:
        - mountPath: /helm-working-dir/plugins
          name: helm-working-dir

    ...

    # cmp-serverにargocd-cliのバイナリをコピーするInitContainer
    - name: copyutil
      image: quay.io/argoproj/argocd:latest
      command:
        - cp
        - -n
        - /usr/local/bin/argocd
        - /var/run/argocd/argocd-cmp-server
      volumeMounts:
        - name: var-files
          mountPath: /var/run/argocd

  # Podの共有ボリューム
  volumes:
    - name: custom-tools
      emptyDir: {}
    - name: var-files
      emptyDir: {}


▶ ArgoCDのコンテナイメージに組み込まれているツールについて

ArgoCDのコンテナイメージ (quay.io/argoproj/argocd) には、いくつかのツール (例:Helm、Kustomize、Ks、Jsonnet、など) の推奨バージョンがあらかじめインストールされています。

そのため、これらのツールのプラグイン (例:helm-secrets) を使用する場合、上記のコンテナイメージからなるrepo-server内のツールをcmp-serverにコピーすればよいのでは、と思った方がいるかもしれません。

この方法は全く問題なく、cmp-serverの/usr/local/binディレクトリ配下にツールをコピーするように、InitContainerを定義してもよいです。
apiVersion: v1
kind: Pod
metadata:
  name: argocd-repo-server
  namespace: foo
spec:
  containers:
    - name: repo-server
      image: quay.io/argoproj/argocd:latest
      volumeMounts:
        - mountPath: /usr/local/bin/helm
          # Podの共有ボリュームを介して、repo-serverでHelmを使用する。
          name: custom-tools

  initContainers:
    - name: copy-helm
      image: quay.io/argoproj/argocd:latest
      # InitContainer上のHelmをVolumeにコピーする
      command:
        - /bin/cp
        - -n
        - /usr/local/bin/helm
        - /custom-tools/helm
      volumeMounts:
        - mountPath: /custom-tools
          name: custom-tools

  # 共有ボリューム
  volumes:
    - name: custom-tools
      emptyDir: {}

反対に、これらツールをInitContainerでインストールし直す場合は、ArgoCD上での推奨バージョンをちゃんとインストールするようにしましょう👍

2.6系では、ArgoCDのリポジトリ内のtool-versions.shファイルに、Helmのバージョンが定義されています。
spec:

  ...

  initContainers:
    - name: helm-installer
      image: alpine:latest
      command:
        - /bin/sh
        - -c
      # ArgoCDのリポジトリ上のtool-versions.shファイルから、Helmのバージョンを取得する
      args:
        - |
          apk --update add curl wget
          ARGOCD_VERSION=$(curl -s https://raw.githubusercontent.com/argoproj/argo-helm/argo-cd-<ArgoCDのバージョン>/charts/argo-cd/Chart.yaml | grep appVersion | sed -e 's/^[^: ]*: //')
          HELM_RECOMMENDED_VERSION=$(curl -s https://raw.githubusercontent.com/argoproj/argo-cd/"${ARGOCD_VERSION}"/hack/tool-versions.sh | grep helm3_version | sed -e 's/^[^=]*=//')
          wget -q https://get.helm.sh/helm-v"${HELM_RECOMMENDED_VERSION}"-linux-amd64.tar.gz
          tar -xvf helm-v"${HELM_RECOMMENDED_VERSION}"-linux-amd64.tar.gz
          cp ./linux-amd64/helm /custom-tools/
          chmod +x /custom-tools/helm
      volumeMounts:
        - mountPath: /custom-tools
          name: custom-tools

  ...

(2) repo-serverによる認証情報取得

repo-serverは、Secret (argocd-repo-creds) からリポジトリの認証情報を取得します。

argocd-repo-credsではリポジトリの認証情報のテンプレートを管理しています。

指定した文字列から始まる (最長一致) URLを持つリポジトリに接続する場合、それらの接続で認証情報を一括して適用できます。

argocd-repo-credsのざっくりした実装例は以下の通りです👇

ここでは、リポジトリSSH公開鍵認証を採用し、argocd-repo-credsに共通の秘密鍵を設定しています。

apiVersion: v1
kind: Secret
metadata:
  name: argocd-repo-creds-github
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repo-creds
type: Opaque
data:
  type: git
  url: https://github.com/hiroki-hasegawa
  # 秘密鍵
  sshPrivateKey: |
    MIIC2 ...

あとは、各リポジトリのSecret (argocd-repo) にURLを設定しておきます。

すると、先ほどのargocd-repo-credsのURLに最長一致するURLを持つSecretには、一括して秘密鍵が適用されます。

# foo-repositoryをポーリングするためのargocd-repo
apiVersion: v1
kind: Secret
metadata:
  namespace: argocd
  name: foo-argocd-repo
  labels:
    argocd.argoproj.io/secret-type: repository
type: Opaque
data:
  # 認証情報は設定しない。
  # チャートリポジトリ名
  name: bar-repository
  # https://github.com/hiroki-hasegawa に最長一致する。
  url: https://github.com/hiroki-hasegawa/bar-chart.git
---
# baz-repositoryをポーリングするためのargocd-repo
apiVersion: v1
kind: Secret
metadata:
  namespace: foo
  name: baz-argocd-repo
  labels:
    argocd.argoproj.io/secret-type: repository
type: Opaque
data:
  # 認証情報は設定しない。
  # チャートリポジトリ名
  name: baz-repository
  # https://github.com/hiroki-hasegawa に最長一致する。
  url: https://github.com/hiroki-hasegawa/baz-chart.git

(3) repo-serverのよるクローン取得とポーリング

repo-serverは、認証情報を使用して、リポジトリgit cloneコマンドを実行します。

取得したクローンを、/tmp/_argocd-repoディレクトリ配下にUUIDの名前で保管します。

また、リポジトリの変更をポーリングし、変更を検知した場合はgit fetchコマンドを実行します。

# クローンが保管されていることを確認できる
$ kubectl -it exec argocd-repo-server \
    -c repo-server \
    -n foo \
    -- bash -c "ls /tmp/_argocd-repo/<URLに基づくUUID>"

# リポジトリ内のファイル
Chart.yaml  README.md  templates  values.yaml


▶ repo-serverでのクローン保管先のバージョン差異について

2.3以前では、repo-serverは/tmpディレクトリ配下にURLに基づく名前でクローンを保管します。
$ kubectl -it exec argocd-repo-server \
    -c repo-server \
    -n foo \
    -- bash -c "ls /tmp/https___github.com_hiroki-hasegawa_foo-repository"

# リポジトリ内のファイル
Chart.yaml  README.md  templates  values.yaml

(4) repo-serverによるサイドカーコール

repo-serverは、自身にマウントされたいくつかのマニフェスト管理ツール (例:Helm、Kustomize) を実行する機能を持っています。

しかし、実行できないツールではサイドカー (cmp-server) をコールします。

この時、Applicationのspec.source.pluginキーでプラグイン名を指定すると、そのApplicationではサイドカーをコールします。

逆を言えば、プラグイン名を指定していないApplicationは、サイドカーをコールしない です。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: foo-application
  namespace: foo
spec:
  source:
    plugin:
      name: helm-secrets # このプラグイン名は、ConfigManagementPluginのmetadata.nameキーに設定したもの

  ...

このコールは、Volume上のUnixドメインソケットを経由します。

Unixドメインソケットのエンドポイントの実体は.sockファイルです。

$ kubectl exec -it argocd-repo-server -c foo-plugin-cmp-server\
    -- bash -c "ls /home/argocd/cmp-server/plugins/"

foo-plugin.sock


Unixソケットドメインについて

Unixソケットドメインは、同じOS上のファイルシステムを介して、データを直接的に送受信する仕組みです。

Unixソケットドメインを使用すると、同じVolumeがマウントされたコンテナのプロセス間で、データを送受信できます👍

(5) repo-serverによる暗号化キーと暗号化変数の取得

cmp-serverは、暗号化キー (例:AWS KMS、Google CKM、など) を使用してSecretストア (例:AWS SecretManager、Google SecretManager、SOPS、Vault、など) の暗号化変数を復号化します。


クラウドプロバイダーの暗号化キーを使用するために必要な証明書について

暗号化キーがクラウドプロバイダーにある場合、クラウドプロバイダーがHTTPSプロトコルの使用を求める場合があります。

cmp-serverに軽量なコンテナイメージを使用していると、/etc/sslディレクトリ (ディレクトリはOSによって異なる) に証明書が無く、cmp-serverがHTTPSプロトコルを使用できない可能性があります。

その場合は、お好きな方法で証明書をインストールし、コンテナにマウントするようにしてください👍
apiVersion: v1
kind: Pod
metadata:
  name: argocd-repo-server
  namespace: foo
spec:
  containers:
    - name: repo-server
      image: quay.io/argoproj/argocd:latest

  ...

    # サイドカーのcmp-server
    - name: helm-secrets-cmp-server
      image: ubuntu:latest

      ...

      volumeMounts:
        # サイドカーがAWS KMSを使用する時にHTTPSリクエストを送信する必要があるため、証明書をマウントする
        - name: certificate
          mountPath: /etc/ssl
  ...

  initContainers:
    - name: certificate-installer
      image: ubuntu:latest
      command:
        - /bin/sh
        - -c
      args:
        - |
          apt-get update -y
          # ルート証明書をインストールする
          apt-get install -y ca-certificates
          # 証明書を更新する
          update-ca-certificates
      volumeMounts:
        - mountPath: /etc/ssl
          name: certificate

  volumes:
    - name: certificate
      emptyDir: {}

(6) サイドカーによるプラグイン処理の取得

cmp-serverは、マニフェスト管理ツールのプラグイン (helm-secrets、argocd-vault-plugin、など) を実行します。

この時マニフェストの作成時のプラグインとして、ConfigMap配下のConfigManagementPluginでプラグインの処理を定義します。

ざっくりした実装例は以下の通りです👇

ここでは、プラグインとしてhelm-secretsを採用し、helm secrets templateコマンドの実行を定義します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmp-cm
  namespace: foo
data:
  helm-secrets-plugin.yaml: |
    apiVersion: argoproj.io/v1alpha1
    kind: ConfigManagementPlugin
    metadata:
      namespace: foo
      name: helm-secrets # このプラグイン名は、Applicationのspec.source.pluginキーで指定したもの
    spec:
      generate:
        command:
          - /bin/bash
          - -c
        args:
          - |
            set -o pipefail
            helm secrets template -f $ARGOCD_ENV_SECRETS -f $ARGOCD_ENV_VALUES -n $ARGOCD_APP_NAMESPACE $ARGOCD_APP_NAME .
  foo-plugin.yaml: |
    ...


▶ ConfigManagementPluginのファイル名について

プラグインごとにConfigManagementPluginのマニフェストを定義できるように、各ConfigManagementPluginを異なるファイル名とし、ConfigMapで管理するとよいです👍

(7) サイドカーによるプラグイン処理の実行

cmp-serverはプラグインを実行し、Secretを含むマニフェストを作成します。

ConfigMap配下のファイルをplugin.yamlの名前でサイドカーにマウントする必要があります。

また、先ほどのUnixドメインソケットの.sockファイルや、 cmp-serverがプラグインを実行するための各バイナリファイルもマウントが必要です。

ざっくりした実装例は以下の通りです👇

ここでは、helm-secretsプラグインを実行するサイドカー (helm-secrets-cmp-server) を作成します。

apiVersion: v1
kind: Pod
metadata:
  name: argocd-repo-server
spec:
  containers:
    # repo-server
    - name: repo-server
      image: quay.io/argoproj/argocd:latest

    ...

    # helm-secretsのcmp-server
    - name: helm-secrets-cmp-server
      # コンテナイメージは軽量にする
      image: ubuntu:latest
      command:
        - /var/run/argocd/argocd-cmp-server
      env:
        # helmプラグインの場所を設定する
        - name: HELM_PLUGINS
          value: /helm-working-dir/plugins
      securityContext:
        runAsNonRoot: true
        runAsUser: 999
      volumeMounts:
        # リポジトリのクローンをコンテナにマウントする
        - name: tmp
          mountPath: /tmp
        # ConfigManagementPluginのマニフェスト (helm-secrets.yaml) を "plugin.yaml" の名前でコンテナにマウントする
        - name: argocd-cmp-cm
          mountPath: /home/argocd/cmp-server/config/plugin.yaml
          subPath: helm-secrets.yaml
        # コンテナ間で通信するためのUnixドメインソケットファイルをコンテナにマウントする
        - name: plugins
          mountPath: /home/argocd/cmp-server/plugins
        # 任意のツールのバイナリファイルをコンテナにマウントする
        - name: custom-tools
          mountPath: /usr/local/bin
        # helmプラグインのバイナリをコンテナにマウントする
        - name: helm-working-dir
          mountPath: /helm-working-dir/plugins

      ...

  # Podの共有ボリューム
  volumes:
    # リポジトリのクローンを含む
    - name: tmp
      emptyDir: {}
    # Helmなどの任意のツールを含む
    - name: custom-tools
      emptyDir: {}
    # helmプラグインを含む
    - name: helm-working-dir
      emptyDir: {}


▶ マウント時のConfigManagementPluginのファイル名について

ArgoCDのv2.6では、ConfigManagementPluginのマニフェスト/home/argocd/cmp-server/configディレクトリに、plugin.yamlの名前でマウントしないといけません。

これは、cmp-serverの起動コマンド (/var/run/argocd/argocd-cmp-server) がplugin.yamlの名前しか扱えないためです。

ArgoCD公式の見解で、サイドカーでは単一のプラグインしか実行できないように設計しているとのコメントがありました。

今後のアップグレードで改善される可能性がありますが、v2.6では、ConfigManagementPluginの数だけcmp-serverが必要になってしまいます🙇🏻‍


▶ Kustomizeのプラグインをどのコンテナで実行するかについて

Kustomizeのプラグイン (例:KSOPS) によるマニフェスト作成は、サイドカーではなくrepo-serverで実行した方がよいかもしれません (Helmプラグインサイドカーで問題ないです)。

執筆時点 (2023/05/02) では、ArgoCDとKustomizeが密に結合しています。

例えば、ArgoCD上のKustomize系オプションはrepo-serverでマニフェストを作成することを想定して設計されています。

無理やりサイドカーでKustomizeのプラグインを実行しようとすると、ArgoCDの既存のオプションを無視した実装になってしまうため、Kustomizeのプラグインだけはrepo-serverで実行することをお勧めします😢


クラウドプロバイダーのSecretストアを採用する場合について

今回は詳しく言及しませんが、クラウドプロバイダーのSecretストア (例:AWS SecretManager、Google SecretManager、など) の変数を使用する場合は、Secretのデータ注入ツールのプラグイン (特にargocd-vault-plugin) を採用しなくてもよいです。

この場合、代わりにSecretsストアCSIドライバーやExternalSecretsOperatorを使用できます。

これらは、クラウドプロバイダーから変数を取得し、これをSecretにデータとして注入してくれます🙇🏻‍


04. application-controller、redis-server

application-controllerとは

コアドメインレイヤーにあるapplication-controllerです。

Clusterにマニフェストをデプロイします。

また、ArgoCD系カスタムリソースのカスタムコントローラーとしても機能します。

redis-serverとは

インフラレイヤーにあるredis-serverです。

application-controllerの処理結果のキャッシュを保管します。

仕組み

argocd_architecture_application-controller.png

(1) ArgoCD用Cluster管理者のkubectl applyコマンド

ArgoCD用Clusterの管理者は、ClusterにArgoCD系のカスタムリソース (例:Application、AppProject、など) をデプロイします。


▶ ArgoCD自体のデプロイにargo-helmを採用する場合について

『卵が先か、ニワトリが先か』みたいな話ですが、ArgoCD自体はArgoCD以外でデプロイする必要があります。

この時、argo-helmを使用すると簡単にArgoCDのマニフェストを作成できます。


ただしHelmの重要な仕様として、チャートの更新時に使用するhelm upgradeコマンドは、CRDを作成できる一方でこれを変更できません。

HelmでCRDを作成するとHelmの管理ラベルが挿入されてしまうため、作成の時点からCRDがHelmの管理外となるように、kubectlコマンドでCRDを作成した方がよいです👍
$ kubectl diff -k "https://github.com/argoproj/argo-cd/manifests/crds?ref=<バージョンタグ>"

$ kubectl apply -k "https://github.com/argoproj/argo-cd/manifests/crds?ref=<バージョンタグ>"
ArgoCD上でHelmを使用してデプロイする場合はこの仕様を気にしなくてよいのかな、と思った方がいるかもしれないです。

ですが本記事で解説した通り、ArgoCDはcmp-serverのhelm templateコマンド (この時、--include-crdsオプションが有効になっている) や、application-controllerのkubectl applyコマンドを組み合わせてマニフェストをデプロイしているため、CRDもちゃんと更新してくれます👍🏻

(2) application-controllerによるArgoCD系カスタムリソースのReconciliation

kube-controller-managerは、application-controllerを操作し、Reconciliationを実施します。

application-controllerは、Etcd上に永続化されたマニフェストと同じ状態のArgoCD系カスタムリソースを作成/変更します。


▶ カスタムコントローラーでもあるapplication-controllerについて

先ほど記載したと通り、application-controllerはカスタムコントローラーとしても機能します。

本記事では詳しく言及しませんが、カスタムコントローラーの仕組みやCRDとの関係については、以下の記事が非常に参考になりました🙇🏻‍

(3) application-controllerによるマニフェスト取得

application-controllerは、repo-serverからリポジトリマニフェストを取得します。

取得したマニフェストは、repo-serverのサイドカーであるcmp-serverが作成したものです。

(4) application-controllerによるヘルスチェック

application-controllerは、プロダクト用Clusterをヘルスチェックします。

application-controllerには、gitops-engineパッケージが内蔵されており、これはヘルスチェックからデプロイまでの基本的な処理を実行します。


▶ gitops-engineパッケージについて

gitops-engineは、ArgoCDのデプロイに必要な処理を揃えたパッケージです。

執筆時点 (2023/05/02) の最新バージョン `v0.7.0` では以下のディレクトリからなります👇
🐱 gitops-engine/
├── 📂 pkg
│    ├── cache
│    ├── diff   # リポジトリとClusterの間のマニフェストの差分を検出する。ArgoCDのDiff機能に相当する。
│    ├── engine # 他のパッケージを使い、GitOpsの一連の処理を実行する。
│    ├── health # Clusterのステータスをチェックする。ArgoCDのヘルスチェック機能に相当する。
│    ├── sync   # Clusterにマニフェストをデプロイする。ArgoCDのSync機能に相当する。
│    └── utils  # 他のパッケージに汎用的な関数を提供する。
│
...
マイクロサービスアーキテクチャ

(5) application-controllerによるマニフェスト差分検出

application-controllerは、プロダクト用Clusterのマニフェストと、repo-serverから取得したマニフェストの差分を検出します。

ここで、kubectl diffコマンドの実行が自動化されています。

(6) application-controllerによる処理結果保管

application-controllerは、処理結果をredis-serverに保管します。

redis-serverは、Applicationやリポジトリのコミットの単位で、application-controllerの処理結果を保管しています。

$ kubectl exec -it argocd-redis-server \
    -n foo \
    -- sh -c "redis-cli --raw"

127.0.0.1:6379> keys *

...

app|resources-tree|<Application名>|<キャッシュバージョン>
cluster|info|<プロダクト用ClusterのURL>|<キャッシュバージョン>
git-refs|<マニフェスト/チャートリポジトリのURL>|<キャッシュバージョン>
mfst|app.kubernetes.io/instance|<Application名>|<最新のコミットハッシュ値>|<デプロイ先Namespace>|*****|<キャッシュバージョン>

...

(7) application-controllerによるマニフェストデプロイ

application-controllerは、Applicationの操作に応じて、Clusterにマニフェストをデプロイします。

ここで、kubectl applyコマンドの実行が自動化されています。


▶ application-controllerがマニフェストを操作した証拠について

Kubernetesリソースのマニフェストには、metadata.managedFieldsキーがあり、何がそのマニフェストを作成/変更したのかを確認できます。

実際にマニフェストを確認してみると、確かにapplication-controllerがマニフェストを作成/変更してくれたことを確認できます。
apiVersion: apps/v1
kind: Deployment
metadata:
  managedFields:
    # ArgoCDのapplication-controllerによる管理
    - manager: argocd-application-controller
      apiVersion: apps/v1
      # kube-apiserverに対するリクエスト内容
      operation: Update
      time: "2022-01-01T16:00:00.000Z"
      # ArgoCDのapplication-controllerが管理するマニフェストのキー部分
      fields: ...


05. dex-server

dex-serverとは

インフラレイヤーにあるdex-serverです。

SSO (例:OAuth 2.0SAML、OIDC) を採用する場合、argocd-serverの代わりに認可リクエストを作成し、またIDプロバイダー (例:GitHub、Keycloak、AWS Cognito、Google Auth、など) に送信します。

これにより、argocd-server上の認証フェーズをIDプロバイダーに委譲できます。


▶ dex-serverの必要性について

dex-serverを使わずに、argocd-serverからIDプロバイダーに認可リクエストを直接的に送信することもできます。

執筆時点 (2023/05/02) で、argocd-serverは特にOIDCの認可リクエストを作成できるため、ログイン要件がOIDCの場合は、dex-serverを必ずしも採用してなくもよいです。

言い換えれば、その他のSSO (例:OAuth 2.0SAML) を使用する場合は、dex-serverを採用する必要があります👍

仕組み

argocd_architecture_dex-server.png

(1) プロダクト用Cluster管理者のログイン

プロダクト用Cluster管理者がダッシュボード (argocd-server) にSSOを使用してログインしようとします。

(2) IDプロバイダーへの認証フェーズ委譲

argocd-serverは、認証フェーズをIDプロバイダーに委譲するために、dex-serverをコールします。


▶ 認証フェーズの委譲について

argocd-serverの認証認可処理は、AuthN (認証) と AuthZ (認可) から構成されています。

今回は認証フェーズを委譲した場合で仕組みを解説していますが、反対に委譲しなかった場合、このAuthNでArgoCD上で定義したユーザーやグループを認証することになります👍

argocd_auth_architecture

(3) dex-serverによる認可リクエスト作成

dex-serverは、認可リクエストを作成します。

認可リクエストに必要な情報は、ConfigMap (argocd-cm) で設定しておく必要があります。

argocd-cmのざっくりした実装例は以下の通りです👇

ここでは、IDプロバイダーをGitHubとし、認可リクエストに必要なクライアントIDとクライアントシークレットを設定しています。

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: foo
  name: argocd-cm
data:
  dex.config: |
    connectors:
      - type: github
        id: github
        name: GitHub SSO
        config:
          clientID: *****
          clientSecret: *****
        # dex-serverが認可レスポンスを受信するURLを設定する
        redirectURI: https://example.com/api/dex/callback


▶ dex-serverの設定について

dex.configキー配下の設定方法は、dexのドキュメントをみるとよいです👍

(4) dex-serverによる認可リクエスト送信

dex-serverは、前の手順で作成した認可リクエストをIDプロバイダーに送信します。

(5) IDプロバイダーによる認証フェーズ実施

IDプロバイダー側でSSOの認証フェーズを実施します。

IDプロバイダーは、コールバックURL (<ArgoCDのドメイン名>/api/dex/callback) を指定して、認可レスポンスを送信します。

認可レスポンスは、argocd-serverを介して、dex-serverに届きます。


▶ dex-serverのコールバックURLについて

IDプロバイダー側のコールバックURLの設定で、dex-serverのエンドポイントを指定する必要があります。

例えばGitHubをIDプロバイダーとする場合、 Developer settingsタブ でSSOを設定する必要があり、この時にAuthorization callback URLという設定箇所があるはずです👍🏻

(6) argocd-serverによる認可フェーズ実施

argocd-serverは、AuthZで認可フェーズを実施します。

ConfigMap (argocd-rbac-cm) を参照し、IDプロバイダーから取得したユーザーやグループに、ArgoCD系カスタムリソースに関する認可スコープを付与します。

ざっくりした実装例は以下の通りです👇

ここでは、developerロールにはdevというAppProjectに属するArgoCD系カスタムリソースにのみ、またmaintainerロールには全てのAppProjectの操作を許可しています。

またこれらのロールを、IDプロバイダーで認証されたグループに紐づけています。

特定のArgoCD系カスタムリソースのみへのアクセスを許可すれば、結果として特定のClusterへのデプロイのみを許可したことになります👍

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: foo
data:
  # デフォルトのロール
  policy.default: role:developer
  policy.csv: |
    p, role:developer, *, *, dev/*/*, allow
    p, role:maintainer, *, *, dev/*/*, allow
    p, role:maintainer, *, *, prd/*/*, allow

    g, developers, role:developer
    g, maintainers, role:maintainer
  scopes: "[groups]"


▶ AppProjectの認可定義の記法について

ConfigMap (argocd-rbac-cm) の認可スコープの定義には、 Casbin の記法を使用します。

今回の実装例で使用したp (パーミッション) とg (グループ) では、以下を記法を使用できます👍
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    # ロールとArgoCD系カスタムリソースの認可スコープを定義する
    p, role:<ロール名>, <Kubernetesリソースの種類>, <アクション名>, <AppProject名>/<ApplicationのNamespace名>/<Application名>, <許否>

    # 認証済みグループにロールを紐付ける
    g, <グループ名>, role:<ロール名>
  scopes: "[groups]"


06. argocd-server (argocd-apiserver)

argocd-serverとは

最後に、インフラレイヤーにあるargocd-serverです。

『argocd-apiserver』とも呼ばれます。

みんながよく知るArgoCDのダッシュボードです。

また、ArgoCDのAPIとしても機能し、他のコンポーネントと通信します🦄

仕組み

argocd_architecture_argocd-apiserver.png

(1) application-controllerによるヘルスチェック

application-controllerは、プロダクト用Clusterをヘルスチェックします。

(2) application-controllerによるマニフェスト差分検出

application-controllerは、プロダクト用Clusterのマニフェストと、ポーリング対象のリポジトリマニフェストの差分を検出します。

(3) application-controllerによる処理結果保管

application-controllerは、処理結果をredis-serverに保管します。

(4) application-controllerによる処理結果取得

argocd-serverは、redis-serverから処理結果を取得します。

(5) プロダクト用Cluster管理者のログイン

プロダクト用Cluster管理者がダッシュボード (argocd-server) にSSOを使用してログインしようとします。

(6) Ingressコントローラーによるルーティング

Ingressコントローラーは、Ingressのルーティングルールを参照し、argocd-serverにルーティングします。

(7) IDプロバイダーへの認証フェーズ委譲

argocd-serverは、ログイン時にIDプロバイダーに認証フェーズを委譲するために、dex-serverをコールします。

(8) IDプロバイダーによる認証フェーズ実施

IDプロバイダー上で認証フェーズが完了します。

argocd-serverは、ConfigMap (argocd-rbac-cm) を参照し、プロダクト用Cluster管理者に認可スコープを付与します。

(9) argocd-serverによる認可フェーズ実施

argocd-serverは、認可スコープに応じて、プロダクト用Cluster管理者がApplicationを操作可能にします。


▶ Namespacedスコープモードについて

今回の図のように、単一のArgoCD用Clusterで複数プロダクトのApplicationを管理する場合、Namespaceを単位としたテナント分割を設定した方がよいです。

その場合、ArgoCD本体をNamespacedスコープモードに設定する必要があります。

Namespacedスコープモードの場合、以下の設定が不要です。
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  namespace: foo
data:
  # 設定してはダメ
  # application.namespaces: "*" # 全てのNamespaceを許可する。
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: dev-foo-project
  namespace: foo
spec:
  # 設定してはダメ
  # sourceNamespaces:
  #  - "foo"
これらにより、fooのNamespaceに属するArgoCDは、他のNamespaceにはアクセスできなくなります👍

(10) application-controllerによるマニフェストデプロイ

プロダクト用Cluster管理者は、ダッシュボード (argocd-server) を使用して、ClusterにマニフェストをSyncします。

この時、Applicationを介してapplication-controllerを操作し、マニフェストをデプロイします。

図では、App-Of-Appsパターンを採用したと仮定しています👨‍👩‍👧‍👦


▶ App-Of-Appsパターンについて

ArgoCDにはApp-Of-Appsパターンというデザインパターンがあります。

これは、Applicationを階層的に作成するパターンであり、最下層のApplication配下のマニフェストをより疎結合に管理できます✌️

例えば以下の画像の通り、最上位のApplication配下に、チーム別の親Applicationを配置します (アプリチームの親Application、インフラチームのそれ) 。

その後、両方のApplication配下にさらにチャート別に最下層の子Applicationを配置し、チャートのデプロイを管理します。

アプリチーム最下層の子Applicationではアプリコンテナのチャート、インフラチームの子Applicationでは監視/ネットワーク/ハードウェアリソース管理系のチャートを管理します👍

root-application


07. アーキテクチャのまとめ

今までの全ての情報をざっくり整理して簡略化すると、ArgoCDは以下の仕組みでマニフェストをデプロイすることになります👇

argocd_architecture.png


08. おわりに

ArgoCDによるデプロイの仕組みの仕組みをもりもり布教しました。

ArgoCDは、UIが使いやすく、仕組みの詳細を知らずとも比較的簡単に運用できるため、ユーザーフレンドリーなツールだと思っています。

もしArgoCDを使わずにマニフェストをデプロイしている方は、ArgoCDの採用をハイパー・ウルトラ・アルティメットおすすめします👍


謝辞

ArgoCDの設計にあたり、以下の方に有益なプラクティスをご教授いただきました。

この場で感謝申し上げます🙇🏻‍


記事関連のおすすめ書籍