k8s

Kubernetes: What, When, Where & How — A Practical, Copy‑Paste Tutorial

Copy this whole file into your blog/docs (e.g. content/kubernetes-what-when-where-how.md). It covers what Kubernetes is, when to use it, where it runs / where things live, and how to set it up & migrate from plain EC2 + Docker.


TL;DR

  • What: Kubernetes (K8s) orchestrates containers: deploy, heal, scale, route, secure, observe.

  • When: Use for multi-service apps, frequent deploys, autoscaling, and zero-downtime. Don’t use for tiny/simple stacks that a single VM + Docker Compose handles fine.

  • Where: On any Linux VMs (cloud/bare‑metal). Needs: container runtime (containerd), CNI (Flannel/Calico/Cilium), Ingress controller, Storage (CSI or local‑path), Registry for images, and a place to keep configs/secrets.

  • How: Start simple with K3s single‑node (fast) or go kubeadm multi‑node (production control). Migrate each service to a Deployment + Service + Ingress, move envs to ConfigMap/Secret, add probes, resources, and (optionally) HPA.


1) What: Core Concepts (fast but complete)

Kubernetes primitives

  • Node: a VM/physical server in the cluster (e.g., an EC2 instance).
  • Pod: the smallest deployable unit (usually 1 container). K8s schedules pods on nodes.
  • Deployment: desired state for pods with rolling updates & self‑healing.
  • Service: stable virtual IP/name to reach pods (ClusterIP/NodePort/LoadBalancer).
  • Ingress: HTTP(S) routing from the Internet to Services (with an Ingress Controller like NGINX/Traefik).
  • ConfigMap / Secret: configuration/env vars (Secret is base64‑encoded, use KMS/SealedSecrets for real security).
  • Volume / PersistentVolume (PV) / PersistentVolumeClaim (PVC): stateful storage for pods.
  • Namespace: logical isolation inside a cluster (e.g., dev, prod).

Kubernetes superpowers

  • Self‑healing (restart pods), desired state (reconcile loops), rolling deploys/rollback, autoscaling, service discovery, resource quotas/limits, RBAC, observability hooks.

2) When: Should You Use Kubernetes?

Use K8s when one or more of these is true:

  • You have > 5 services (or expect to), and/or deploy frequently (≥ 5 times/week).
  • Need zero‑downtime deploys, automatic recovery, blue‑green/canary.
  • Traffic spikes (flash sales, events) → want autoscaling.
  • Multiple teams → need standardized operations (RBAC, quotas, CI/CD).
  • Multi‑tenant, strict isolation, or need to treat infrastructure as code.

Avoid/Delay K8s when:

  • 1–2 apps, steady traffic, rare deploys → Docker Compose + Nginx on a single VM is faster/cheaper.
  • Team lacks ops bandwidth: K8s adds operational complexity and you must run upgrades, monitoring, backups.

Rule of thumb: Start simple; adopt K8s when release frequency, service count, or SLA demands it.


3) Where: Runtime, Networking, Storage, Registry, Config

Where does K8s run?

  • Anywhere with Linux VMs: cloud (EC2/GCE), bare‑metal, on‑prem, edge. You don’t have to use managed offerings (EKS/GKE/AKS) if you prefer DIY.

Where do my containers run?

  • On nodes using a container runtime (usually containerd). K8s schedules pods across nodes.

Where do services get IPs?

  • CNI plugin (Flannel/Calico/Cilium) provides Pod networking. Services get stable virtual IPs inside the cluster.

Where does inbound traffic enter?

  • Via an Ingress Controller (NGINX/Traefik) listening on node ports/LB. Public DNS points to the controller.

Where is data stored?

  • For stateful workloads: use CSI drivers (e.g., AWS EBS CSI) or simpler local‑path (single‑node dev). PVCs bind to PVs.

Where are images kept?

  • A container registry: Docker Hub, GHCR, GitLab Registry, or AWS ECR. CI pushes images; K8s pulls them.

Where are configs/secrets?

  • ConfigMap and Secret (optionally sealed/encrypted). Infrastructure configs live in Git (Helm/Kustomize).

4) How: Two Setup Paths

Path A — K3s (single‑node) — quickest way to get started

Good for dev/POC or small prod where a single VM is acceptable.

# Ubuntu VM (e.g., EC2)
sudo apt-get update -y && sudo apt-get upgrade -y
curl -sfL https://get.k3s.io | sh -

# Kubeconfig for kubectl
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config

# Verify
kubectl get nodes
kubectl get pods -A

By default, K3s brings Flannel (CNI), Traefik (Ingress), and local‑path storage. You can swap Traefik for NGINX Ingress if you prefer.

Path B — kubeadm (multi‑node) — minimal production topology

For 1 control‑plane + ≥1 worker. You manage all pieces.

# On all nodes: disable swap, prepare kernel modules
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay && sudo modprobe br_netfilter
cat <<'EOF' | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sudo sysctl --system

# Install containerd + kubeadm/kubelet/kubectl (Ubuntu; v1.30 as an example)
# (omitted full repo setup for brevity; follow official docs for your distro)

# On control-plane:
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
mkdir -p $HOME/.kube
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# Install a CNI (Flannel shown)
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

# On workers: join using the token/sha printed by kubeadm init
sudo kubeadm join <CP_PRIVATE_IP>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>

Then install Ingress NGINX, MetalLB (if you want LoadBalancer type on bare VMs), metrics‑server, cert‑manager (TLS), and your storage solution (EBS CSI / OpenEBS / local‑path).


5) How: From EC2 + Docker → Kubernetes (Migration)

Step‑by‑step

  1. Harden your images: add /health endpoint, small base images, proper CMD, clear listening port.

  2. Move env/config:

    • Non‑secrets → ConfigMap; secrets → Secret (optionally Sealed Secrets).
  3. Model each service:

    • Deployment (replicas, probes, resources) + Service (ClusterIP) + Ingress.
  4. Expose to the Internet: Ingress rule per host/path; point DNS to the Ingress entrypoint.

  5. Add autoscaling: install metrics‑server, then HorizontalPodAutoscaler.

  6. Cut over: run K8s side‑by‑side, route small % traffic (canary) or flip DNS; monitor logs/metrics; rollback quickly if needed.

Compose → K8s mapping

Docker ComposeKubernetes equivalent
services.<name>.imageDeployment.spec.template.containers[].image
ports: "80:3000"Service (ClusterIP) + Ingress rules
environment:ConfigMap / Secret
volumes:PersistentVolumeClaim + VolumeMount
depends_on:
(usually handled by probes & readiness)

Minimal manifests (example: one backend on port 3000)

apiVersion: v1
kind: Namespace
metadata: { name: prod }
---
apiVersion: v1
kind: ConfigMap
metadata: { name: app-a-config, namespace: prod }
data:
  NODE_ENV: "production"
---
apiVersion: v1
kind: Secret
metadata: { name: app-a-secret, namespace: prod }
type: Opaque
stringData:
  DATABASE_URL: "postgres://user:pass@host:5432/db"
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: app-a, namespace: prod }
spec:
  replicas: 2
  selector: { matchLabels: { app: app-a } }
  template:
    metadata: { labels: { app: app-a } }
    spec:
      containers:
        - name: app-a
          image: ghcr.io/you/app-a:1.2.3
          ports: [{ containerPort: 3000 }]
          envFrom:
            - configMapRef: { name: app-a-config }
            - secretRef: { name: app-a-secret }
          readinessProbe:
            httpGet: { path: /health, port: 3000 }
            initialDelaySeconds: 5
          livenessProbe:
            httpGet: { path: /health, port: 3000 }
            initialDelaySeconds: 15
          resources:
            requests: { cpu: "100m", memory: "128Mi" }
            limits:   { cpu: "500m", memory: "512Mi" }
---
apiVersion: v1
kind: Service
metadata: { name: app-a, namespace: prod }
spec:
  selector: { app: app-a }
  ports: [{ port: 80, targetPort: 3000 }]
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: apps
  namespace: prod
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx # or traefik if using Traefik
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /app-a
            pathType: Prefix
            backend: { service: { name: app-a, port: { number: 80 } } }

Autoscale

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: app-a, namespace: prod }
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app-a
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

6) Operating Checklist (Day‑2)

  • Probes (liveness/readiness) for every service.
  • Resources (requests/limits) to prevent noisy neighbors & enable HPA.
  • TLS via cert‑manager + Let’s Encrypt.
  • Observability: metrics‑server → HPA; Prometheus/Grafana for metrics; Loki/Promtail for logs.
  • Backups: for any stateful storage/DB.
  • Upgrades: plan for K8s version bumps and node OS patching.
  • RBAC & Namespaces: enforce least privilege and quotas.

7) FAQ: Is it just scaling containers or EC2s?

  • K8s scales pods (containers) first on the nodes you already have.
  • When nodes run out of CPU/RAM, you add nodes (more EC2s). You can automate this with Cluster Autoscaler, but it requires extra setup on DIY clusters.
  • Beyond scaling, K8s gives rolling updates, health management, service discovery, configuration, security, and visibility — the platform around your containers.

8) Troubleshooting Cheatsheet

# What’s running?
kubectl get nodes
kubectl get pods -A

# Describe why a pod isn’t ready
kubectl describe pod <name> -n <ns>

# Inspect logs
kubectl logs deploy/app-a -n prod
kubectl logs pod/<pod-name> -n prod --previous

# Port-forward to debug locally
kubectl port-forward svc/app-a 8080:80 -n prod

# Apply and diff manifests
kubectl apply -f k8s/
kubectl diff -f k8s/

# Exec into a pod
kubectl exec -it deploy/app-a -n prod -- sh

9) Next Steps / Where to go from here

  • Use Helm or Kustomize to template manifests per env.
  • Gate /editor or internal tools behind an Ingress + auth.
  • Introduce GitOps (Argo CD/Flux) to reconcile from Git.
  • If you outgrow single‑node: add workers (k3s agent) or move to kubeadm cluster.

You now have a practical mental model of Kubernetes (what/when/where/how) and a working migration path from plain EC2 + Docker.