Room 56
PLATFORM ENGINEERING CAPSTONE
The Final Mastery Challenge
- Everything you’ve learned comes together here.
- Design a production-grade Kubernetes platform: multi-tenancy, RBAC, networking, storage, monitoring, security, and GitOps.
- This is the ultimate test of your Kubernetes mastery.
📜 Mission Briefing
- Create a multi-tenant Kind cluster with 3 namespaces:
team-alpha,team-beta,platform. - Configure RBAC: each team gets scoped access only to their namespace.
- Set ResourceQuotas and LimitRanges for each tenant namespace.
- Deploy a shared ingress controller in the
platformnamespace. - Deploy applications for each team with proper NetworkPolicies.
- Set up monitoring with metrics-server and resource dashboards.
- Implement a CI/CD-style pipeline that deploys from manifests.
- Verify all components work end-to-end.
- The combined first letters of all namespace names spell part of the password:
master56.
Key Commands
| Command | Purpose |
|---|---|
kubectl auth can-i --list --as=alpha-dev -n team-alpha |
Check RBAC permissions |
kubectl get resourcequota -A |
View all quotas |
kubectl get limitrange -A |
View all limit ranges |
kubectl get networkpolicy -A |
View all network policies |
kubectl top nodes |
Node resource usage |
kubectl get ingress -A |
All ingress rules |
Step 1: Multi-Tenant Cluster Setup
# kind-platform.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ResourceQuota,PodSecurity"
extraPortMappings:
- containerPort: 30080
hostPort: 80
protocol: TCP
- containerPort: 30443
hostPort: 443
protocol: TCP
- role: worker
- role: worker
# Create the platform cluster
kind create cluster --name platform --config kind-platform.yaml
# Create tenant namespaces
kubectl create namespace team-alpha
kubectl create namespace team-beta
kubectl create namespace platform
# Label namespaces for network policy
kubectl label ns team-alpha tenant=alpha
kubectl label ns team-beta tenant=beta
kubectl label ns platform role=platform
Step 2: RBAC for Multi-Tenancy
# rbac-alpha.yaml - Scoped access for team-alpha
apiVersion: v1
kind: ServiceAccount
metadata:
name: alpha-dev
namespace: team-alpha
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: team-alpha-developer
namespace: team-alpha
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "services", "configmaps", "secrets", "jobs", "cronjobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log", "pods/exec"]
verbs: ["get", "create"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "create", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alpha-dev-binding
namespace: team-alpha
subjects:
- kind: ServiceAccount
name: alpha-dev
namespace: team-alpha
roleRef:
kind: Role
name: team-alpha-developer
apiGroup: rbac.authorization.k8s.io
# rbac-beta.yaml - Scoped access for team-beta
apiVersion: v1
kind: ServiceAccount
metadata:
name: beta-dev
namespace: team-beta
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: team-beta-developer
namespace: team-beta
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "services", "configmaps", "secrets", "jobs", "cronjobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log", "pods/exec"]
verbs: ["get", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: beta-dev-binding
namespace: team-beta
subjects:
- kind: ServiceAccount
name: beta-dev
namespace: team-beta
roleRef:
kind: Role
name: team-beta-developer
apiGroup: rbac.authorization.k8s.io
Step 3: Resource Quotas and Limits
# quota-alpha.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: alpha-quota
namespace: team-alpha
spec:
hard:
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 4Gi
pods: "20"
services: "10"
persistentvolumeclaims: "5"
---
apiVersion: v1
kind: LimitRange
metadata:
name: alpha-limits
namespace: team-alpha
spec:
limits:
- default:
cpu: 200m
memory: 256Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "1"
memory: 1Gi
min:
cpu: 50m
memory: 64Mi
type: Container
# quota-beta.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: beta-quota
namespace: team-beta
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
pods: "10"
services: "5"
persistentvolumeclaims: "3"
---
apiVersion: v1
kind: LimitRange
metadata:
name: beta-limits
namespace: team-beta
spec:
limits:
- default:
cpu: 100m
memory: 128Mi
defaultRequest:
cpu: 50m
memory: 64Mi
max:
cpu: 500m
memory: 512Mi
min:
cpu: 25m
memory: 32Mi
type: Container
Step 4: Network Isolation
# netpol-alpha.yaml - Isolate team-alpha, allow only intra-namespace + ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: alpha-isolation
namespace: team-alpha
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: alpha
- namespaceSelector:
matchLabels:
role: platform
egress:
- to:
- namespaceSelector:
matchLabels:
tenant: alpha
- to: # Allow DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
Step 5: Tenant Applications
# alpha-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: alpha-web
namespace: team-alpha
spec:
replicas: 2
selector:
matchLabels:
app: alpha-web
template:
metadata:
labels:
app: alpha-web
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: alpha-web-svc
namespace: team-alpha
spec:
selector:
app: alpha-web
ports:
- port: 80
targetPort: 80
# beta-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: beta-api
namespace: team-beta
spec:
replicas: 2
selector:
matchLabels:
app: beta-api
template:
metadata:
labels:
app: beta-api
spec:
containers:
- name: api
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: beta-api-svc
namespace: team-beta
spec:
selector:
app: beta-api
ports:
- port: 80
targetPort: 80
Step 6: Ingress Controller + Routes
# Install NGINX Ingress Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120s
# ingress-routes.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: alpha-ingress
namespace: team-alpha
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: alpha.platform.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: alpha-web-svc
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: beta-ingress
namespace: team-beta
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: beta.platform.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: beta-api-svc
port:
number: 80
Step 7: Monitoring
# Install metrics-server for Kind
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Patch for Kind (insecure TLS)
kubectl patch deployment metrics-server -n kube-system \
--type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
# Wait for metrics-server
kubectl wait --for=condition=ready pod -l k8s-app=metrics-server -n kube-system --timeout=120s
# Check resource usage
kubectl top nodes
kubectl top pods -A
Step 8: Verification Script
#!/bin/bash
# verify-platform.sh - End-to-end platform verification
echo "🏗️ Platform Engineering Capstone Verification"
echo "================================================"
PASS=true
# Check namespaces
echo -n "✓ Namespaces: "
NS=$(kubectl get ns --no-headers | grep -E "team-alpha|team-beta|platform" | wc -l)
[[ $NS -eq 3 ]] && echo "OK ($NS/3)" || { echo "FAIL ($NS/3)"; PASS=false; }
# Check RBAC
echo -n "✓ RBAC alpha: "
kubectl auth can-i list pods --as=system:serviceaccount:team-alpha:alpha-dev -n team-alpha > /dev/null 2>&1 \
&& echo "OK" || { echo "FAIL"; PASS=false; }
echo -n "✓ RBAC isolation: "
kubectl auth can-i list pods --as=system:serviceaccount:team-alpha:alpha-dev -n team-beta > /dev/null 2>&1 \
&& { echo "FAIL (cross-namespace access!)"; PASS=false; } || echo "OK (blocked)"
# Check quotas
echo -n "✓ ResourceQuotas: "
RQ=$(kubectl get resourcequota -A --no-headers 2>/dev/null | wc -l)
[[ $RQ -ge 2 ]] && echo "OK ($RQ quotas)" || { echo "FAIL"; PASS=false; }
# Check network policies
echo -n "✓ NetworkPolicies: "
NP=$(kubectl get networkpolicy -A --no-headers 2>/dev/null | wc -l)
[[ $NP -ge 1 ]] && echo "OK ($NP policies)" || { echo "FAIL"; PASS=false; }
# Check deployments
echo -n "✓ Deployments: "
DEP=$(kubectl get deploy -A --no-headers | grep -E "alpha-web|beta-api" | wc -l)
[[ $DEP -ge 2 ]] && echo "OK ($DEP apps)" || { echo "FAIL"; PASS=false; }
# Check ingress
echo -n "✓ Ingress: "
ING=$(kubectl get ingress -A --no-headers 2>/dev/null | wc -l)
[[ $ING -ge 2 ]] && echo "OK ($ING routes)" || { echo "FAIL"; PASS=false; }
echo ""
if $PASS; then
echo "🏆 PLATFORM COMPLETE! Password: master56"
else
echo "❌ Some checks failed. Review the issues above."
fi
💡 Hints
- ResourceQuotas enforce hard limits per namespace - pods won’t schedule without requests when quotas exist.
- LimitRanges apply defaults - containers without explicit resources get the LimitRange defaults.
- RBAC Roles are namespace-scoped; ClusterRoles are cluster-wide.
- NetworkPolicies are additive - a pod with no policy allows all traffic; any policy restricts it.
- Test RBAC with
kubectl auth can-i --as=system:serviceaccount:NAMESPACE:SA_NAME.
🔑 Password
Run the verification script after deploying all platform components: master56.