An ApplicationSet is one resource that produces many Argo CD Applications:
a template for the application, plus a generator that supplies one set of
parameters per application to produce. Ten services across four clusters is
forty Application manifests written by hand, or one ApplicationSet whose
generator yields forty parameter sets. This post is about the three
generators that cover 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: an element removed is an application 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: 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
The Cartesian product of two generators: every parameter set of the first combined 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, and the cluster selector reads the
environment from the list, so each element lands only on its own clusters
and 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 the fields a tool such as
Renovate can bump in a pull request, and in a
monorepo with CODEOWNERS routing 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, and with the default sync policy their
resources with them. preserveResourcesOnDeletion and a sync policy of
create-only are the guard rails, and they 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, and 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