← Back to Guides

Kubernetes from Scratch

📖 19 min read | 📅 Updated: January 2025 | 🏷️ DevOps & Cloud

Introduction

Kubernetes (K8s) is the leading container orchestration platform. This comprehensive guide covers pods, services, deployments, configuration, scaling, networking, storage, and production-ready practices for managing containerized applications at scale.

1. Kubernetes Architecture

Kubernetes Cluster Components:

Control Plane (Master):
├── API Server - Frontend for K8s control plane
├── etcd - Key-value store for cluster data
├── Scheduler - Assigns pods to nodes
├── Controller Manager - Runs controller processes
└── Cloud Controller Manager - Cloud-specific logic

Worker Nodes:
├── Kubelet - Ensures containers are running
├── Kube-proxy - Network proxy
└── Container Runtime - Docker/containerd/CRI-O

# Install kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

# Local development cluster
# Minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube start

# Kind (Kubernetes in Docker)
kind create cluster

# Verify
kubectl cluster-info
kubectl get nodes

2. Pods - Smallest Deployable Unit

# Simple pod definition
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: nginx
    environment: production
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

# Apply configuration
kubectl apply -f pod.yaml

# Pod with multiple containers
apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:1.0
    ports:
    - containerPort: 3000
    env:
    - name: DATABASE_URL
      value: "postgres://db:5432/mydb"
  
  - name: sidecar-logger
    image: fluent/fluent-bit
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
  
  volumes:
  - name: logs
    emptyDir: {}

# Pod commands
kubectl get pods
kubectl get pods -o wide
kubectl describe pod nginx-pod
kubectl logs nginx-pod
kubectl logs -f nginx-pod -c container-name  # Follow logs
kubectl exec -it nginx-pod -- bash
kubectl delete pod nginx-pod
kubectl port-forward nginx-pod 8080:80

3. Deployments - Declarative Updates

# Deployment definition
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-deployment
  labels:
    app: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myapi:1.0.0
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: url
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "500m"
          limits:
            memory: "512Mi"
            cpu: "1000m"

# Deployment commands
kubectl apply -f deployment.yaml
kubectl get deployments
kubectl describe deployment api-deployment
kubectl scale deployment api-deployment --replicas=5
kubectl rollout status deployment api-deployment
kubectl rollout history deployment api-deployment
kubectl rollout undo deployment api-deployment

# Update image
kubectl set image deployment/api-deployment api=myapi:1.1.0

# Rolling update strategy
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # Max pods above desired during update
      maxUnavailable: 0  # Max pods unavailable during update

4. Services - Network Abstraction

# ClusterIP (internal only)
apiVersion: v1
kind: Service
metadata:
  name: api-service
spec:
  type: ClusterIP
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 3000
    protocol: TCP

# NodePort (external access via node IP)
apiVersion: v1
kind: Service
metadata:
  name: api-nodeport
spec:
  type: NodePort
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 3000
    nodePort: 30080  # Optional: 30000-32767

# LoadBalancer (cloud provider LB)
apiVersion: v1
kind: Service
metadata:
  name: api-loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 3000

# Headless service (direct pod IPs)
apiVersion: v1
kind: Service
metadata:
  name: database-headless
spec:
  clusterIP: None
  selector:
    app: database
  ports:
  - port: 5432

# Service commands
kubectl get services
kubectl describe service api-service
kubectl get endpoints api-service

# DNS resolution
# service-name.namespace.svc.cluster.local
# Example: api-service.default.svc.cluster.local

5. ConfigMaps & Secrets

# ConfigMap - non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  app.properties: |
    server.port=3000
    log.level=info
  database.host: "postgres.default.svc.cluster.local"
  redis.host: "redis.default.svc.cluster.local"

# Create from file
kubectl create configmap app-config --from-file=config.json

# Use in pod
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: myapp:1.0
    # As environment variables
    envFrom:
    - configMapRef:
        name: app-config
    # As volume
    volumeMounts:
    - name: config
      mountPath: /etc/config
  volumes:
  - name: config
    configMap:
      name: app-config

# Secret - sensitive data (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: cG9zdGdyZXM=  # base64 encoded
  password: c2VjcmV0MTIz
stringData:
  url: "postgres://user:pass@host:5432/db"  # Auto-encoded

# Create secret
kubectl create secret generic db-secret \
  --from-literal=username=postgres \
  --from-literal=password=secret123

# Use secret
spec:
  containers:
  - name: app
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password

6. Persistent Volumes

# PersistentVolume (admin creates)
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: standard
  hostPath:
    path: /mnt/data

# PersistentVolumeClaim (user requests)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard

# Use in pod
apiVersion: v1
kind: Pod
metadata:
  name: postgres
spec:
  containers:
  - name: postgres
    image: postgres:15
    volumeMounts:
    - mountPath: /var/lib/postgresql/data
      name: postgres-storage
  volumes:
  - name: postgres-storage
    persistentVolumeClaim:
      claimName: postgres-pvc

# StatefulSet for stateful applications
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

7. Ingress - HTTP Routing

# Install Ingress Controller (nginx)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/cloud/deploy.yaml

# Ingress resource
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: api-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

# Multiple hosts
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-host-ingress
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
  - host: admin.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 80

kubectl get ingress
kubectl describe ingress app-ingress

8. Namespaces & Resource Quotas

# Create namespace
apiVersion: v1
kind: Namespace
metadata:
  name: production

kubectl create namespace production
kubectl get namespaces

# Use namespace
kubectl apply -f deployment.yaml -n production
kubectl get pods -n production
kubectl get all -n production

# Set default namespace
kubectl config set-context --current --namespace=production

# ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    limits.cpu: "20"
    limits.memory: 40Gi
    persistentvolumeclaims: "10"
    pods: "20"

# LimitRange (default limits per pod)
apiVersion: v1
kind: LimitRange
metadata:
  name: production-limits
  namespace: production
spec:
  limits:
  - max:
      cpu: "2"
      memory: "2Gi"
    min:
      cpu: "100m"
      memory: "64Mi"
    default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "200m"
      memory: "256Mi"
    type: Container

9. Horizontal Pod Autoscaling

# HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-deployment
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Percent
        value: 100
        periodSeconds: 30
      - type: Pods
        value: 2
        periodSeconds: 30

# Install metrics server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# Check HPA
kubectl get hpa
kubectl describe hpa api-hpa
kubectl top pods
kubectl top nodes

10. Production Best Practices

# Complete production deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-production
  namespace: production
  labels:
    app: api
    version: v1.0.0
spec:
  replicas: 3
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: api
        version: v1.0.0
    spec:
      # Security
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      
      # Init container
      initContainers:
      - name: wait-for-db
        image: busybox
        command: ['sh', '-c', 'until nc -z postgres 5432; do sleep 1; done']
      
      containers:
      - name: api
        image: myapi:1.0.0
        imagePullPolicy: Always
        
        ports:
        - containerPort: 3000
          name: http
          protocol: TCP
        
        # Environment from ConfigMap & Secret
        envFrom:
        - configMapRef:
            name: api-config
        - secretRef:
            name: api-secret
        
        # Health checks
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 2
        
        # Resources
        resources:
          requests:
            memory: "256Mi"
            cpu: "500m"
          limits:
            memory: "512Mi"
            cpu: "1000m"
        
        # Security
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL
        
        # Volumes
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: cache
          mountPath: /app/cache
      
      volumes:
      - name: tmp
        emptyDir: {}
      - name: cache
        emptyDir: {}
      
      # Pod anti-affinity (spread across nodes)
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: api
              topologyKey: kubernetes.io/hostname

# Network Policy (restrict traffic)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-network-policy
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 3000
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - protocol: TCP
      port: 5432

11. Monitoring & Debugging

# Useful commands
kubectl get all -A                    # All resources, all namespaces
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl describe pod pod-name
kubectl logs pod-name -c container-name --previous
kubectl exec -it pod-name -- sh
kubectl cp pod-name:/path/to/file ./local-file
kubectl port-forward pod-name 8080:3000
kubectl attach pod-name -i

# Debug pod
kubectl run debug --image=alpine --rm -it -- sh

# View resource usage
kubectl top nodes
kubectl top pods --all-namespaces --sort-by=memory

# Check cluster health
kubectl get componentstatuses
kubectl cluster-info dump

# YAML output
kubectl get deployment api-deployment -o yaml
kubectl get pod pod-name -o json | jq '.spec.containers[0].image'

12. Best Practices Checklist

✓ Kubernetes Best Practices:

Conclusion

Kubernetes provides powerful container orchestration capabilities. Master pods, services, deployments, and scaling to build resilient applications. Always follow security best practices, implement proper monitoring, and test disaster recovery procedures.

💡 Pro Tip: Use Helm charts for packaging and deploying complex applications. Helm is the package manager for Kubernetes that simplifies deployment with templating and versioning. Start with existing charts from Artifact Hub and customize for your needs.