If you’re managing multiple clusters, applying manifests by hand with kubectl apply quickly becomes a nightmare. You lose track of what is deployed where, configuration drift sets in, and rolling back a bad change turns into an archeological dig through your terminal history.

The solution is GitOps. We want our GitHub repository to be the absolute single source of truth. If a manifest isn’t in main, it doesn’t run in the cluster. Period.

To achieve this, we’re going to use Argo CD. We’ll set it up as a control plane that watches our repository and automatically syncs changes out to both our US and EU clusters.

The ApplicationSet Approach

We could manually create an Argo CD Application resource for every app in every cluster, but that scales poorly. Instead, we’ll use an ApplicationSet. This allows us to define a template: “For every cluster that matches this label, deploy this application.”

 [GitHub Repo (manifests)]
   [Argo CD (control)]
        ╱      ╲
       ▼        ▼
 [Cluster US] [Cluster EU]
   (syncs us)   (syncs eu)

Labeling and Registration

First, Argo CD needs to know our clusters exist and what “region” they represent. The ApplicationSet cluster generator reads labels from Argo CD’s registered cluster secrets, not from Kubernetes namespace labels.

Assuming you have Argo CD running in a control cluster (or one of your regions acting as the control plane), register each cluster with a region label. This gives Argo CD the credentials it needs to deploy resources on your behalf and gives the ApplicationSet a safe selector.

argocd cluster add prod-us-east-1 --name prod-us --label region=us --yes
argocd cluster add prod-eu-west-1 --name prod-eu --label region=eu --yes

Writing the ApplicationSet

Now for the magic. We define an ApplicationSet that uses the cluster generator. It looks at all the clusters Argo CD knows about, grabs their region label, and uses that to figure out which Kustomize overlay to apply.

Save this as deploy/apps/echo-appset.yaml:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: echo-multicluster
  namespace: argocd
spec:
  generators:
    # Target only registered clusters with an explicit region label
    - clusters:
        selector:
          matchExpressions:
            - key: region
              operator: In
              values: [ "us", "eu" ]
  template:
    metadata:
      name: 'echo-{{name}}'
    spec:
      project: default
      destination:
        server: '{{server}}'
        namespace: echo
      source:
        repoURL: https://github.com/your-org/your-repo
        targetRevision: main
        # Here is the clever part: we dynamically inject the region label
        # to pick the correct overlay (e.g., deploy/rollouts/overlays/us)
        path: deploy/rollouts/overlays/{{metadata.labels.region}}
      syncPolicy:
        automated: 
          prune: true
          selfHeal: true

Verifying the Deployment

Once you apply that ApplicationSet to your control cluster, Argo CD takes over. You can verify it worked by checking the application list:

argocd app list

You should see two applications, echo-prod-us and echo-prod-eu, and their status should be Sync OK. You can also check the clusters directly to confirm the echo namespace was created.

We now have a continuous deployment pipeline that automatically targets multiple regions based on cluster labels. Next, we’ll tackle the hardest part of multi-region architecture: cross-cluster networking with Istio.

Previous: Part 1 · Continue to Part 3: Istio Multi Region Mesh