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
Installing kubectl and Minikube
kubectl get pods -w
$ 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
Local Kubernetes cluster running

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"
Single-container pod definition
# 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
Managing pods with kubectl

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: {}
Multi-container pod with sidecar pattern

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
Complete deployment with probes and resource limits
# 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
Deployment management commands

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
Different service types
# 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
Service operations

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
ConfigMaps for environment and file configuration

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
Creating secrets
# 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
Using secrets in deployments

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
Complete three-tier application manifest
# 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
Deploying and testing the complete application

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
Essential kubectl commands for daily use

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:

Gataya Med

DevOps Engineer & Backend Developer. Sharing insights on cloud, automation, and scalable systems.

Comments (0)

Sarah Chen January 24, 2025

This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!

Reply

Leave a Comment