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

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

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



01. はじめに

argocd_rocket.png

ロケットに乗るArgoくんのツラが腹立つわー。

さて最近の業務で、全プロダクトが共通基盤として使用するArgoCDとAWS EKS Clusterのアーキテクチャをリプレイスしています。

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

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

現在に至るまでArgoCDのアーキテクチャには変遷があり、今回紹介するのは執筆時点 (2023/05/02) 時点で最新の 2.6 系のアーキテクチャです。

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

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


02. 概要

アーキテクチャ

▼ レイヤー

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

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

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

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

argocd_architecture_layer.png

↪️:argo-cd/components.md at master · argoproj/argo-cd · GitHub

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

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

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

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

↪️:モノリスからマイクロサービスへ ―モノリスを進化させる実践移行ガイド | Sam Newman, 島田 浩二 |本 | 通販 | Amazon

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

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

そのため、これはマイクロサービス間の依存方向を視覚化するために有効なUML図です🙆🏻‍
↪️:Component Diagrams - Code With Engineering Playbook

コンポーネント

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

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プロバイダーに委譲できます。

↪️:Amazon | GitOps and Kubernetes: Continuous Deployment with Argo CD, Jenkins X, and Flux | Yuen, Billy, Matyushentsev, Alexander, Ekenstam, Todd, Suen, Jesse | PMP Exam

仕組み

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

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

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

argocd_architecture_introduction.png

【1】

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

【2】

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

【3】

application-controllerは、デプロイ先Clusterの現状を確認します。

【4】

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

【5】

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

【6】

デプロイ先Cluster管理者は、argocd-serverにログインしようとします。

【7】

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

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

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

【8】

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

【9】

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

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

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

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

【10】

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】

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: {}

↪️:Custom Tooling - Argo CD - Declarative GitOps CD for Kubernetes

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

そのため、サイドカーのcmp-serverを採用しない場合は、これらのツールをインストールする必要はありません。

反対に、cmp-serverを採用する場合はインストールが必要になります🙇🏻

公式が、推奨バージョンを含むサイドカー用イメージを公開してくれるのを待ちたいところなのですが、リポジトリからバージョンを取得することもできなくはないです🙆🏻‍
# ArgoCDのコンテナイメージに内蔵されたHelmの推奨バージョンを取得する
$ curl -s https://raw.githubusercontent.com/argoproj/argo-cd/<バージョンタグ>/hack/tool-versions.sh \
    | grep helm3_version | sed -e 's/^[^=]*=//'
↪️:argo-cd/tool-versions.sh at master · argoproj/argo-cd · GitHub

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

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

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

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

【2】

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

↪️:Declarative Setup - Argo CD - Declarative GitOps CD for Kubernetes

【3】

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

↪️:custom repo-server - where is the local cache kept? · argoproj/argo-cd · Discussion #9889 · GitHub

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は、Volume上のUnixドメインソケットを介して、サイドカー (cmp-server) によるプラグインの実行をコールします。

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

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

【5】

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

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

cmp-serverに軽量なコンテナイメージを使用していると、/etc/sslディレクトリ (OSによる) にSSL証明書が無く、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リクエストを送信する必要があるため、SSL証明書をマウントする
        - 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、Kustomize) でマニフェストを作成する時に、これのプラグイン (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
    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で異なるファイル名とし、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: {}
ArgoCDのv2.6では、ConfigManagementPluginのマニフェスト/home/argocd/cmp-server/configディレクトリに、plugin.yamlの名前でマウントしないといけません。

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

今後のアップグレードで改善される可能性がありますが、v2.6では、ConfigManagementPluginの数だけcmp-serverが必要になってしまいます🙇🏻‍
今回は詳しく言及しませんが、クラウドプロバイダーのSecretストア (例:AWS SecretManager、Google SecretManager、など) の変数を使用する場合は、Secretのデータ注入ツールのプラグイン (特にargocd-vault-plugin) は必須ではありません。

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

これらは、クラウドプロバイダーから変数を取得し、これをSecretにデータとして注入してくれます🙇🏻‍
↪️:How to manage Kubernetes secrets with GitOps? | Akuity


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の管理者は、ClusterにArgoCD系のカスタムリソース (例:Application、AppProject、など) をデプロイします。

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

この時、argo-helmを使用すると簡単にArgoCDのマニフェストを作成できます。
️↪️:GitHub - argoproj/argo-helm: ArgoProj Helm Charts



ただし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もちゃんと更新してくれます👍🏻
️↪️:Helm | Custom Resource Definitions

【2】

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

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

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

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

【3】

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

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

【4】

application-controllerは、デプロイ先Clusterをヘルスチェックします。

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

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

執筆時点 (2023/05/02) では以下のディレクトリからなります👇
gitops-engine/
├── pkg
│   ├── cache
│   ├── diff   # リポジトリとClusterの間のマニフェストの差分を検出する。ArgoCDのDiff機能に相当する。
│   ├── engine # 他のパッケージを使い、GitOpsの一連の処理を実行する。
│   ├── health # Clusterのステータスをチェックする。ArgoCDのヘルスチェック機能に相当する。
│   ├── sync   # Clusterにマニフェストをデプロイする。ArgoCDのSync機能に相当する。
│   └── utils  # 他のパッケージに汎用的な関数を提供する。...
↪️:gitops-engine/design-top-down.md at master · argoproj/gitops-engine · GitHub

【5】

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

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

【6】

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の操作に応じて、Clusterにマニフェストをデプロイします。

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

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: ...
️↪️:Server-Side Apply | Kubernetes


05. dex-server

dex-serverとは

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

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

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

↪️:GitHub - dexidp/dex: OpenID Connect (OIDC) identity and OAuth 2.0 provider with pluggable connectors

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

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

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

️↪️:Overview - Argo CD - Declarative GitOps CD for Kubernetes

仕組み

argocd_architecture_dex-server.png

【1】

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

【2】

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

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

認証フェーズを委譲しない場合、このAuthNにて、ArgoCD上で定義したユーザーやグループを認証することになります👍

argocd_auth_architecture
️↪️:argo-cd/authz-authn.md at master · argoproj/argo-cd · GitHub

【3】

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.configキー配下の設定方法に関しては、dexのドキュメントをみると良いです👍
↪️:Dex

【4】

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

【5】

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

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

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

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

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

【6】

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, *, *, *, allow

    # IDプロバイダーで認証されたグループにロールを紐付ける。
    g, developers, role:developer
    g, maintainers, role:maintainer
  scopes: "[groups]"
ConfigMap (argocd-rbac-cm) の認可スコープの定義には、 Casbin の記法を使用します。

今回の実装例で使用したpgでは、以下を定義できます。

記号 説明 記法
p (パーミッション) ロールと認可スコープを定義する。 p, <ロール名> <Kubernetesリソースの種類> <アクション名> <AppProject名>/<Kubernetesリソースの識別名>
g (グループ) グループにロールを紐付ける。 g, <グループ名> <ロール名>
↪️:RBAC Configuration - Argo CD - Declarative GitOps CD for Kubernetes


06. argocd-server (argocd-apiserver)

argocd-serverとは

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

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

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

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

仕組み

argocd_architecture_argocd-apiserver.png

【1】

application-controllerは、デプロイ先Clusterをヘルスチェックします。

【2】

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

【3】

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

【4】

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

【5】

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

【6】

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

【7】

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

【8】

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

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

【9】

argocd-serverは、認可スコープに応じて、デプロイ先Cluster管理者がApplicationを操作できるようにします。

今回の図のように、単一の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`に属するArgoCDは、他のNamespaceにはアクセスできなくなります👍
↪️:Installation - Argo CD - Declarative GitOps CD for Kubernetes

【10】

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

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

図では、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の設計にあたり、@yaml_villager さんに有益なプラクティスをご教授いただきました。

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