Schedule One Kubernetes Namespace Instead of the Cluster
Put a qa or dev namespace to sleep with kubectl while ingress, monitoring and shared services in the same cluster keep running.
A shared EKS, GKE or AKS cluster rarely goes idle as a whole. The ingress controller, the monitoring stack and the CI runners serve everyone, but the qa namespace is empty after 7 pm. Stopping that one namespace means scaling its workloads to zero and pausing its CronJobs, then putting everything back exactly as it was. Kubernetes has no namespace “stop” object, so this page builds one from kubectl scale and kubectl patch.
Before you start
kubectlaccess withupdateondeployments/scale,statefulsets/scaleandcronjobsin the target namespace, plusgetonhorizontalpodautoscalers.jqfor saving and replaying replica counts.- A plan for the nodes. Emptying a namespace frees capacity but only saves money when a cluster autoscaler (or a scheduled pool resize) removes the nodes that capacity sat on.
1. Save the current replica counts
The restore needs the numbers you had, not a guess. Store them outside the cluster:
NS=qakubectl get deployments,statefulsets -n "$NS" -o json \ | jq -r '.items[] | "\(.kind|ascii_downcase) \(.metadata.name) \(.spec.replicas)"' > "$NS-replicas.txt"cat "$NS-replicas.txt"2. Scale workloads to zero
kubectl scale deployment --all --replicas=0 -n "$NS"kubectl scale statefulset --all --replicas=0 -n "$NS"StatefulSet Pods terminate but their PersistentVolumeClaims stay, so data on those volumes is still there on restore, and the volumes keep billing.
Autoscaled workloads. If a HorizontalPodAutoscaler targets one of these Deployments, setting the target’s replicas to 0 while the HPA’s minReplicas is above 0 puts the HPA into an implicit maintenance mode: it stops adjusting the target and sets its ScalingActive condition to false until you change the replica count again. You do not need to delete the HPA for the night.
3. Suspend the CronJobs
for cj in $(kubectl get cronjobs -n "$NS" -o name); do kubectl patch "$cj" -n "$NS" -p '{"spec":{"suspend":true}}'doneSuspending does not touch Jobs a CronJob already started; they run to completion. Runs that fall inside the suspended window count as missed. When you unsuspend a CronJob that has no startingDeadlineSeconds, the missed runs are scheduled immediately, so a nightly report CronJob can fire the moment you wake the namespace. Set a starting deadline on CronJobs where that matters.
4. Check the namespace is quiet
kubectl get pods -n "$NS"kubectl get cronjobs -n "$NS" -o custom-columns=NAME:.metadata.name,SUSPEND:.spec.suspendkubectl get hpa -n "$NS"Expect no running Pods apart from any bare Pods you created by hand (scale does not touch them), true in the SUSPEND column, and HPAs reporting the target at 0.
5. Wake it up
while read -r kind name replicas; do kubectl scale "$kind/$name" --replicas="$replicas" -n "$NS"done < "$NS-replicas.txt"
for cj in $(kubectl get cronjobs -n "$NS" -o name); do kubectl patch "$cj" -n "$NS" -p '{"spec":{"suspend":false}}'done
kubectl rollout status deployment -n "$NS" --timeout=10mRestoring the replica count also brings any HPA out of maintenance mode.
Namespaces to leave alone
Do not run this against kube-system, kube-public, kube-node-lease or namespaces holding CNI, CSI, ingress or service-mesh control planes. Treat default with care too: any Pod created there by hand has no controller, so no scale command will bring it back.
The ZopNight version
ZopNight has a namespace-level start and stop for EKS, GKE and AKS that does the same job from a schedule. Stop scales every Deployment and StatefulSet in the namespace to zero and suspends its CronJobs, saving the prior replica counts, HPA and PDB; Start restores them. It refuses system namespaces (kube-*, zopnight-*, istio-*, helm-* and default) before making any Kubernetes call. Schedules use cron expressions in an IANA timezone, and overrides can keep a namespace running for a set window. Details are on the namespace schedule page, and namespace-level scheduling covers when this beats emptying a node pool.