Skip to content

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

  1. Create a multi-tenant Kind cluster with 3 namespaces: team-alpha, team-beta, platform.
  2. Configure RBAC: each team gets scoped access only to their namespace.
  3. Set ResourceQuotas and LimitRanges for each tenant namespace.
  4. Deploy a shared ingress controller in the platform namespace.
  5. Deploy applications for each team with proper NetworkPolicies.
  6. Set up monitoring with metrics-server and resource dashboards.
  7. Implement a CI/CD-style pipeline that deploys from manifests.
  8. Verify all components work end-to-end.
  9. 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.