Kubernetes (K8s) is a container orchestration platform that automates deployment, scaling, and self-healing of containerized workloads built from OCI-compatible images. Kubelets ask a Container Runtime Interface (CRI)-compatible runtime, such as containerd or CRI-O, to create containers; Kubernetes does not require Docker Engine. The core value proposition: declare the desired state of your system, and Kubernetes continuously reconciles reality to match it.

Core Concepts

Pod: The smallest deployable unit. A pod wraps one or more containers that share a network namespace and storage. In practice, most pods contain a single container.

Deployment: Manages a set of identical pods. Handles rolling updates, rollbacks, and scaling. You declare the desired replica count and image; Kubernetes ensures that many pods are running.

Service: A stable network endpoint for a set of pods. Pods are ephemeral (they get new IPs when restarted); a Service provides a consistent DNS name and IP that load-balances across healthy pods.

Ingress: Routes external HTTP/HTTPS traffic to Services based on host and path rules. Requires an Ingress controller (nginx, Traefik, Azure Application Gateway).

ConfigMap / Secret: Externalize configuration from container images. ConfigMaps for non-sensitive config; Secrets for credentials (base64-encoded, not encrypted by default — use sealed secrets or Azure Key Vault for production).

Namespace: A scope for names and policy attachment, not a hard isolation boundary. Use namespaces to separate environments or teams, then enforce the boundary with RBAC, NetworkPolicy, ResourceQuota or LimitRange, admission policy, and service-account and secret controls. Use separate clusters when workloads cross a stronger trust boundary.

Deploying a .NET App

A minimal Kubernetes deployment for a .NET 8 API:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapi
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapi
  template:
    metadata:
      labels:
        app: myapi
    spec:
      containers:
      - name: myapi
        image: myregistry.azurecr.io/myapi:1.2.3
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8080
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          periodSeconds: 5
        startupProbe:
          httpGet:
            path: /health/startup
            port: 8080
          periodSeconds: 5
          failureThreshold: 30
        env:
        - name: ConnectionStrings__Default
          valueFrom:
            secretKeyRef:
              name: myapi-secrets
              key: connection-string
---
apiVersion: v1
kind: Service
metadata:
  name: myapi-svc
  namespace: production
spec:
  selector:
    app: myapi
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP

Application and Controller Patterns

PatternKubernetes mechanismOwnership and cost
Init containerRuns to completion before app containersGood for bounded setup; a failing init blocks every restart
SidecarShares a Pod lifecycle and network with the appUse only when co-location is required; it consumes resources per replica
Adapter or ambassadorTranslates telemetry or network protocol beside the appSimplifies the app but adds another failure path
Deployment controllerReconciles stateless replica count and rolloutDefault for replaceable processes
StatefulSetStable identity and ordered lifecycleDoes not make the database itself correct or highly available
Job or CronJobRun-to-completion workRequires idempotency because retries can repeat work
OperatorCustom controller reconciles domain stateWorth it only when recurring operational knowledge justifies API and controller maintenance

The application still owns graceful shutdown, readiness, idempotency, resource behavior, and data correctness. Kubernetes owns declared scheduling and reconciliation. For example, a retrying Job can execute a payment twice unless the handler supplies an idempotency key; no controller pattern repairs that application contract.

Service Exposure

ClusterIP is the default: a stable virtual IP and DNS name reachable inside the cluster, with EndpointSlices selecting ready Pods. NodePort exposes a port on every node and is usually an implementation detail beneath another load balancer. LoadBalancer asks an integration to provision an external balancer. ExternalName returns a DNS CNAME and creates no proxy or endpoints.

Use ClusterIP for service-to-service traffic. Use LoadBalancer for a small number of direct L4 entry points. Use Ingress or Gateway API when many HTTP services need shared TLS, host/path routing, and policy. Check health-check behavior, source-IP preservation, network boundaries, and provider cost; a Service type alone does not define those semantics. ExternalName can surprise clients that validate the original hostname or do not handle CNAMEs as expected.

Non-normative source visual

The ExternalName panel is incorrect: an ExternalName Service returns a DNS CNAME and has no selector, EndpointSlices, or data-plane proxy to Pods. The other panels are conceptual summaries; provider integrations still determine the external load-balancer path.

Pitfalls

Unmeasured resource policy: Requests are scheduler reservations used for placement and capacity planning, not hints. Set CPU and memory requests from observed demand plus headroom. A CPU limit enforces a quota by throttling; a memory limit bounds usage by making the container eligible for an OOM kill. Use limits deliberately for fairness and containment—blanket CPU limits can create latency through throttling—and enforce team defaults with LimitRange or policy where needed.

Probe misconfiguration: A liveness probe that fails during startup restarts the container, while a readiness probe that reports healthy too early sends traffic before dependencies are ready. Use a startupProbe to gate liveness and readiness during slow initialization, a readiness probe to control Service endpoints, and a liveness probe only for states that require a restart. Keep readiness independent of optional downstreams that would otherwise remove every replica during their outage.

Secrets are not encrypted at rest by default: Kubernetes Secrets are base64-encoded, not encrypted. Anyone with etcd access can read them. Fix: enable etcd encryption at rest, or use external secret management (Azure Key Vault with the Secrets Store CSI driver, or Sealed Secrets).

No pod disruption budget: During node maintenance, Kubernetes may evict all pods of a Deployment simultaneously, causing downtime. Fix: define a PodDisruptionBudget to ensure at least N pods remain available during voluntary disruptions.

Treating namespaces as isolation: Namespaces let you attach different RBAC rules, resource quotas, and network policies, but they enforce none of those controls by themselves. Create namespaces deliberately, then apply and test the policies that establish the intended boundary.

Tradeoffs

Managed K8s (AKS/EKS/GKE)Self-hosted K8sDocker Compose
Control planeManaged by cloudYou manageN/A
Upgrade effortLowHighN/A
Control-plane costProvider fee or plan varies; workers, networking, storage, and operations remainControl-plane infrastructure plus engineering and on-call workVM and runtime costs plus application operations
ScaleMulti-node, auto-scaleMulti-nodeSingle host
Production fitMulti-node workloads with platform operationsMulti-node workloads with platform expertiseBounded single-host workloads without HA requirements

Managed vs self-hosted: Use managed K8s unless regulatory, edge, platform-control, or provider constraints justify self-hosting. Compare current provider pricing with worker, network, storage, upgrade, support, and on-call costs. A self-hosted control plane may avoid a provider fee while costing more in infrastructure and operator time; there is no universal hourly price.

K8s vs Docker Compose: Compose is a good fit for local development and can run bounded single-host production workloads when host failure and manual rollout are acceptable. Kubernetes fits multi-node workloads that need scheduling, controlled rollout, autoscaling, and reconciliation. Compose does not supply high availability; Kubernetes adds mechanisms for it but still requires sound application probes, capacity, disruption policy, and failure-domain design.

Questions

References