TECHSTEP

ITインフラ関連の記事を公開してます。

ArgoCD ApplicationSetを動かしてみる

今回はArgoCDをより拡張する機能を持つ ApplicationSetを動かしてみます。

ApplicationSetとは

ApplicationSetは、Argo Projectに含まれるプロダクトの一つです。ArgoCDを利用する複数のユースケースにおいて、Applicationリソースの管理を自動化しつつ、柔軟に管理する方法を提供するControllerになります。

ArgoCDはApplicationというリソースの中で、利用するGitリポジトリマニフェストファイルの場所デプロイ先のクラスターなどを指定します。ArgoCDを利用する環境の規模が大きくなったりすると、管理をする上で面倒になるケースが出てきます。

例えば、1つのアプリケーションを提供する環境が複数のKubernetesクラスターで構成されており、それぞれのクラスターに同じアプリケーションをデプロイしようとすると、クラスターごとにApplicationリソースを作成しなければなりません。 また、monorepoのような、1つのリポジトリ中で複数のアプリケーションを管理する場合も、それぞれのアプリケーションごとに Application リソースを用意しなくてはなりません。

ApplicationSetは、上記のような悩みを解消するため、複数のクラスターやGitリポジトリを効率的に扱うための機能を提供します。

Generator

ApplicationSetは Generator というパラメータを含みます。 Generatorクラスターの名称やURL、Gitリポジトリなどのパラメータを設定することで、それぞれに対応した Application マニフェストを生成し、複数のクラスターやGitリポジトリを使用したArgoCDによるアプリケーション管理を実現します。

Generator にはいくつか種類があり、以下の通りになります。

  • List generator : クラスターの名称とアクセス先URLなどのリストを生成する
  • Cluster generator : ArgoCDに登録されているクラスター情報を生成する。ArgoCDに登録されたクラスターは自動的に検知する。
  • Git generator : Gitリポジトリリポジトリ中のフォルダ構成などの情報を生成する。
  • Matrix generator : 2種類のgeneratorを組み合わせて使う。
  • SCM Provider generator : GitHub/GitlabのようなSCMの提供するAPIを使用し、Organization中のリポジトリを探索してパラメータを生成する
  • Cluster Decision Resource generator : ArgoCDに登録されたクラスターのリストを生成する。Custom Resourceのインターフェイスとして利用し、事前にConfigMapに定義したCustom Resourceを作成する。

※ 参考:ApplicationSet Controller - Generators

ユースケース

ApplicationSetでは、いくつかのユースケースを想定しています。

Cluster add-on

Cluster add-onとは、Kubernetesクラスターに機能を追加・拡張するようなプロダクトのことを総称しており、例えば Prometheus OperatorArgo workflow などを指しています。これらは各クラスターに共通して導入することが多いため、ApplicationSetを生かすことのできるユースケースの一つとなります。

Cluster add-onはクラスターレベルの権限を必要とするため、その管理はインフラ管理者が担うことが多くなります。Add-onを導入するクラスター数の増減が多くなるほど管理コストは高くなります。またクラスターごとに利用用途が異なる(テスト・本番環境など)と、add-onの設定を変更する場合も出てくるため、より複雑になります。

ApplicationSetでは複数のGeneratorを提供しており、それぞれ用途に応じて使い分けることができます。

  • List generator : デプロイするadd-onごとにApplicationSetを用意し、デプロイ先のクラスターリストを定義することで管理できます。List generatorの場合クラスターの増減に応じてApplicationSetの手動更新が必要となります。

  • Cluster generator : デプロイするadd-onごとにApplicationSetを用意します。Cluster generatorはArgoCDに登録されたクラスターを自動で検知するため、クラスターの変更に合わせた自動更新が可能となります。

  • Git generator : Git generatorの files というfieldを使い、クラスターのリストをJSONファイルで定義しておきます。このJSONファイルを更新することでクラスター情報を更新し、add-onをデプロイすることができます。また directories というfieldを使うと、例えばクラスターごとに対応するディレクトリを用意しておき、ディレクトリ名とクラスター名を一致させておくと、ディレクトリの更新に合わせてクラスター側を更新することができます。

Monorepo

あるプロジェクトでMonorepoを採用している場合、一つのGitリポジトリにすべてのコードが格納されます。Monorepoの場合は複数チームが共通の基準に従う必要があり、それらを促進・強制するためにも、自動化が重要になってきます(参考リンク)。

Monorepoを採用する場合、Kubernetesクラスターの管理者は、1つのリポジトリからクラスター全体の状態を管理することになります。そのため、リポジトリ中のマニフェストファイルが更新されれば、その変更は即座にクラスターへと反映されるべきです。

Git generator を利用することで、monorepoを採用するプロジェクトの管理者をサポートすることが可能となります。directories fieldでアプリケーション個別に分かれたサブディレクトリを指定することが可能です。また files fieldを使うと、JSONメタデータを含むファイルを参照し、そこに定義されたアプリケーションをデプロイすることも可能です。

Self-service of ArgoCD Applications on multi-tenant cluster

開発者が自らArgoCDの Applicationを開発・利用する場合、app-of-appsというパターンと組み合わせ、クラスター管理者がPRを通じてマニフェストファイルをチェックし、デプロイの許可を行う、というケースがあります。Applicationには project cluster など重要なパラメータがあり、この設定を誤ってしまうと別のArgoCD Projectやクラスターが更新されるため、レビュアーの重要度が上がってしまいます。そのため管理者としては、開発者の変更できるパラメータを絞り、重要なものは変更できないようにしておきたくなります。

ApplicationSetでは、Git generatorと組み合わせた代替手段を提供しています。ApplicationSetの template filedに、 project destination などの情報をべた書きして固定し、 source 部分のみ Git generatorで生成するようにしておきます。開発者にはGit generator部分のみ更新してもらうことで、アプリケーションの更新先をある程度コントロールすることが可能になります。

※参考:ApplicationSet Controller - Use Cases

ApplicationSetを動かしてみる

ここから実際にApplicationSetを動かしてみます。今回はひとまず動かしてみるという目的で、いくつかのパターンで動かしました。

基本的にはドキュメント通りに動かすだけですが、1点だけ気を付けるとすると、ApplicationSet Controllerと同じNamespaceに ApplicationSetリソースを作成する必要があります。

※参考:ApplicationSet Controller - How ApplicationSet controller interacts with Argo CD

今回の検証環境は以下の通りです。

インストール

ApplicationSetはArgoCDと一緒に利用する必要があります。今回はArgoCDとApplicationSetを別々に導入しました。ArgoCDの導入はこちらのページに記載されているので割愛します。ArgoCD導入後は、ArgoCDと同じNamespace(今回は argocd )にApplicationSet Controllerなどのリソースをデプロイすれば、準備は完了です。

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/applicationset/v0.2.0/manifests/install.yaml

※参考:ApplicationSet Controller - Getting Started

Local cluster

まずはArgoCDの導入されたクラスター上に、ApplicationSetを使ってリソースを作成します。

List generator

使ったマニフェストファイルはこちらです。 spec.generator.list 部分にクラスターの名称とURLを指定します。そうすると spec.template 配下にある {{cluster}} {{url}} 部分にパラメータが設定され、それぞれのパラメータに応じた Applicationリソースが作成されます。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      - cluster: in-cluster
        url: https://kubernetes.default.svc
  template:
    metadata:
      name: '{{cluster}}-guestbook'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps.git
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{url}}'
        namespace: default

※ 参考:ApplicationSet Controller - List Generator

上記ファイルをデプロイすると、以下のようにリソースが作成されます。

$ kubectl apply -f list-applicationset.yaml 
applicationset.argoproj.io/guestbook created

$ kubectl get applicationset -n argocd
NAME        AGE
guestbook   16s


$ kubectl get app -n argocd
NAME                   SYNC STATUS   HEALTH STATUS
in-cluster-guestbook   OutOfSync     Missing

$ argocd app list
NAME                  CLUSTER                         NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                                 PATH       TARGET
in-cluster-guestbook  https://kubernetes.default.svc  argocd     default  OutOfSync  Missing  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD  

あとは argocd app sync コマンドなどでSyncすれば、テスト用のアプリケーションがデプロイされます。もちろん templatespec.syncPolicy: automated を追加すればして自動Syncを有効にすれば、ApplicationSetデプロイ後にテスト用アプリのリソースは作られます。

なお、ApplicationSet controllerのログは以下の通り。

ApplicationSet作成時にControllerに出力されるログ

$ kubectl logs -n argocd argocd-applicationset-controller-5558c458d5-n52d9 -f
2021-11-23T01:58:32.411Z        INFO    setup   ApplicationSet controller v0.2.0 using namespace 'argocd'       {"namespace": "argocd", "COMMIT_ID": "2fb043581b8692c84205075494acab6f4b5aef2d"}
2021-11-23T01:58:33.118Z        INFO    controller-runtime.metrics      metrics server is starting to listen    {"addr": ":8080"}
2021-11-23T01:58:33.119Z        INFO    setup   Starting manager
2021-11-23T01:58:33.219Z        INFO    controller-runtime.manager.controller.applicationset    Starting EventSource   {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "source": "kind source: /, Kind="}
2021-11-23T01:58:33.220Z        INFO    controller-runtime.manager.controller.applicationset    Starting EventSource   {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "source": "kind source: /, Kind="}
2021-11-23T01:58:33.220Z        INFO    controller-runtime.manager.controller.applicationset    Starting EventSource   {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "source": "kind source: /, Kind="}
2021-11-23T01:58:33.220Z        INFO    controller-runtime.manager.controller.applicationset    Starting Controller    {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet"}
2021-11-23T01:58:33.219Z        INFO    controller-runtime.manager      starting metrics server {"path": "/metrics"}
2021-11-23T01:58:33.421Z        INFO    controller-runtime.manager.controller.applicationset    Starting workers       {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "worker count": 1}




# ApplicationSet作成時
time="2021-11-23T02:08:33Z" level=info msg="Alloc=6126 TotalAlloc=18354 Sys=73297 NumGC=9 Goroutines=49"
time="2021-11-23T02:11:09Z" level=info msg="generated 1 applications" generator="{0xc0002b3080 <nil> <nil> <nil> <nil> <nil>}"
time="2021-11-23T02:11:09Z" level=info msg="Starting configmap/secret informers"
time="2021-11-23T02:11:09Z" level=info msg="Configmap/secret informer synced"
time="2021-11-23T02:11:09Z" level=info msg="created Application" app=in-cluster-guestbook appSet=guestbook
2021-11-23T02:11:09.581Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"ApplicationSet","namespace":"argocd","name":"guestbook","uid":"35d9488c-837a-46c0-a029-014890b804f9","apiVersion":"argoproj.io/v1alpha1","resourceVersion":"1618215"}, "reason": "created", "message": "created Application \"in-cluster-guestbook\""}
time="2021-11-23T02:11:09Z" level=info msg="end reconcile" requeueAfter=0s
time="2021-11-23T02:11:09Z" level=info msg="generated 1 applications" generator="{0xc00092cdc0 <nil> <nil> <nil> <nil> <nil>}"
time="2021-11-23T02:11:09Z" level=info msg="unchanged Application" app=in-cluster-guestbook appSet=guestbook
2021-11-23T02:11:09.607Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"ApplicationSet","namespace":"argocd","name":"guestbook","uid":"35d9488c-837a-46c0-a029-014890b804f9","apiVersion":"argoproj.io/v1alpha1","resourceVersion":"1618215"}, "reason": "unchanged", "message": "unchanged Application \"in-cluster-guestbook\""}
time="2021-11-23T02:11:09Z" level=info msg="end reconcile" requeueAfter=0s
time="2021-11-23T02:11:11Z" level=info msg="generated 1 applications" generator="{0xc0007f22c0 <nil> <nil> <nil> <nil> <nil>}"
time="2021-11-23T02:11:11Z" level=info msg="unchanged Application" app=in-cluster-guestbook appSet=guestbook
2021-11-23T02:11:11.713Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"ApplicationSet","namespace":"argocd","name":"guestbook","uid":"35d9488c-837a-46c0-a029-014890b804f9","apiVersion":"argoproj.io/v1alpha1","resourceVersion":"1618215"}, "reason": "unchanged", "message": "unchanged Application \"in-cluster-guestbook\""}
time="2021-11-23T02:11:11Z" level=info msg="end reconcile" requeueAfter=0s

Cluster generator

マニフェストファイルは以下の通りです。

Cluster generatorはArgoCDに登録されているクラスターを検知してパラメータを生成するため、デプロイ先のクラスターを指定しない場合は特に設定は必要ありません。 spec.template{{name}} {{server}} には、ArgoCDで登録されているクラスター情報がそのまま使われます(ここではそれぞれ in-cluster https://kubernetes.default.svc)。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - clusters: {} 
  template:
    metadata:
      name: '{{name}}-guestbook'
    spec:
      project: "default"
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps/
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{server}}'
        namespace: default

※ 参考:ApplicationSet Controller - Cluster Generator

こちらもデプロイすれば、先ほどと同様リソースが作成されます。

$ kubectl apply -f cluster-applicationset.yaml 
applicationset.argoproj.io/guestbook created

$ kubectl get appset -n argocd
NAME        AGE
guestbook   8s

$ kubectl get apps -n argocd
NAME                   SYNC STATUS   HEALTH STATUS
in-cluster-guestbook   OutOfSync     Missing

あとはSyncすればPodも作成されます。

$ argocd app sync in-cluster-guestbook
$ kubectl get pods 
NAME                            READY   STATUS    RESTARTS   AGE
guestbook-ui-85985d774c-t7mxr   1/1     Running   0          13s

なお、ApplicationSetを削除すると、関連するApplication / Podなどのリソースがすべて削除されます。

$ kubectl delete -f cluster-applicationset.yaml 
applicationset.argoproj.io "guestbook" deleted

$ kubectl get appset -n argocd
No resources found in argocd namespace.

$ kubectl get app -n argocd
No resources found in argocd namespace.

$ kubectl get pods
No resources found in default namespace.

Git generator

マニフェストファイルはこちらです。

ここでは directories fieldで使用するマニフェストファイルの場所を指定します。また template にある path.basename には、path に指定したディレクトリの配下にあるサブディレクトリの名称が入ります(ここでは argo-workflows prometheus-operator)。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: https://github.com/argoproj-labs/applicationset.git
      revision: HEAD
      directories:
      - path: examples/git-generator-directory/cluster-addons/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj-labs/applicationset.git
        targetRevision: HEAD
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'

※ 参考:ApplicationSet Controller - Git Generator

こちらも同様にデプロイすれば、以下のようにリソースは作成されます。ここではadd-onをデプロイするためにリポジトリを変更しており、2つのApplicationリソースが作成されます。

$ kubectl apply -f git-applicationset.yaml 
applicationset.argoproj.io/cluster-addons created

$ kubectl get appset -n argocd
NAME             AGE
cluster-addons   25s

$ kubectl get app -n argocd
NAME                  SYNC STATUS   HEALTH STATUS
argo-workflows        OutOfSync     Missing
prometheus-operator   OutOfSync     Missing

こちらもSyncすればリソースは作成されますが、今回はNamespaceがサブディレクトリ名と一致する形式のため、事前にNamespaceを作成してからSyncします。

$ kubectl create ns argo-workflows

$ argocd app sync argo-workflows

$ kubectl get pods -n argo-workflows
NAME                                   READY   STATUS    RESTARTS   AGE
argo-server-5ddd56c954-mkgkg           1/1     Running   0          31s
workflow-controller-574596cc9f-2wzmk   1/1     Running   0          31s

External cluster

次は外部クラスターに向けてのデプロイです。今回は eksctl コマンドで別のEKSクラスターを作成し、その後ArgoCDにクラスターを追加登録します。

# external clusterの作成
$ eksctl create cluster -f eks-clusterconfig-2.yaml

$ eksctl get clusters
2021-11-23 15:22:29 []  eksctl version 0.69.0
2021-11-23 15:22:29 []  using region ap-northeast-1
NAME                    REGION          EKSCTL CREATED
eks-cluster-argodst     ap-northeast-1  True #追加したクラスター
eks-cluster-argosrc     ap-northeast-1  True

# 登録するクラスターの確認
$ kubectl config get-contexts

# クラスターの登録
$ argocd cluster add <external cluster name>

※ 参考:ArgoCD Docs - Argocd cluster add

クラスターを登録すると以下のようにSecretリソースが作成されます。なおSecretリソース名にあるURL先のクラスターは、既に削除されています。

$ kubectl get secret -n argocd
NAME                                                                                       TYPE                                  DATA   AGE
argocd-application-controller-token-22xzv                                                  kubernetes.io/service-account-token   3      4h39m
argocd-applicationset-controller-token-v84pl                                               kubernetes.io/service-account-token   3      4h29m
argocd-dex-server-token-g76wx                                                              kubernetes.io/service-account-token   3      4h39m
argocd-initial-admin-secret                                                                Opaque                                1      4h38m
argocd-redis-token-m94v5                                                                   kubernetes.io/service-account-token   3      4h39m
argocd-secret                                                                              Opaque                                5      4h39m
argocd-server-token-rqn9r                                                                  kubernetes.io/service-account-token   3      4h39m
# "cluster-"で始まるSecretが作成される
cluster-c68e805bffb0dd155295b27b15cbf8ac.gr7.ap-northeast-1.eks.amazonaws.com-3093402059   Opaque                                3      90s
default-token-qc648                                                                        kubernetes.io/service-account-token   3      4h39m

List generator

ここでは以下のマニフェストファイルを使用します。これは先ほどのマニフェストファイルに外部クラスター情報を追加したものです。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      - cluster: in-cluster
        url: https://kubernetes.default.svc
      # 追加
      - cluster: external-cluster
        url: https://C68E805BFFB0DD155295B27B15CBF8AC.gr7.ap-northeast-1.eks.amazonaws.com
  template:
    metadata:
      name: '{{cluster}}-guestbook'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps.git
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{url}}'
        namespace: default

こちらをデプロイするとApplicationが作成されます。今回は2つのクラスターに対してApplication リソースが作られ、その名称はクラスターと対応しています。

$ kubectl apply -f list-applicationset.yaml 
applicationset.argoproj.io/guestbook created

$ kubectl get appset -n argocd
NAME        AGE
guestbook   14s

$ kubectl get app -n argocd
NAME                         SYNC STATUS   HEALTH STATUS
external-cluster-guestbook   OutOfSync     Missing
in-cluster-guestbook         OutOfSync     Missing

あとはSyncすればデプロイは完了です。

$ argocd app sync external-cluster-guestbook

$ argocd app list
NAME                        CLUSTER                                                                        NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO
                                    PATH       TARGET
external-cluster-guestbook  https://C68E805BFFB0DD155295B27B15CBF8AC.gr7.ap-northeast-1.eks.amazonaws.com  default    default  Synced     Healthy  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD
in-cluster-guestbook        https://kubernetes.default.svc                                                 default    default  OutOfSync  Missing  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD

# 操作対象のクラスターを切り替える
$ kubectl config use-context <external cluster name>

$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
guestbook-ui-85985d774c-v7rv6   1/1     Running   0          4m34s

Cluster generator

ここでは外部クラスターのみにデプロイするよう、条件を追加しています。ArgoCDにクラスターを登録した際に作られるSecretには argocd.argoproj.io/secret-type=cluster というタグが付与されており、これを含むクラスターだけを対象とします。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - clusters:
    # 追加
      selector:
        matchLabels:
          argocd.argoproj.io/secret-type: cluster
  template:
    metadata:
      name: 'external-guestbook'
    spec:
      project: "default"
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps/
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{server}}'
        namespace: default

なお spec.template.metadata.name の名称を {{name}}-guestbook から external-guestbook に変更していますが、クラスターの名称が長すぎるとSync時に以下のようなエラーが発生するため変更しています。

# エラーの例
$ argocd app sync 

(中略)

Deployment.apps "guestbook-ui" is invalid: metadata.labels: Invalid value: "<cluster name>": must be no more than 63 characters

デプロイ後は以下の通りです。

$ kubectl apply -f cluster-applicationset.yaml

$ argocd app sync external-guestbook

$ kubectl config use-context <external cluster>

$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
guestbook-ui-85985d774c-v4xqc   1/1     Running   0          36s

Matrix generator

Matrix generatorは2種類のgeneratorを組み合わせるgeneratorです。今回は Cluster generator と Git generator を組み合わせたパターンにしています。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-git
  namespace: argocd
spec:
  generators:
    - matrix:
        generators:
          - git:
              repoURL: https://github.com/argoproj-labs/applicationset.git
              revision: HEAD
              directories:
              - path: examples/matrix/cluster-addons/*
          - clusters:
              selector:
                matchLabels:
                  argocd.argoproj.io/secret-type: cluster
  template:
    metadata:
      name: '{{path.basename}}-external'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj-labs/applicationset.git
        targetRevision: HEAD
        path: '{{path}}'
      destination:
        server: '{{server}}'
        namespace: default

※ 参考:ApplicationSet Controller - Matrix Generator

デプロイ後は以下の通りです。

$ kubectl apply -f matrix-applicationset.yaml
applicationset.argoproj.io/cluster-git created

$ kubectl get appset -n argocd
NAME          AGE
cluster-git   11s

$ kubectl get app -n argocd
NAME                           SYNC STATUS   HEALTH STATUS
argo-workflows-external        OutOfSync     Missing
prometheus-operator-external   OutOfSync     Missing