Kubernetes (K8s) is the industry-standard container orchestration platform. Key concepts: Pods (smallest deployable unit), Deployments (manage replicas + rolling updates), Services (stable networking), ConfigMaps/Secrets (configuration), Ingress (HTTP routing), PersistentVolumes (durable storage), and HPA (autoscaling). Use kubectl to interact with the cluster and Helm to package applications as charts.
- Kubernetes automates deployment, scaling, and self-healing of containerized applications across clusters.
- Pods are the smallest unit; Deployments manage Pods at scale with rolling updates and rollback support.
- Services provide stable DNS names and load balancing; Ingress routes HTTP traffic to multiple services.
- HPA autoscales Pod replicas based on CPU/memory; the Cluster Autoscaler adds/removes nodes automatically.
- Helm packages applications as Charts â reusable, versioned, configurable â and manages release lifecycle.
- PersistentVolumes and StorageClasses provide durable storage for stateful workloads like databases.
- For production, use a managed Kubernetes service (EKS, GKE, AKS) to eliminate control plane operations.
What Is Kubernetes and Why It Matters
Kubernetes (K8s) is an open-source container orchestration system originally designed by Google and donated to the Cloud Native Computing Foundation (CNCF) in 2014. It automates the deployment, scaling, and lifecycle management of containerized applications across clusters of machines. In 2026, Kubernetes is the de facto standard for running production workloads at scale â from startups shipping their first microservice to hyperscalers managing millions of containers per day.
Before Kubernetes, teams managed servers manually: custom deployment scripts, fragile SSH-based deploys, and hand-rolled load balancers. Docker solved the "it works on my machine" problem by packaging apps into containers, but running thousands of containers across dozens of servers without orchestration is chaos. Kubernetes brings order: you describe the desired state of your system in YAML manifests, and Kubernetes continuously reconciles the actual state to match. A container crashes? Kubernetes restarts it. A node dies? Kubernetes reschedules the workloads to healthy nodes. Traffic spikes? Kubernetes scales out. Traffic drops? Kubernetes scales back in â automatically.
The platform provides self-healing, horizontal scaling, rolling updates with zero downtime, secret management, service discovery, load balancing, and storage orchestration â all as built-in primitives. This guide covers everything you need to go from "what is a Pod?" to running a production-grade application with Helm, autoscaling, and persistent storage.
Key Kubernetes Features
- Self-healing: automatically restarts failed containers, replaces unresponsive Pods, kills containers that fail health checks, and reschedules them on healthy nodes.
- Horizontal scaling: scale applications up or down with a single command (kubectl scale) or automatically based on CPU, memory, or custom metrics via HPA.
- Rolling updates & rollbacks: deploy new versions with zero downtime using configurable rolling update strategies; roll back instantly if anything goes wrong.
- Service discovery & load balancing: built-in DNS and kube-proxy distribute traffic automatically across healthy Pod instances without manual configuration.
- Configuration management: separate app code from configuration using ConfigMaps (non-sensitive) and Secrets (sensitive), injected as environment variables or file mounts.
- Storage orchestration: automatically provision and attach cloud or on-premises storage to Pods using PersistentVolumes and StorageClasses.
- Infrastructure abstraction: the same Kubernetes manifests run on AWS EKS, Google GKE, Azure AKS, or bare-metal with minimal modifications.
- Extensibility: CustomResourceDefinitions (CRDs) and Operators allow you to extend Kubernetes with domain-specific automation for databases, messaging systems, and more.
Kubernetes Cluster Architecture
ââââââââââââââââââââââââââââ CONTROL PLANE ââââââââââââââââââââââââââââ
â â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââââââââââââ â
â â API Server â â Scheduler â â Controller Manager â â
â â (REST API) â â (podânode) â â (node/replica/svc) â â
â ââââââââ¬ââââââââ ââââââââââââââââ ââââââââââââââââââââââââââ â
â â â
â ââââââââŽââââââââ ââââââââââââââââ â
â â etcd â â Cloud â â
â â (key-value) â â Controller â â
â ââââââââââââââââ ââââââââââââââââ â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â â â
⌠⌠âŒ
ââââââââââââââââââââ ââââââââââââââââââââ ââââââââââââââââââââ
â Worker Node 1 â â Worker Node 2 â â Worker Node 3 â
â â â â â â
â ââââââââââââââ â â ââââââââââââââ â â ââââââââââââââ â
â â kubelet â â â â kubelet â â â â kubelet â â
â ââââââââââââââ â â ââââââââââââââ â â ââââââââââââââ â
â ââââââââââââââ â â ââââââââââââââ â â ââââââââââââââ â
â â kube-proxy â â â â kube-proxy â â â â kube-proxy â â
â ââââââââââââââ â â ââââââââââââââ â â ââââââââââââââ â
â ââââââââââââââ â â ââââââââââââââ â â ââââââââââââââ â
â â containerd â â â â containerd â â â â containerd â â
â ââââââââââââââ â â ââââââââââââââ â â ââââââââââââââ â
â [Pod][Pod][Pod] â â [Pod][Pod][Pod] â â [Pod][Pod][Pod] â
ââââââââââââââââââââ ââââââââââââââââââââ ââââââââââââââââââââCore Concepts: Pods, Deployments, Services, and More
Pods â The Smallest Deployable Unit
A Pod is the atomic unit of Kubernetes â the smallest thing you can deploy. A Pod wraps one or more containers that share the same network namespace (they communicate via localhost) and storage volumes. In practice, most Pods run a single application container, with optional sidecar containers for logging, proxying (Envoy/Istio), or secrets injection (Vault Agent).
Deployments â Managing Replicas and Updates
A Deployment is a higher-level abstraction that manages a ReplicaSet, which in turn manages a set of identical Pods. You tell the Deployment "I want 3 replicas of this container image" and it creates, monitors, and maintains exactly 3 running Pods. When you update the image, the Deployment orchestrates a rolling update â spinning up new Pods before terminating old ones â ensuring zero downtime.
Services â Stable Networking Endpoints
Pods are ephemeral â they can be created, destroyed, and rescheduled at any time with different IP addresses. A Service provides a stable virtual IP (ClusterIP) and DNS name that proxies traffic to a set of Pods selected by labels. Four Service types exist: ClusterIP (internal cluster access), NodePort (accessible on each node IP), LoadBalancer (cloud provider external load balancer), and ExternalName (DNS alias).
ReplicaSets â Ensuring Availability
A ReplicaSet ensures that a specified number of Pod replicas are running at any given time. If a Pod crashes, the ReplicaSet controller immediately creates a replacement. ReplicaSets are rarely created directly; Deployments manage them automatically and provide the additional capability of rolling updates and rollback history.
Namespaces â Logical Isolation
Namespaces partition a single cluster into multiple virtual clusters. They are the primary mechanism for multi-tenancy: separating dev, staging, and production environments; isolating teams; or organizing microservices. Resources within a namespace share the same DNS domain and can be subject to ResourceQuotas and LimitRanges to prevent any one namespace from consuming all cluster resources.
kubectl Commands: The Complete Reference
kubectl is the command-line tool for interacting with a Kubernetes cluster. It communicates with the Kubernetes API server and is your primary interface for deploying applications, inspecting resources, debugging problems, and managing the cluster.
Install kubectl and Set Up a Local Cluster
# Install kubectl (macOS)
brew install kubectl
# Install kubectl (Linux)
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install kubectl /usr/local/bin/kubectl
# Verify installation
kubectl version --client
# ââ Local Cluster Options âââââââââââââââââââââââââââââââââââââââââ
# Option 1: minikube (recommended for beginners)
brew install minikube
minikube start --cpus=4 --memory=8192 --driver=docker
minikube dashboard # Open web UI
minikube stop
# Option 2: kind (Kubernetes in Docker)
brew install kind
kind create cluster --name dev
kind get clusters
kind delete cluster --name dev
# Option 3: Docker Desktop
# Settings â Kubernetes â Enable Kubernetes â Apply & Restart
# Check cluster connection
kubectl cluster-info
kubectl get nodes -o wideEssential kubectl Commands
# ââ Cluster & Nodes ââââââââââââââââââââââââââââââââââââââââââââââ
kubectl cluster-info # Show control plane URL
kubectl get nodes # List all nodes
kubectl get nodes -o wide # Nodes with IP, OS, runtime
kubectl top nodes # CPU & memory usage per node
kubectl describe node <node-name> # Detailed node info & events
# ââ Pods âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
kubectl get pods # Pods in current namespace
kubectl get pods -A # Pods across all namespaces
kubectl get pods -n staging # Pods in staging namespace
kubectl get pods -o wide # Pods with node & IP
kubectl get pods --show-labels # Show pod labels
kubectl get pods -l app=web-app # Filter by label
kubectl get pods -w # Watch pod status live
kubectl describe pod <pod-name> # Detailed pod info & events
kubectl logs <pod-name> # Container stdout logs
kubectl logs <pod-name> -f # Stream logs (follow)
kubectl logs <pod-name> --previous # Logs from crashed container
kubectl logs <pod-name> -c <container> # Logs from specific container
kubectl exec -it <pod-name> -- bash # Shell into pod
kubectl exec -it <pod-name> -- /bin/sh # Shell (Alpine/BusyBox)
kubectl exec <pod-name> -- env # Print environment variables
kubectl port-forward pod/<pod-name> 8080:80 # Local port 8080 to pod port 80
kubectl port-forward svc/<svc-name> 8080:80 # Local port to service port
kubectl delete pod <pod-name> # Delete pod (Deployment recreates it)
# ââ Deployments ââââââââââââââââââââââââââââââââââââââââââââââââââ
kubectl get deployments
kubectl get deploy -o wide
kubectl describe deployment <name>
kubectl apply -f deployment.yaml # Create or update
kubectl delete -f deployment.yaml # Delete
kubectl diff -f deployment.yaml # Preview changes
kubectl scale deploy <name> --replicas=5 # Scale manually
kubectl set image deploy/<name> app=myimage:2.0.0 # Update image
kubectl rollout status deploy/<name> # Watch rollout progress
kubectl rollout history deploy/<name> # View revision history
kubectl rollout undo deploy/<name> # Rollback to previous
kubectl rollout undo deploy/<name> --to-revision=3 # Specific revision
# ââ Services & Ingress âââââââââââââââââââââââââââââââââââââââââââ
kubectl get services
kubectl get svc -o wide
kubectl describe svc <name>
kubectl get ingress
kubectl describe ingress <name>
# ââ ConfigMaps & Secrets âââââââââââââââââââââââââââââââââââââââââ
kubectl get configmaps
kubectl describe configmap <name>
kubectl get secrets
kubectl get secret <name> -o jsonpath="{.data.password}" | base64 --decode
# ââ Namespaces âââââââââââââââââââââââââââââââââââââââââââââââââââ
kubectl get namespaces
kubectl create namespace staging
kubectl config set-context --current --namespace=staging
# ââ Debugging ââââââââââââââââââââââââââââââââââââââââââââââââââââ
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl get pod <name> -o yaml # Full YAML spec
kubectl run debug-pod --image=busybox:latest -it --rm -- sh
kubectl top pods # Resource usage
# ââ All Resources ââââââââââââââââââââââââââââââââââââââââââââââââ
kubectl get all # All resources in namespace
kubectl get all -n my-namespace
kubectl get all -A # All resources everywhereYAML Manifests: Pod, Deployment, Service, ConfigMap, Secret
Kubernetes resources are defined as YAML manifests and applied to the cluster using kubectl apply -f. Every manifest has four top-level fields: apiVersion, kind, metadata, and spec. Here are production-ready examples for the most common resource types.
Pod Manifest
# pod.yaml â Production-ready Pod definition
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: production
labels:
app: my-app
version: v1.0.0
tier: backend
spec:
containers:
- name: app
image: nginx:1.27-alpine
ports:
- containerPort: 80
protocol: TCP
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 10
startupProbe:
httpGet:
path: /healthz
port: 80
failureThreshold: 30
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 30
restartPolicy: AlwaysDeployment Manifest (Production-Ready)
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
labels:
app: web-app
annotations:
kubernetes.io/change-cause: "Release v1.2.0 â performance improvements"
spec:
replicas: 3
selector:
matchLabels:
app: web-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max 1 extra Pod during update
maxUnavailable: 0 # No downtime
template:
metadata:
labels:
app: web-app
version: "1.2.0"
spec:
containers:
- name: web
image: my-registry/web-app:1.2.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
envFrom:
- configMapRef:
name: app-config
resources:
requests:
memory: "128Mi"
cpu: "200m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 60
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web-appService Manifest â All Four Types
# ClusterIP â internal cluster access only (default)
apiVersion: v1
kind: Service
metadata:
name: web-app-svc
namespace: production
spec:
type: ClusterIP
selector:
app: web-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
---
# NodePort â accessible at <NodeIP>:30080 from outside cluster
apiVersion: v1
kind: Service
metadata:
name: web-app-nodeport
spec:
type: NodePort
selector:
app: web-app
ports:
- port: 80
targetPort: 3000
nodePort: 30080 # Must be 30000â32767
---
# LoadBalancer â provisions cloud LB (EKS/GKE/AKS)
apiVersion: v1
kind: Service
metadata:
name: web-app-lb
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
type: LoadBalancer
selector:
app: web-app
ports:
- port: 443
targetPort: 3000ConfigMap and Secret
# configmap.yaml â non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
NODE_ENV: "production"
LOG_LEVEL: "info"
PORT: "3000"
MAX_CONNECTIONS: "100"
app.json: |
{
"features": {
"darkMode": true,
"betaFeatures": false
},
"pagination": {
"defaultPageSize": 20
}
}
---
# secret.yaml â sensitive data (base64-encoded in etcd)
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: admin
password: "super-secret-password-change-me"
url: "postgres://admin:password@postgres:5432/myapp"
---
# TLS Secret â for HTTPS certificates
apiVersion: v1
kind: Secret
metadata:
name: tls-cert
namespace: production
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>Helm: The Package Manager for Kubernetes
Helm is the package manager for Kubernetes, analogous to apt for Debian or npm for Node.js. A Helm chart is a collection of Kubernetes manifest templates with configurable values. Charts can be shared via repositories (Artifact Hub hosts thousands of community charts for databases, monitoring, ingress controllers, and more). Helm manages the release lifecycle: install, upgrade, rollback, and uninstall.
Helm v3 (current) eliminates the server-side Tiller component from Helm v2. Charts are versioned and releases can be inspected, diffed, and rolled back. For teams that need per-environment customization, Helm values files provide a clean separation between chart logic and deployment-specific configuration.
Helm Installation and Basic Commands
# Install Helm (macOS)
brew install helm
# Install Helm (Linux)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Verify
helm version
# ââ Repository Management ââââââââââââââââââââââââââââââââââââââââ
helm repo add stable https://charts.helm.sh/stable
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update # Refresh repo indices
helm repo list # Show configured repos
# ââ Installing Charts ââââââââââââââââââââââââââââââââââââââââââââ
# Install NGINX Ingress Controller
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace
# Install PostgreSQL with custom values
helm install my-postgres bitnami/postgresql \
--namespace production \
--set auth.postgresPassword=secret123 \
--set primary.persistence.size=20Gi \
--set primary.resources.requests.memory=256Mi
# Install with a values file (preferred for complex configs)
helm install my-release my-chart/ -f values-production.yaml
# ââ Release Management âââââââââââââââââââââââââââââââââââââââââââ
helm list # List all releases
helm list -n production # Releases in namespace
helm status my-postgres # Release status
helm history my-postgres # Release history
# Upgrade a release
helm upgrade my-postgres bitnami/postgresql \
--namespace production \
--set auth.postgresPassword=secret123 \
--set primary.persistence.size=30Gi
# Rollback to previous version
helm rollback my-postgres 1
# Uninstall a release
helm uninstall my-postgres -n production
# ââ Chart Development ââââââââââââââââââââââââââââââââââââââââââââ
helm create my-app # Scaffold a new chart
helm lint my-app/ # Validate chart syntax
helm template my-app my-app/ -f values.yaml # Render templates locally
helm install my-app my-app/ --dry-run --debug
helm package my-app/ # Package chart as .tgzHelm Values File Example
# values-production.yaml
# Override default chart values for production environment
replicaCount: 3
image:
repository: my-registry/web-app
tag: "1.2.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.example.com
resources:
requests:
memory: 256Mi
cpu: 200m
limits:
memory: 512Mi
cpu: 500m
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
config:
NODE_ENV: production
LOG_LEVEL: info
postgresql:
enabled: true
auth:
database: myapp
existingSecret: db-credentials
primary:
persistence:
size: 20Gi
storageClass: gp3Kubernetes Networking: ClusterIP, NodePort, LoadBalancer, Ingress
Kubernetes networking operates at three levels: Pod-to-Pod communication (handled by the CNI plugin â Flannel, Calico, Cilium), Service-based load balancing (kube-proxy), and external access (Ingress controllers). Understanding each layer is critical for designing resilient, secure application architectures.
Ingress â HTTP Routing with TLS
# ingress.yaml â Route traffic to multiple services via one entry point
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
- api.example.com
secretName: app-tls-secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-v1-svc
port:
number: 80
- path: /v2
pathType: Prefix
backend:
service:
name: api-v2-svc
port:
number: 80
---
# ClusterIssuer â Let's Encrypt production certificates
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginxNetworkPolicy â Zero-Trust Pod Networking
# networkpolicy.yaml â Deny all, then allow only required paths
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend # Only frontend can call API
- namespaceSelector:
matchLabels:
name: monitoring # Prometheus can scrape metrics
ports:
- protocol: TCP
port: 3000
egress:
- to:
- podSelector:
matchLabels:
app: postgres # API can only reach the DB
ports:
- protocol: TCP
port: 5432Storage: PersistentVolumes, PersistentVolumeClaims, StorageClasses
Container storage is ephemeral by default â data is lost when a container restarts. For stateful applications (databases, caches, file storage), Kubernetes provides the PersistentVolume (PV) subsystem. A PV is a piece of storage in the cluster provisioned by an administrator or dynamically by a StorageClass. A PersistentVolumeClaim (PVC) is a request for storage by a user, specifying size and access mode. Kubernetes binds PVCs to matching PVs.
StorageClasses allow dynamic provisioning: instead of pre-creating PVs, the cluster automatically provisions storage (EBS on AWS, Persistent Disk on GCP, Azure Disk) when a PVC is created. This is the standard approach on managed Kubernetes. StatefulSets use volumeClaimTemplates to give each Pod instance its own stable, uniquely-named PVC that persists across Pod rescheduling.
StorageClass, PVC, and StatefulSet
# storageclass.yaml â Dynamic provisioning on AWS
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
---
# pvc.yaml â Request 20Gi of SSD storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
---
# StatefulSet â PostgreSQL with per-pod stable storage
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: production
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1"
livenessProbe:
exec:
command: ["pg_isready", "-U", "admin", "-d", "myapp"]
initialDelaySeconds: 30
periodSeconds: 10
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
---
# Headless service for StatefulSet DNS
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: production
spec:
clusterIP: None # Headless â no virtual IP
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432Auto-scaling: HPA, VPA, and Cluster Autoscaler
Kubernetes provides three complementary autoscaling mechanisms operating at different levels of the stack. The Horizontal Pod Autoscaler (HPA) scales the number of Pod replicas based on metrics. The Vertical Pod Autoscaler (VPA) adjusts resource requests and limits for existing Pods. The Cluster Autoscaler adjusts the number of nodes in the cluster based on pending Pods and node utilization.
For most applications, HPA is the primary autoscaling tool. It queries the metrics API (CPU, memory, or custom metrics from Prometheus Adapter) and scales the target Deployment or StatefulSet up or down with configurable stabilization windows to prevent thrashing.
Horizontal Pod Autoscaler (HPA)
# hpa.yaml â Scale web-app pods based on CPU and memory
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 4
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60Vertical Pod Autoscaler (VPA)
# vpa.yaml â Automatically tune resource requests and limits
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: web
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: "2"
memory: 2Gi
controlledResources: ["cpu", "memory"]Test Autoscaling with Load Generation
# Apply HPA
kubectl apply -f hpa.yaml
# Check current HPA status
kubectl get hpa -n production
kubectl describe hpa web-app-hpa -n production
# Generate CPU load to trigger autoscaling
kubectl run load-generator \
--image=busybox:latest \
--rm -it \
--restart=Never \
-- /bin/sh -c "while true; do wget -q -O- http://web-app-svc/; done"
# In a second terminal: watch pods scale up
kubectl get pods -n production -w
# Watch HPA metrics update live
kubectl get hpa -n production -w
# Stop load generator (Ctrl+C), watch pods scale down after 5 min
# Cluster Autoscaler adds nodes when pods are Pending
kubectl get nodes -wKubernetes vs Docker Swarm vs HashiCorp Nomad
Kubernetes is not the only container orchestrator. Docker Swarm is simpler and built into Docker Engine. HashiCorp Nomad is a general-purpose orchestrator that handles containers, binaries, Java JARs, and more. Here is how they compare across the dimensions that matter most in production:
| Feature | Kubernetes | Docker Swarm | HashiCorp Nomad |
|---|---|---|---|
| Origin | Google â CNCF (2014) | Docker Inc. (2016) | HashiCorp (2015) |
| Workloads | OCI containers | Docker containers only | Containers, binaries, JARs, VMs |
| Learning Curve | High | Low | Medium |
| Setup Complexity | High (many components) | Very low (built-in to Docker) | Low (single binary) |
| Auto-scaling | HPA, VPA, Cluster Autoscaler | Basic / manual | Dynamic task placement |
| Self-healing | Full reschedule + restart | Basic restart policies | Job re-scheduling |
| Networking | CNI plugins, NetworkPolicy, full SDN | Overlay network, basic routing | Consul Connect, CNI plugins |
| Storage | PV/PVC/StorageClass ecosystem | Docker volumes | Host volumes, CSI plugins |
| Rolling Updates | Native, configurable | Built-in, limited options | Canary, blue-green, rolling |
| Service Discovery | CoreDNS + kube-proxy | Built-in DNS (VIP) | Consul (first-class) |
| RBAC | Granular, mature | Basic Swarm secrets | ACL policies via Consul/Vault |
| Ecosystem | Enormous (CNCF, Helm, Operators) | Small, Docker-centric | HashiCorp stack integration |
| Managed Cloud | EKS, GKE, AKS, DOKS, and more | Not offered by major clouds | HCP Nomad (HashiCorp Cloud) |
| Resource Overhead | High (~2GB control plane) | Minimal (embedded in Docker) | Low (~256MB single agent) |
| Best For | Large-scale microservices, cloud-native | Simple teams, small scale | Mixed workloads (containers + legacy) |
- Running 10+ microservices that need independent scaling
- Production workloads requiring zero-downtime deploys and auto-rollback
- Teams need fine-grained RBAC and namespace-level resource quotas
- You want the richest ecosystem (Helm, Operators, service mesh, Prometheus)
- You are deploying to AWS, GCP, or Azure with managed control plane offerings
- Small team with simple Docker-based apps running on 1-5 servers
- You want zero additional tooling beyond Docker Engine
- Quick setup is more valuable than advanced features
- Migrating from Docker Compose to basic orchestration with minimal relearning
- Limited budget for DevOps expertise
- Mixed workloads: containers AND legacy binaries, Java JARs, or VMs
- Already invested in the HashiCorp stack (Consul, Vault, Terraform)
- You need Kubernetes-grade features with significantly lower operational overhead
- Edge deployments or multi-datacenter workloads across heterogeneous infrastructure
- Compliance requires workload isolation without full Kubernetes complexity
Complete Production Application Example
The following manifest deploys a Node.js API with PostgreSQL, Ingress, and HPA in a single file. Apply with kubectl apply -f complete-app.yaml.
# complete-app.yaml
# 1. Namespace
apiVersion: v1
kind: Namespace
metadata:
name: myapp
labels:
environment: production
---
# 2. ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: myapp
data:
NODE_ENV: production
PORT: "3000"
LOG_LEVEL: info
---
# 3. Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: myapp
type: Opaque
stringData:
DATABASE_URL: postgres://app:secret@postgres:5432/myapp
JWT_SECRET: change-me-in-production
---
# 4. PostgreSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: myapp
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- { name: POSTGRES_DB, value: myapp }
- { name: POSTGRES_USER, value: app }
- { name: POSTGRES_PASSWORD, value: secret }
volumeMounts:
- { name: pg-data, mountPath: /var/lib/postgresql/data }
resources:
requests: { memory: 256Mi, cpu: 250m }
limits: { memory: 512Mi, cpu: "1" }
volumeClaimTemplates:
- metadata:
name: pg-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
---
# 5. Postgres Service
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: myapp
spec:
type: ClusterIP
selector:
app: postgres
ports:
- { port: 5432, targetPort: 5432 }
---
# 6. App Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: myapp
spec:
replicas: 3
selector:
matchLabels:
app: web-app
strategy:
type: RollingUpdate
rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: app
image: my-registry/web-app:1.0.0
ports:
- { containerPort: 3000 }
envFrom:
- configMapRef: { name: app-config }
- secretRef: { name: app-secrets }
resources:
requests: { memory: 128Mi, cpu: 100m }
limits: { memory: 256Mi, cpu: 500m }
livenessProbe:
httpGet: { path: /health, port: 3000 }
initialDelaySeconds: 15
readinessProbe:
httpGet: { path: /ready, port: 3000 }
initialDelaySeconds: 5
---
# 7. App Service
apiVersion: v1
kind: Service
metadata:
name: web-app-svc
namespace: myapp
spec:
type: ClusterIP
selector:
app: web-app
ports:
- { port: 80, targetPort: 3000 }
---
# 8. Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app-ingress
namespace: myapp
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [myapp.example.com]
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app-svc
port: { number: 80 }
---
# 9. HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
namespace: myapp
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 15
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Verify the Deployment
# Apply the entire stack at once
kubectl apply -f complete-app.yaml
# Verify all resources are created
kubectl get all -n myapp
# Watch pods reach Running state
kubectl get pods -n myapp -w
# Stream application logs
kubectl logs -n myapp -l app=web-app --tail=50 -f
# Test connectivity from inside the cluster
kubectl run test --image=curlimages/curl:latest --rm -it \
--restart=Never -n myapp \
-- curl http://web-app-svc/health
# Port-forward for local testing
kubectl port-forward -n myapp svc/web-app-svc 8080:80
# Open http://localhost:8080
# Check HPA status
kubectl get hpa -n myapp
# Check TLS certificate (cert-manager)
kubectl get certificate -n myapp
kubectl describe certificate myapp-tls -n myappTry our related developer tools
JSON Formatter · YAML Validator · Base64 Encoder · JWT Decoder · Cron Expression Generator
Frequently Asked Questions
What is the difference between a Pod and a container in Kubernetes?
A container is a single process with its own filesystem and process namespace. A Pod is Kubernetes abstraction layer that wraps one or more containers sharing the same network namespace and storage volumes. Containers inside a Pod communicate via localhost. In most cases a Pod contains one container, but the sidecar pattern uses multiple containers â for example, an app container plus an Envoy proxy for service mesh traffic management.
How do I debug a CrashLoopBackOff Pod?
CrashLoopBackOff means the container keeps crashing and Kubernetes keeps restarting it with exponential back-off. Debug with: (1) kubectl describe pod to read the Events section for error messages, (2) kubectl logs --previous to see logs from the last crashed instance, (3) kubectl exec -it to shell into the running container if it starts briefly, (4) check resource limits â OOMKilled means the container exceeded its memory limit.
What is Helm and when should I use it?
Helm is a package manager for Kubernetes. It bundles related Kubernetes manifests into reusable, versioned packages called charts. Use Helm when: (1) you deploy complex applications with 5+ interdependent resources, (2) you need per-environment configuration (dev, staging, prod) without duplicating manifests, (3) you want to install community-maintained software (PostgreSQL, Redis, NGINX Ingress, Prometheus) from Artifact Hub. For simple single-service apps, raw kubectl apply is often sufficient.
How much does running Kubernetes cost?
Kubernetes itself is free. Cluster costs include: managed control plane fees ($70-150/month on EKS/GKE/AKS), worker node compute ($50-500+/month depending on workload), load balancers ($15-20/month each), and persistent storage ($0.10-0.20/GB/month). A minimal production cluster on EKS might run $200-400/month. Lightweight alternatives like K3s on a single VPS can cost as little as $10-20/month.
What is the difference between a Deployment and a StatefulSet?
Deployments are for stateless applications where Pods are interchangeable. StatefulSets are for stateful applications (databases, queues) where Pods need: (1) stable, unique network identities (pod-0, pod-1, pod-2), (2) stable persistent storage that follows the Pod when rescheduled, (3) ordered, graceful deployment and scaling. Use Deployments for web servers and APIs; use StatefulSets for PostgreSQL, MongoDB, Kafka, Elasticsearch, and ZooKeeper.
How does Kubernetes handle zero-downtime deployments?
Kubernetes Deployments use a RollingUpdate strategy by default. With maxUnavailable: 0 and maxSurge: 1, Kubernetes creates one new Pod with the updated image, waits for it to become Ready (pass readiness probes), then terminates one old Pod, and repeats until all replicas are updated. Combined with pre-stop hooks and a terminationGracePeriodSeconds, running requests complete before the old Pod is killed, achieving true zero-downtime deploys.
What managed Kubernetes service should I choose: EKS, GKE, or AKS?
Google GKE is generally considered the most feature-complete and operationally excellent â unsurprisingly, since Google invented Kubernetes. AWS EKS integrates deeply with the AWS ecosystem (IAM, ALB, EBS) and is the best choice if you are already all-in on AWS. Azure AKS is the natural choice for Microsoft-centric shops using Azure AD and Azure DevOps. All three provide autopilot/serverless node management modes that further reduce operational overhead.
How do I expose a Kubernetes application to the internet?
You have three options: (1) NodePort: exposes the service on a static port on each node IP â simplest but not production-grade, (2) LoadBalancer: provisions a cloud load balancer automatically â simplest for cloud environments but creates one LB per service (expensive), (3) Ingress: a single entry point that routes HTTP/HTTPS traffic to multiple services based on host and path rules â the recommended production pattern. Use an Ingress controller (NGINX, Traefik, AWS ALB Ingress) and cert-manager for automatic TLS certificates.
Conclusion
Kubernetes is one of the most transformative technologies in modern infrastructure, but it comes with genuine complexity. The key insight is to learn it incrementally: start with Pods and Deployments on a local cluster (minikube or kind), add Services and Ingress, then graduate to Helm for packaging, HPA for autoscaling, and PersistentVolumes for stateful workloads. In production, use a managed service (EKS, GKE, AKS) so you can focus on your application rather than cluster operations. The investment in learning Kubernetes pays dividends for years â it is a foundational skill for any developer working in cloud-native environments.