article thumbnail
Containerization (serverless computing)
A deep dive into containerization - Docker & Kubernetes
#containerization, #docker, #kubernetes

Advanced Guide to Containerization and Kubernetes

Introduction: Beyond the Basics

Containers have transformed the way we package and deploy applications. Unlike virtual machines, containers share the host kernel while isolating processes. This makes them lightweight, portable, and fast to start. But running containers at scale is not as simple as docker run. In this article, we'll explore advanced containerization topics with practical examples, focusing on Kubernetes as the de facto orchestrator.


Building Secure and Efficient Images

A container image is the foundation of your workload. Poorly designed images lead to slow deployments, security risks, and wasted storage.

Example: Multi-stage Dockerfile

# Stage 1: Builder
FROM golang:1.20 AS builder
WORKDIR /src
COPY . .
RUN go build -o app .

# Stage 2: Runtime
FROM gcr.io/distroless/base
COPY --from=builder /src/app /app
CMD ["/app"]

Explanation:

Security Scanning

Before pushing, scan the image:

trivy image myregistry/myapp:1.0.0

This reports CVEs and vulnerable dependencies.


Kubernetes Runtime: Docker vs containerd

Kubernetes originally supported Docker directly via dockershim. As of v1.24, dockershim was removed. Kubernetes now relies on CRI-compliant runtimes:

What this means: You can still build images with Docker, but Kubernetes runs them using containerd or CRI-O under the hood. Tooling that depends on the Docker socket may break and should be updated.


State and Storage

Containers are ephemeral, but many applications need persistence. Kubernetes handles this through Persistent Volumes (PVs) and Persistent Volume Claims (PVCs).

Example: PVC and StatefulSet

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: "postgres"
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

Explanation:


Networking Deep Dive

Kubernetes networking is more than just exposing a Service. You often need fine-grained traffic control and security.

Example: Ingress with NGINX

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80

Explanation: This routes traffic from myapp.example.com to the internal service.

Example: NetworkPolicy

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
spec:
  podSelector:
    matchLabels:
      role: backend
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend

This allows only frontend pods to connect to backend pods.


Scaling and Deployment Strategies

Kubernetes supports several deployment strategies beyond "scale replicas up or down."

Example: Canary Deployment with Istio

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
  - myapp.example.com
  http:
  - route:
    - destination:
        host: myapp
        subset: stable
      weight: 90
    - destination:
        host: myapp
        subset: canary
      weight: 10

Explanation:


Security in Action

Security should be applied at multiple layers: cluster, pod, and runtime.

Example: Restricted Pod

apiVersion: v1
kind: Pod
metadata:
  name: restricted
spec:
  containers:
  - name: app
    image: myapp:1.0.0
    securityContext:
      runAsNonRoot: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]

Explanation:

Example: RBAC Role

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]

This restricts access to only reading pods in the dev namespace.


Observability and Operations

Without observability, debugging distributed systems is guesswork.

Example: Prometheus Scrape Config

scrape_configs:
  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true

Pods annotated with prometheus.io/scrape=true are automatically monitored.

Distributed Tracing

Use OpenTelemetry to trace requests across microservices. This helps identify latency bottlenecks.


CI/CD and GitOps

Modern workflows deploy continuously and declaratively.

Example: ArgoCD Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
spec:
  destination:
    namespace: default
    server: https://kubernetes.default.svc
  source:
    repoURL: 'https://github.com/myorg/myapp-configs'
    targetRevision: main
    path: overlays/prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Explanation:


Conclusion: Best Practices and Pitfalls

By following these practices, you can run resilient, secure, and scalable workloads in production.