An ApplicationSet is one resource that produces many Argo CD Applications.
It holds a template for the application, plus a generator that supplies one
set of parameters per application. Ten services across four clusters is forty
Application manifests written by hand, or one ApplicationSet whose
generator yields forty parameter sets. This post covers the three generators
that handle nearly everything I deploy: list, cluster and matrix, and
what each one produces.
The list generator
The parameters are written out by hand, one map per application, and every key becomes a variable in the template:
apiVersion: argoproj.io/v1alpha1kind: ApplicationSetmetadata: name: platform-toolsspec: generators: - list: elements: - appName: atlantis namespace: automation - appName: crossplane namespace: crossplane-system template: metadata: name: "{{ appName }}" spec: project: default source: targetRevision: main path: "charts/{{ appName }}" helm: valueFiles: ["{{ appName }}-values.yaml"] destination: server: https://kubernetes.default.svc namespace: "{{ namespace }}"The controller creates two Applications from it and keeps them in step with
the list. Remove an element and its application is deleted.
| Application | helm.valueFiles | destination.namespace |
|---|---|---|
atlantis | atlantis-values.yaml | automation |
crossplane | crossplane-values.yaml | crossplane-system |
The cluster generator
One parameter set per cluster Argo CD knows. Clusters are registered as
secrets labelled argocd.argoproj.io/secret-type: cluster, and the generator
reads them. It takes name and server from the secret’s fields, plus every
label and annotation as metadata.labels.* and metadata.annotations.*. A
selector narrows the set. Without one, every cluster is in. The cluster Argo
CD itself runs in is named in-cluster.
Here each cluster carries an env label, and the values file follows it:
apiVersion: argoproj.io/v1alpha1kind: ApplicationSetmetadata: name: metrics-serverspec: generators: - clusters: {} template: metadata: name: "metrics-server-{{ name }}" spec: project: default source: targetRevision: main path: charts/metrics-server helm: valueFiles: ["{{ metadata.labels.env }}-values.yaml"] destination: server: "{{ server }}" namespace: kube-systemWith two registered clusters, east labelled env: development and west
labelled env: production:
| Application | Cluster | helm.valueFiles |
|---|---|---|
metrics-server-east | east | development-values.yaml |
metrics-server-west | west | production-values.yaml |
The matrix generator
This combines two generators: every parameter set of the first, paired with every set of the second. A list of applications times the clusters is the common case:
spec: generators: - matrix: generators: - list: elements: - appName: external-dns - appName: cert-manager - clusters: {} template: metadata: name: "{{ appName }}-{{ name }}" spec: project: default source: targetRevision: main path: "kustomize/overlays/{{ appName }}" destination: server: "{{ server }}" namespace: "{{ appName }}"Two applications times the same two clusters is four Applications:
external-dns-east, external-dns-west, cert-manager-east and
cert-manager-west.
A revision per environment
The second generator in a matrix may use the first generator’s parameters,
which turns the product into a join. Here the list carries an environment and
the revision to deploy there. The cluster selector reads the environment from
the list, so each element lands only on its own clusters. Staging can run a
tagged release while development follows main:
apiVersion: argoproj.io/v1alpha1kind: ApplicationSetmetadata: name: elasticsearch-exporterspec: generators: - matrix: generators: - list: elements: - environment: development revision: main - environment: staging revision: elasticsearch-exporter-v1.3.0 - clusters: selector: matchLabels: env: "{{ environment }}" template: metadata: name: "elasticsearch-exporter-{{ name }}" spec: project: default source: targetRevision: "{{ revision }}" path: "kustomize/elasticsearch-exporter/overlays/{{ name }}" destination: server: "{{ server }}" namespace: monitoringThe revision values are fields a tool such as
Renovate can bump in a pull request. In a monorepo
where CODEOWNERS routes each ApplicationSet to its team, that pull request
goes to the people who own the deployment.
Limitations
Every generated parameter is a string unless the ApplicationSet sets
goTemplate: true. A list element with a number or a boolean in it has to
quote it, and nested maps flatten to dotted keys.
A generator that produces nothing produces a deletion. A cluster secret
removed by mistake, or a list emptied by a bad merge, deletes every
Application the set owned. With the default sync policy, their resources go
with them. preserveResourcesOnDeletion and a sync policy of create-only
are the guard rails, and both are opt-in.
Template names must be unique across everything the set generates. Two
elements that render the same metadata.name yield one Application and an
error in the controller’s log about the other.
The cluster generator sees only clusters registered with Argo CD. A cluster added to the fleet but not to Argo CD is not a target, and no application tells you so.
What’s next?
Everything above puts variables into template.spec.source. That field is where the real choices are: a chart from a Helm repository, a chart in Git, a Kustomize overlay, or Kustomize inflating a chart. Pointing an ApplicationSet at Helm and Kustomize: five options takes them one at a time.
Comments