Command Palette

Search for a command to run...

Hectal

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.

50 min Free — two kind clusters 4 steps 2 break-it drills

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.

Hub and spokediagram
Rendering diagram…

Part 2

Your project after this mission · 5 files change

shoplite-gitops/
  • 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. 1

    Create and register a second cluster

    argocd cluster add creates a ServiceAccount in the target cluster and stores its credentials as a Secret in the argocd namespace. 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-prod
    argocd cluster add kind-shoplite-prod --name prod-mumbai --label env=prod --label region=ap-south-1 --yes
    argocd cluster set in-cluster --label env=dev
    argocd cluster list
    ── expected output ──
    SERVER NAME VERSION STATUS MESSAGE
    https://kubernetes.default.svc in-cluster 1.34 Successful
    https://172.18.0.3:6443 prod-mumbai 1.34 Successful
  2. 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 an env label). Each pair becomes an Application whose path uses the cluster's env label to choose the overlay. goTemplate gives proper templating with missingkey=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. 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's spec (it needs --enable-progressive-syncs on 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. 4

    Adopt it and delete the hand-written apps

    Adding a new service is now: create apps/cart/base and its overlays, commit. The ApplicationSet discovers the folder and creates cart-in-cluster and cart-prod-mumbai. Adding a region is argocd cluster add with labels.

    terminal
    $ git rm argocd/apps/web-dev.yaml argocd/apps/web-prod.yaml
    git add . && git commit -m 'appset: services × clusters' && git push
    argocd app list -l service -o name
    ── expected output ──
    argocd/cart-in-cluster
    argocd/cart-prod-mumbai
    argocd/checkout-in-cluster
    argocd/checkout-prod-mumbai
    argocd/web-in-cluster
    argocd/web-prod-mumbai

Checkpoint — you should now have

  • ✓Two clusters are registered with env labels.
  • ✓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.

terminal
$ kubectl -n shoplite-prod get pods --context kind-shoplite-prod
── what you'll see ──
No resources found in shoplite-prod namespace.

Break #2

A missing overlay for one cluster

Register a third cluster labelled env=staging without creating overlays/staging folders.

terminal
$ argocd app list | grep staging
── what you'll see ──
web-staging ... Unknown Unknown ComparisonError: apps/web/overlays/staging: app path does not exist

Part 5

Interview questions from this mission

01

What's an ApplicationSet and which generators have you used?

02

One Argo CD for all clusters or one per cluster?

0/4 · 0%