Docker runs containers on one machine. Kubernetes runs containers across a fleet of machines. When you need high availability, automatic scaling, zero-downtime deployments, and self-healing infrastructure—Kubernetes is the answer. It's the operating system of the cloud-native world, and it's required knowledge for every DevOps engineer. In this comprehensive guide, you'll learn Kubernetes from the ground up.
1. Learning Objectives
By the end of this lesson, you will be able to:
- Understand Kubernetes architecture (control plane vs worker nodes)
- Create and manage Pods (smallest deployable units)
- Use Deployments for rolling updates and rollbacks
- Expose applications with Services (ClusterIP, NodePort, LoadBalancer)
- Manage configuration with ConfigMaps and Secrets
- Set up local Kubernetes with Minikube or kind
- Deploy a complete multi-tier application
2. Why Kubernetes for DevOps?
Real-world scenario: Your startup now has 100 microservices running in Docker containers across 50 servers. A server dies at 3 AM. Without Kubernetes, you wake up to pager alerts. With Kubernetes, it automatically reschedules containers on healthy nodes, restarts failed containers, and maintains availability.
What Kubernetes gives you:
- ✅ Self-healing (restarts failed containers, reschedules when nodes die)
- ✅ Horizontal scaling (kubectl scale or auto-scaling)
- ✅ Rolling updates (zero-downtime deployments)
- ✅ Service discovery and load balancing
- ✅ Secret and configuration management
- ✅ Storage orchestration (volumes, persistent storage)
3. Core Concepts
Kubernetes Architecture
Control Plane (Master): Manages the cluster
- API Server: Front door to Kubernetes (kubectl talks here)
- etcd: Distributed key-value store (cluster state)
- Scheduler: Assigns pods to nodes
- Controller Manager: Runs controllers (deployment, node, etc.)
Worker Nodes: Run your applications
- kubelet: Agent that runs on each node
- kube-proxy: Network rules and load balancing
- Container Runtime: Docker, containerd, CRI-O
Setting Up Local Kubernetes
# Install kubectl (Kubernetes CLI)
# Linux
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
# macOS
brew install kubectl
# Windows (choco)
choco install kubernetes-cli
# Install Minikube (local Kubernetes)
# Linux
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# macOS
brew install minikube
# Start cluster
minikube start --cpus=4 --memory=8192
# Verify cluster
kubectl cluster-info
kubectl get nodes
$ minikube start
😄 minikube v1.32.0 on Ubuntu 22.04
✨ Using the docker driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🔥 Creating docker container (CPUs=4, Memory=8192MB)
🐳 Preparing Kubernetes v1.28.3 on Docker 24.0.7
🔎 Verifying Kubernetes components...
🌟 Enabled addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use "minikube" cluster
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane 30s v1.28.3
Pods: The Smallest Deployable Unit
# pod-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
environment: production
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
# Create pod
kubectl apply -f pod-nginx.yaml
# List pods
kubectl get pods
# Get detailed pod info
kubectl describe pod nginx-pod
# Port forward to access pod
kubectl port-forward pod/nginx-pod 8080:80
# Execute command in pod
kubectl exec -it nginx-pod -- /bin/bash
# View pod logs
kubectl logs nginx-pod
# Delete pod
kubectl delete pod nginx-pod
Multi-Container Pods (Sidecar Pattern)
# pod-sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
# Main application container
- name: app
image: nginx:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
# Sidecar container (logs collector)
- name: log-collector
image: busybox
command: ["/bin/sh"]
args: ["-c", "tail -f /var/log/nginx/access.log"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
volumes:
- name: shared-logs
emptyDir: {}
Deployments: Managing Pod Lifecycles
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp-deployment
labels:
app: webapp
spec:
replicas: 3 # Run 3 pods
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx:1.25
ports:
- containerPort: 80
env:
- name: ENVIRONMENT
value: "production"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# Create deployment
kubectl apply -f deployment.yaml
# Check deployment status
kubectl get deployments
kubectl get pods
# Scale deployment
kubectl scale deployment webapp-deployment --replicas=5
# Auto-scaling (based on CPU)
kubectl autoscale deployment webapp-deployment --cpu-percent=50 --min=3 --max=10
# Update image (rolling update)
kubectl set image deployment/webapp-deployment webapp=nginx:1.26
# Check rollout status
kubectl rollout status deployment/webapp-deployment
# Rollback to previous version
kubectl rollout undo deployment/webapp-deployment
# View rollout history
kubectl rollout history deployment/webapp-deployment
# Rollback to specific revision
kubectl rollout undo deployment/webapp-deployment --to-revision=2
# Pause rollout (for canary deployments)
kubectl rollout pause deployment/webapp-deployment
# Resume rollout
kubectl rollout resume deployment/webapp-deployment
Services: Exposing Applications
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: webapp-service
spec:
type: ClusterIP # Default - internal only
selector:
app: webapp # Routes to pods with this label
ports:
- port: 80 # Service port
targetPort: 80 # Container port
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: webapp-nodeport
spec:
type: NodePort # Exposes on each node's IP at high port (30000-32767)
selector:
app: webapp
ports:
- port: 80
targetPort: 80
nodePort: 30080
---
apiVersion: v1
kind: Service
metadata:
name: webapp-loadbalancer
spec:
type: LoadBalancer # Cloud load balancer (AWS ELB, Azure LB, GCP LB)
selector:
app: webapp
ports:
- port: 80
targetPort: 80
# Create service
kubectl apply -f service.yaml
# List services
kubectl get services
# Access ClusterIP (internal only)
kubectl port-forward service/webapp-service 8080:80
# Access NodePort (if using Minikube)
minikube service webapp-nodeport
# Get service endpoint
kubectl get svc webapp-loadbalancer
# Service discovery via DNS
# Inside cluster: webapp-service.default.svc.cluster.local
ConfigMaps: Configuration Management
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# Key-value pairs
DATABASE_HOST: "postgres-service"
DATABASE_PORT: "5432"
LOG_LEVEL: "INFO"
# Configuration file
nginx.conf: |
server {
listen 80;
location / {
proxy_pass http://app:3000;
}
}
---
# Using ConfigMap in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
spec:
template:
spec:
containers:
- name: app
image: myapp:latest
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST
- name: DATABASE_PORT
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_PORT
envFrom:
- configMapRef:
name: app-config # Load all keys
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: app-config
Secrets: Sensitive Data
# Create secret from literal
kubectl create secret generic db-secret \
--from-literal=DATABASE_PASSWORD=SuperSecret123 \
--from-literal=API_KEY=abc123xyz
# Create secret from file
kubectl create secret generic ssl-cert \
--from-file=./cert.pem \
--from-file=./key.pem
# Create secret from .env file
kubectl create secret generic app-secrets \
--from-env-file=.env
# secret.yaml (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
DATABASE_PASSWORD: U3VwZXJTZWNyZXQxMjM= # echo -n "SuperSecret123" | base64
data:
API_KEY: YWJjMTIzeHl6
---
# Using secret in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
spec:
template:
spec:
containers:
- name: app
image: myapp:latest
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DATABASE_PASSWORD
envFrom:
- secretRef:
name: db-secret
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: app-secrets
4. Complete Project: Three-Tier Application
# complete-app.yaml - Frontend + Backend + Database
# PostgreSQL Deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-deployment
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_USER
value: "myuser"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: POSTGRES_DB
value: "mydb"
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
# PostgreSQL Service
apiVersion: v1
kind: Service
metadata:
name: postgres-service
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
---
# Redis Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deployment
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
---
# Redis Service
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
---
# Backend API Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: mybackend:latest
ports:
- containerPort: 5000
env:
- name: DATABASE_URL
value: "postgresql://myuser:password@postgres-service:5432/mydb"
- name: REDIS_URL
value: "redis-service:6379"
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 30
---
# Backend Service (ClusterIP)
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
- port: 5000
targetPort: 5000
---
# Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: myfrontend:latest
ports:
- containerPort: 3000
env:
- name: API_URL
value: "http://backend-service:5000"
---
# Frontend Service (LoadBalancer)
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: LoadBalancer
selector:
app: frontend
ports:
- port: 80
targetPort: 3000
# Create secret first
kubectl create secret generic postgres-secret --from-literal=password=SecurePassword
# Deploy the complete application
kubectl apply -f complete-app.yaml
# Check all resources
kubectl get all
# Watch pods come online
kubectl get pods -w
# Check service endpoints
kubectl get svc
# Test the application
kubectl port-forward service/frontend-service 8080:80
# Visit http://localhost:8080
# Check logs if something fails
kubectl logs -l app=backend
# Describe problematic pod
kubectl describe pod -l app=backend
5. Useful Kubectl Commands
# Context switching
kubectl config get-contexts
kubectl config use-context minikube
# Namespaces
kubectl get namespaces
kubectl create namespace dev
kubectl get pods -n dev
# Pod troubleshooting
kubectl logs pod-name --previous
kubectl exec -it pod-name -- /bin/bash
# Resource management
kubectl top nodes # Show CPU/Memory usage
kubectl top pods
# Debugging
kubectl describe deployment webapp
kubectl get events --sort-by='.lastTimestamp'
# YAML generation
kubectl run nginx --image=nginx --dry-run=client -o yaml
kubectl create deployment webapp --image=nginx --dry-run=client -o yaml
# Port forwarding
kubectl port-forward pod/nginx-pod 8080:80
kubectl port-forward service/backend-service 5000:5000
# Copy files
kubectl cp pod-name:/path/to/file ./local-file
kubectl cp ./local-file pod-name:/path/to/file
6. Common Errors & Solutions
7. Summary Checklist
8. Next Steps
Advanced Kubernetes topics to learn next:
- Ingress: HTTP/HTTPS routing to services
- Helm: Package manager for Kubernetes
- Operators: Custom controllers for stateful apps
- Service Mesh (Istio): Advanced traffic management
- RBAC: Role-based access control
Practice resources:
- KillerCoda Kubernetes Scenarios - Free interactive labs
- Kubernetes Official Tutorials
- Play with Minikube
- KodeKloud - Hands-on Kubernetes training
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment