Mission 2.3 · Stage 2 — GitOps at Scale: Secrets, Rollouts, and Many Clusters
ApplicationSets and Multi-Cluster GitOps
Goal: One ApplicationSet generates every ShopLite service for every environment and cluster, so adding a service or a cluster is a single commit.
By the end of this mission
- Register additional clusters with Argo CD
- Generate Applications with list, cluster, Git-directory, and matrix generators
- Choose hub-and-spoke vs Argo-per-cluster topologies
- Roll changes across clusters progressively
Part 1
Understand it first
Why ApplicationSets
With 8 services × 3 environments × 2 regions, app-of-apps means 48 near-identical Application files. An APPLICATIONSET is a template plus GENERATORS that produce parameters: the LIST generator (fixed values), CLUSTER (every cluster registered with Argo, filtered by labels), GIT DIRECTORY (one app per folder matching a pattern), GIT FILE (one per config file), PULL REQUEST (a preview environment per open PR), and MATRIX/MERGE to combine them. Add a folder or register a cluster, and the Applications appear.
Topologies
HUB AND SPOKE: one Argo CD in a management cluster deploys to all others. One pane of glass and one place for RBAC, but it needs network access and credentials for every cluster, and it's a single point of failure (deploys stop, workloads keep running). ARGO PER CLUSTER: each cluster runs its own Argo pointing at its own folder. Isolated and resilient, but N installations to operate and no single view. Many organisations use hub-and-spoke per region or per environment tier as a middle ground. Pull-based agents (Argo CD agent mode, Flux per cluster) remove the need for inbound access to spokes.
Part 2
Your project after this mission · 5 files change
- shoplite-gitops/
- apps/
- cart/
- overlays/
- dev/
- kustomization.yamlnew
- prod/
- kustomization.yamlnew
- web/
- overlays/
- prod/
- kustomization.yaml
- argocd/
- apps/
- web-dev.yamldeleted
- web-prod.yamldeleted
- appsets/
- shoplite-services.yamlnew
Part 3
Build it, step by step
- 1
Create and register a second cluster
argocd cluster addcreates a ServiceAccount in the target cluster and stores its credentials as a Secret in theargocdnamespace. Labels on that Secret drive the cluster generator. On EKS, use IAM-based auth instead of long-lived tokens. With kind, use the Docker network IP for the API server so Argo's pods can reach it.terminal$ kind create cluster --name shoplite-prodargocd cluster add kind-shoplite-prod --name prod-mumbai --label env=prod --label region=ap-south-1 --yesargocd cluster set in-cluster --label env=devargocd cluster list── expected output ──SERVER NAME VERSION STATUS MESSAGEhttps://kubernetes.default.svc in-cluster 1.34 Successfulhttps://172.18.0.3:6443 prod-mumbai 1.34 Successful - 2
One ApplicationSet for every service × cluster
The MATRIX generator combines a Git directory generator (every
apps/*folder is a service) with a cluster generator (every cluster with anenvlabel). Each pair becomes an Application whose path uses the cluster'senvlabel to choose the overlay.goTemplategives proper templating withmissingkey=error, so a typo fails loudly.shoplite-gitops/argocd/appsets/shoplite-services.yamlwhole fileyaml apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: shoplite-services namespace: argocd spec: goTemplate: true goTemplateOptions: ["missingkey=error"] generators: - matrix: generators: - git: repoURL: https://github.com/<you>/shoplite-gitops.git revision: main directories: - path: apps/* - clusters: selector: matchExpressions: - { key: env, operator: In, values: [dev, prod] } template: metadata: name: '{{.path.basename}}-{{.name}}' labels: { service: '{{.path.basename}}', env: '{{index .metadata.labels "env"}}' } spec: project: shoplite source: repoURL: https://github.com/<you>/shoplite-gitops.git targetRevision: main path: 'apps/{{.path.basename}}/overlays/{{index .metadata.labels "env"}}' destination: server: '{{.server}}' namespace: 'shoplite-{{index .metadata.labels "env"}}' syncPolicy: automated: { prune: true, selfHeal: true } syncOptions: [CreateNamespace=true] - 3
Roll out cluster by cluster
By default an ApplicationSet change updates every generated app at once. The progressive sync strategy (
RollingSync) updates apps in groups by label: dev first, then prod, and each group must be Healthy before the next starts. Add this to the ApplicationSet'sspec(it needs--enable-progressive-syncson the controller).shoplite-gitops/argocd/appsets/shoplite-services.yamladd to fileyaml strategy: type: RollingSync rollingSync: steps: - matchExpressions: [{ key: env, operator: In, values: [dev] }] - matchExpressions: [{ key: env, operator: In, values: [prod] }] maxUpdate: 50% - 4
Adopt it and delete the hand-written apps
Adding a new service is now: create
apps/cart/baseand its overlays, commit. The ApplicationSet discovers the folder and createscart-in-clusterandcart-prod-mumbai. Adding a region isargocd cluster addwith labels.terminal$ git rm argocd/apps/web-dev.yaml argocd/apps/web-prod.yamlgit add . && git commit -m 'appset: services × clusters' && git pushargocd app list -l service -o name── expected output ──argocd/cart-in-clusterargocd/cart-prod-mumbaiargocd/checkout-in-clusterargocd/checkout-prod-mumbaiargocd/web-in-clusterargocd/web-prod-mumbai
Checkpoint — you should now have
- ✓Two clusters are registered with
envlabels. - ✓Every
apps/*folder yields one Application per cluster. - ✓Creating a new service folder creates its Applications with no other change.
Part 4
Break it on purpose
Make each change, run the command, and read the error before revealing the diagnosis. Recognising these messages on sight is what makes you fast on a real team. Undo the change afterwards.
Break #1
Delete the ApplicationSet by accident
kubectl delete applicationset shoplite-services -n argocd.
Break #2
A missing overlay for one cluster
Register a third cluster labelled env=staging without creating overlays/staging folders.
Part 5
Interview questions from this mission
What's an ApplicationSet and which generators have you used?
One Argo CD for all clusters or one per cluster?