Lab: Multi-Cluster Management¶
Duration: 25 minutes
Objectives¶
- Create and manage two kind clusters
- Switch safely between Kubernetes contexts
- Deploy the same application to multiple clusters
- Customize per-cluster configuration
- Simulate active/passive failover
- Compare resource state across clusters
Prerequisites¶
- kind installed
- kubectl configured and working
- At least 12GB RAM recommended for two clusters
- Basic understanding of Kubernetes contexts
Tasks¶
Task 1: Create Two Clusters¶
Create two local kind clusters to represent separate environments.
Requirements:
- Primary cluster name:
c2k-primary - Secondary cluster name:
c2k-secondary - Confirm both clusters appear in
kind get clusters - Confirm both contexts appear in
kubectl config get-contexts - Store your original context name so you can switch back later
Hint
Record your current context:
Create the clusters:
List clusters and contexts:
Solution
ORIGINAL_CONTEXT=$(kubectl config current-context)
echo $ORIGINAL_CONTEXT
kind create cluster --name c2k-primary
kind create cluster --name c2k-secondary
kind get clusters
kubectl config get-contexts
Expected output:
Task 2: Practice Context-Safe Commands¶
Inspect both clusters without relying on the current context.
Requirements:
- Get nodes from
kind-c2k-primary - Get nodes from
kind-c2k-secondary - Create aliases or shell variables for both contexts
- Confirm the current context before applying anything
Hint
Set context variables:
Use them explicitly:
Solution
PRIMARY=kind-c2k-primary
SECONDARY=kind-c2k-secondary
kubectl --context=$PRIMARY get nodes
kubectl --context=$SECONDARY get nodes
kubectl config current-context
Expected output: Each cluster shows one node with Ready status. Using --context means you never accidentally apply to the wrong cluster.
Task 3: Create a Shared Application Manifest¶
Create a small application that can run unchanged in both clusters.
Requirements:
- Namespace:
multi-cluster-demo - Deployment name:
hello - Image:
nginx:1.25-alpine - Label:
app=hello - Replicas: 2
- Service name:
hello - Service type:
ClusterIP - Service port: 80
Hint
Use nginx with a shell command that writes index.html from an environment variable before starting nginx. This allows patching the response per cluster without changing the base manifest:
env:
- name: CLUSTER_NAME
value: primary
command: ["/bin/sh", "-c"]
args:
- |
echo "hello from ${CLUSTER_NAME}" > /usr/share/nginx/html/index.html
nginx -g 'daemon off;'
For the shared manifest, start with primary; patch it for secondary later.
Solution
Create hello-namespace.yaml:
Create hello-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
namespace: multi-cluster-demo
spec:
replicas: 2
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
env:
- name: CLUSTER_NAME
value: primary
ports:
- containerPort: 80
command: ["/bin/sh", "-c"]
args:
- |
echo "hello from ${CLUSTER_NAME}" > /usr/share/nginx/html/index.html
nginx -g 'daemon off;'
Create hello-service.yaml:
Task 4: Deploy to the Primary Cluster¶
Deploy the shared application to the primary cluster.
Requirements:
- Apply the namespace, Deployment, and Service to
kind-c2k-primary - Wait for the Deployment to become available
- Port-forward the primary Service to local port 8080
- Confirm the response includes
primary
Hint
Apply with an explicit context:
kubectl --context=$PRIMARY apply -f hello-namespace.yaml
kubectl --context=$PRIMARY apply -f hello-deployment.yaml
kubectl --context=$PRIMARY apply -f hello-service.yaml
Wait and test:
Solution
kubectl --context=$PRIMARY apply -f hello-namespace.yaml
kubectl --context=$PRIMARY apply -f hello-deployment.yaml
kubectl --context=$PRIMARY apply -f hello-service.yaml
kubectl --context=$PRIMARY wait --for=condition=Available deployment/hello \
-n multi-cluster-demo --timeout=90s
kubectl --context=$PRIMARY port-forward -n multi-cluster-demo svc/hello 8080:80
In another terminal:
Expected output:
Task 5: Deploy to the Secondary Cluster¶
Deploy the application to the secondary cluster with a different response.
Requirements:
- Apply the same namespace, Deployment, and Service to
kind-c2k-secondary - Patch or configure the secondary Deployment response to include
secondary - Wait for the Deployment to become available
- Port-forward the secondary Service to local port 8081
- Confirm the response includes
secondary
Hint
Apply the same files to secondary, then patch the environment variable:
kubectl --context=$SECONDARY set env deployment/hello \
-n multi-cluster-demo CLUSTER_NAME=secondary
Restart the Deployment so nginx rewrites the page:
kubectl --context=$SECONDARY rollout restart deployment/hello -n multi-cluster-demo
kubectl --context=$SECONDARY rollout status deployment/hello -n multi-cluster-demo
Port-forward to a different local port:
Solution
kubectl --context=$SECONDARY apply -f hello-namespace.yaml
kubectl --context=$SECONDARY apply -f hello-deployment.yaml
kubectl --context=$SECONDARY apply -f hello-service.yaml
kubectl --context=$SECONDARY set env deployment/hello \
-n multi-cluster-demo CLUSTER_NAME=secondary
kubectl --context=$SECONDARY rollout restart deployment/hello -n multi-cluster-demo
kubectl --context=$SECONDARY rollout status deployment/hello -n multi-cluster-demo
kubectl --context=$SECONDARY port-forward -n multi-cluster-demo svc/hello 8081:80
In another terminal:
Expected output:
Task 6: Compare Cluster State¶
Compare the same workload across both clusters.
Requirements:
- List Pods in
multi-cluster-demoon both clusters - List Services in
multi-cluster-demoon both clusters - Compare Deployment replica counts
- Scale the primary Deployment to 3 replicas
- Confirm the secondary cluster remains at 2 replicas
Hint
Run the same commands against both contexts:
kubectl --context=$PRIMARY get pods,svc,deploy -n multi-cluster-demo
kubectl --context=$SECONDARY get pods,svc,deploy -n multi-cluster-demo
Scale only primary:
Solution
kubectl --context=$PRIMARY get pods,svc,deploy -n multi-cluster-demo
kubectl --context=$SECONDARY get pods,svc,deploy -n multi-cluster-demo
kubectl --context=$PRIMARY scale deployment hello -n multi-cluster-demo --replicas=3
kubectl --context=$PRIMARY get deployment hello -n multi-cluster-demo
kubectl --context=$SECONDARY get deployment hello -n multi-cluster-demo
Expected result: Primary has 3 desired replicas. Secondary remains at 2 desired replicas. Each cluster is fully independent.
Task 7: Simulate Failover¶
Simulate moving traffic from primary to secondary.
Requirements:
- Treat local port 8080 as primary traffic
- Treat local port 8081 as secondary traffic
- Scale primary
helloto 0 replicas to simulate failure - Confirm primary Service has no available endpoints
- Confirm secondary still serves traffic
- Scale secondary to 3 replicas to handle failover
- Restore primary to 2 replicas
Hint
Scale primary down:
kubectl --context=$PRIMARY scale deployment hello -n multi-cluster-demo --replicas=0
kubectl --context=$PRIMARY get endpoints hello -n multi-cluster-demo
Scale secondary up:
Restore primary:
Solution
kubectl --context=$PRIMARY scale deployment hello -n multi-cluster-demo --replicas=0
kubectl --context=$PRIMARY get endpoints hello -n multi-cluster-demo
Primary endpoints should be empty (no active Pods).
curl http://localhost:8081
kubectl --context=$SECONDARY scale deployment hello -n multi-cluster-demo --replicas=3
kubectl --context=$SECONDARY rollout status deployment/hello -n multi-cluster-demo
Restore primary:
Task 8: Document a GitOps Layout¶
Create a local directory structure that could manage both clusters from Git.
Requirements:
- Directory:
fleet-demo - Base app path:
fleet-demo/apps/base - Primary overlay path:
fleet-demo/apps/primary - Secondary overlay path:
fleet-demo/apps/secondary - Cluster config path:
fleet-demo/clusters/primary - Cluster config path:
fleet-demo/clusters/secondary - Add a short
README.mdexplaining which path belongs to which cluster
Hint
Create directories:
mkdir -p fleet-demo/apps/base fleet-demo/apps/primary fleet-demo/apps/secondary
mkdir -p fleet-demo/clusters/primary fleet-demo/clusters/secondary
The README can describe:
apps/base: common manifests shared by every clusterapps/primary: primary-specific patchesapps/secondary: secondary-specific patchesclusters/primary: primary cluster sync entrypoint (e.g., Flux Kustomization)clusters/secondary: secondary cluster sync entrypoint
Solution
mkdir -p fleet-demo/apps/base fleet-demo/apps/primary fleet-demo/apps/secondary
mkdir -p fleet-demo/clusters/primary fleet-demo/clusters/secondary
Create fleet-demo/README.md:
# Fleet Demo
This directory shows one way to organize multi-cluster GitOps configuration.
- `apps/base`: common application manifests shared by every cluster
- `apps/primary`: primary cluster patches and overrides
- `apps/secondary`: secondary cluster patches and overrides
- `clusters/primary`: primary cluster sync entrypoint (Flux Kustomization)
- `clusters/secondary`: secondary cluster sync entrypoint (Flux Kustomization)
Verify:
Verification¶
Check your work:
kind get clusters
kubectl --context=kind-c2k-primary get deploy,svc,endpoints -n multi-cluster-demo
kubectl --context=kind-c2k-secondary get deploy,svc,endpoints -n multi-cluster-demo
find fleet-demo -maxdepth 3 -type d | sort
Expected outcomes:
- Both clusters exist and are reachable independently
- Primary and secondary deployments return different responses
- Scaling one cluster does not affect the other
fleet-demodirectory structure exists locally
Cleanup¶
kind delete cluster --name c2k-primary
kind delete cluster --name c2k-secondary
kubectl config use-context "$ORIGINAL_CONTEXT"
rm -rf fleet-demo
Key takeaways¶
--contextflag is the safest way to target a specific cluster without changing the active context globally- Kustomize overlays customize base manifests per cluster without duplicating YAML
- GitOps controllers reconcile cluster state from Git continuously, making multi-cluster management auditable and repeatable
- Context discipline prevents accidental changes to the wrong cluster — always verify with
kubectl config current-context - Active/passive failover requires independent clusters with synchronized configuration and a traffic-switching mechanism
- Multiple kubeconfigs can be merged or referenced via
KUBECONFIGto manage many clusters from a single terminal
Next section¶
Congratulations on completing Part 2! Explore the Resources section for production readiness guidance.