Overview

Kubernetes (K8s) is the de facto standard for orchestrating containers at scale. It handles scheduling, scaling, self-healing, and service discovery. This tutorial explains the core objects you need to understand before touching a production cluster.

Why Kubernetes Exists

Running a single Docker container is easy. Running hundreds across multiple machines with rolling updates, automatic restarts, and load balancing is not. Kubernetes solves this by declaring the desired state of your application and continuously reconciling reality to match it.

Problem Kubernetes solution
Container crashes Automatic restart via ReplicaSet
Traffic spikes Horizontal Pod Autoscaler
Rolling updates Deployment strategy with zero downtime
Service discovery ClusterIP and DNS-based service names
Configuration drift Declarative manifests reconciled continuously

Cluster Architecture

Component Role
Control plane API server, scheduler, controller manager, etcd
Worker node Runs kubelet and container runtime; hosts pods
kubelet Agent that ensures containers are running on its node
kube-proxy Network rules for service routing

Core Objects

Pod

A Pod is the smallest deployable unit. It wraps one or more containers that share a network namespace and storage volumes. In practice, most pods run a single container.

Deployment

A Deployment manages ReplicaSets, which in turn manage Pods. It handles rolling updates, rollbacks, and replica count.

Service

A Service gives a stable IP and DNS name to a group of pods. It decouples clients from the ephemeral nature of pod IPs.

Service type Use case
ClusterIP Internal-only access within the cluster (default)
NodePort Exposes the service on each node's IP at a static port
LoadBalancer Provisions a cloud load balancer
ExternalName Maps to an external DNS name

Your First Deployment

Save this as deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi

Apply it:

kubectl apply -f deployment.yaml
kubectl get deployments
kubectl get pods -l app=web

Exposing the Deployment

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
  type: ClusterIP
kubectl apply -f service.yaml
kubectl get svc web

Other pods can now reach this service at http://web.default.svc.cluster.local.

Scaling and Rolling Updates

kubectl scale deployment web --replicas=5
kubectl set image deployment/web web=nginx:1.28
kubectl rollout status deployment/web
kubectl rollout undo deployment/web

By default, Deployments use a rolling update strategy that keeps a configurable number of pods available throughout the change.

Configuration with ConfigMaps and Secrets

kubectl create configmap app-config --from-literal=LOG_LEVEL=info
kubectl create secret generic db-creds --from-literal=password=s3cr3t

Mount them into a pod:

envFrom:
  - configMapRef:
      name: app-config
  - secretRef:
      name: db-creds

Health Checks

Probe Purpose
livenessProbe Restart the container if it is unhealthy
readinessProbe Stop sending traffic if the pod is not ready
startupProbe Delay liveness checks until the app has booted
livenessProbe:
  httpGet:
    path: /healthz
    port: 80
  initialDelaySeconds: 10
  periodSeconds: 15

Local Clusters for Practice

Common Beginner Mistakes

  • Forgetting to set resource requests and limits, causing noisy-neighbor problems.
  • Using latest image tags; pin a specific version for reproducible rollouts.
  • Baking configuration into images instead of using ConfigMaps and Secrets.
  • Ignoring the namespace field and deploying everything into default.