Managing many Kubernetes clusters with FluxCD: cluster strategies per environment, geography, tenant, and hub-and-spoke, multi-cluster bootstrap, cross-cluster dependencies for service discovery and secret sharing, and Cluster API and vcluster integration.

In episode 22 you secured secrets with Sealed Secrets — Git can now be a safe home for secret configuration. But there's a scale we haven't touched: real organizations don't run on a single cluster. There's staging, production, maybe one cluster per region or per team. Each cluster means an environment, configuration, and workflow that must be managed.
This episode covers multi-cluster management with FluxCD: strategies for choosing a cluster topology, bootstrapping many clusters from Git, cross-cluster dependencies such as service discovery and secret sharing, and integration with Cluster API, vcluster, and multi-cluster service meshes.
There's no single right model — the choice depends on isolation, latency, and cost. Four common patterns:
| Strategy | When to use | Advantage |
|---|---|---|
| Cluster per environment | Separating prod vs staging | Small blast radius, independent environments |
| Geographic distribution | Users spread across regions | Low latency, regional HA |
| Tenant per cluster | Multi-tenant SaaS | Strict tenant isolation |
| Hub-and-spoke | Many workloads, centralized control | Centralized configuration, independent spokes |
The hub-and-spoke pattern is the most popular for GitOps at scale: a hub (or central repository) becomes the source of truth, while each spoke cluster pulls the relevant configuration. Git acts as the communication bus between clusters.
Flux is bootstrapped per cluster, each pointing to a different path in the same repository:
flux bootstrap github \
--owner=devvnull --repository=gitops-production \
--branch=main --path=clusters/production --personal
flux bootstrap github \
--owner=devvnull --repository=gitops-production \
--branch=main --path=clusters/staging --personalEach bootstrap creates its own Git connection, its own credentials, and its own flux-system namespace. The directory structure becomes the main organizational pattern:
clusters/
├── production/
│ ├── flux-system/
│ └── apps/
├── staging/
│ ├── flux-system/
│ └── apps/
└── shared/
├── base/
└── kustomization.yamlManifests that are identical in all clusters (e.g. monitoring, logging, policy) only need to be written once in shared/base and used together. Manifests that differ per cluster (e.g. resource sizes, replica counts, endpoints) are written in each cluster's own path.
Flux connects the two through an additional Kustomization pointing at the shared repository, or through a kustomize overlay that references the same base:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: shared
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: shared
path: ./shared/base
prune: trueClusters rarely stand alone. The three most common dependencies:
An application in cluster A calls a service in cluster B. A multi-cluster service mesh solves this transparently: Istio multi-primary lets a VirtualService reference a service in another cluster, while Linkerd multicluster "mirroring" copies Services from a remote cluster to the local cluster:
linkerd multicluster link --cluster-name production | kubectl apply -f -
linkerd multicluster checkSecrets can be shared between clusters in several ways: Sealed Secrets with the same key restored to each cluster (episode 22), or ESO with one central backend accessed by all clusters (episode 21). The choice between Git-ciphertext and an external backend applies the same way at the multi-cluster level.
Configuration changes are spread through Git: push to the repository, and each cluster pulls the changes on its own interval. Because each cluster has its own Kustomization, the rollout can be managed — for example staging follows automatically, while production waits for approval via a change in a path protected by branch protection.
Tip
Staged propagation uses the promotion pattern: configuration is tested in the clusters/staging path first, then copied to clusters/production through a pull request. Git provides a complete audit trail for every change between environments.
Cluster API (CAPI) makes the cluster itself a declaratively managed resource — and because CAPI is a Kubernetes application, it can also run through Flux on a management cluster:
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: workload-production
namespace: clusters
spec:
clusterNetwork:
pods:
cidrBlocks:
- 10.244.0.0/16
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSCluster
name: workload-productionvcluster creates virtual clusters inside a physical cluster — each vcluster is an isolated namespace with its own API server. This is cheap for many tenants and pairs very well with Flux: each vcluster is bootstrapped as a separate cluster with its own Flux credentials, without adding hardware.
A multi-cluster service mesh unifies identity and security policy across clusters: Istio multi-primary for failover and cross-cluster traffic split, Linkerd multicluster for simple service mirroring, or AWS App Mesh for a regional mesh. mTLS and access policies become consistent across all clusters. Verify inter-cluster connectivity with linkerd multicluster check.
This episode closes the multi-cluster phase: FluxCD doesn't just sync a single cluster, it becomes the foundation for managing dozens of clusters from one or several repositories. The strategy is chosen based on isolation and latency needs, bootstrap runs per cluster with a separate path, cross-cluster dependencies are handled through service meshes, secret sharing, and Git as the propagation medium.
The key takeaways:
With many clusters, new challenges appear: the same configuration is spread out and must stay consistent, and each cluster's resources must be watched. In the next episode, episode 24, we'll discuss Sharding and Performance: horizontal and vertical Flux sharding, reconciliation tuning, and large-scale repository patterns. See you in episode 24!