Loading learning content...
Running a single container on your laptop is easy. Running hundreds or thousands of containers across dozens of servers, each needing to be scheduled, scaled, networked, monitored, and self-healed—that's container orchestration.
As organizations adopt microservices and containerized deployments, the complexity explodes. Which server should run each container? How do containers discover and communicate with each other? What happens when a server fails? How do you deploy updates without downtime? These questions drive the need for orchestration platforms.
Kubernetes has become the de facto standard for container orchestration, but understanding the underlying problems and concepts applies across orchestration platforms. This knowledge is essential for any engineer working with production containerized systems.
By the end of this page, you will understand why container orchestration is necessary, the core concepts and challenges it addresses, Kubernetes architecture and its key components, essential abstractions like Pods, Services, and Deployments, and common orchestration patterns used in production systems.
Container orchestration solves problems that emerge when containers are deployed at scale. While a single Docker host is manageable, production systems quickly outgrow manual management.
Problems Orchestration Solves:
MANUAL CONTAINER MANAGEMENT (doesn't scale)=========================================== You have 50 microservices across 20 servers. Need to: 1. Deploy new version of payment-service - SSH into each server running payment-service - Stop old containers - Pull new image - Start new containers - Verify health - Repeat for each instance TIME: Hours + risk of human error 2. Handle server failure - Detect failure (monitoring needed) - Identify which containers were running - SSH into replacement server - Manually start containers - Update load balancer config TIME: Minutes to hours of downtime 3. Scale for Black Friday traffic - Calculate capacity needed - Provision servers - Install Docker - Start containers on each - Update DNS/load balancer TIME: Days of preparation ═══════════════════════════════════════════════════════════════ ORCHESTRATED CONTAINER MANAGEMENT (Kubernetes)============================================== 1. Deploy new version of payment-service $ kubectl set image deployment/payment-service \ payment=payment:v2.1.0 Kubernetes: - Starts new pods with v2.1.0 - Waits for health checks - Gradually shifts traffic - Terminates old pods only when new are healthy TIME: 2-5 minutes, zero downtime, automatic rollback if issues 2. Handle server failure Kubernetes automatically: - Detects node failure within seconds - Marks pods as evicted - Schedules pods on healthy nodes - Updates networking TIME: Seconds to minutes, automatic 3. Scale for Black Friday traffic $ kubectl scale deployment/frontend --replicas=100 Or with autoscaling: $ kubectl autoscale deployment/frontend \ --min=10 --max=200 --cpu-percent=70 TIME: Seconds for scale-out, automatic based on actual loadMost teams find that 10-20 containers across 3+ servers marks the point where manual management becomes unsustainable. Orchestration provides value immediately and becomes essential as you scale further.
Several container orchestration platforms exist, each with different design philosophies and target use cases. Understanding the landscape helps you choose appropriately.
Major Platforms:
| Platform | Provider | Key Characteristics | Best For |
|---|---|---|---|
| Kubernetes | CNCF (Cloud Native Computing Foundation) | Extensible, declarative, industry standard. Complex but comprehensive. | Production workloads at scale, multi-cloud, vendor neutrality |
| Docker Swarm | Docker, Inc. | Simple, integrated with Docker. Limited compared to Kubernetes. | Small deployments, Docker-native environments |
| Amazon ECS | AWS | AWS-native, simpler than Kubernetes. Deep AWS integration. | AWS-only deployments, simpler requirements |
| Nomad | HashiCorp | Simple, supports VMs and non-containerized workloads. | Mixed workloads, simpler orchestration needs |
| OpenShift | Red Hat | Kubernetes + enterprise features, developer platform. | Enterprise, regulated industries |
Why Kubernetes Won:
Kubernetes emerged from Google's internal container management system (Borg) and was open-sourced in 2014. It became the dominant platform for several reasons:
The rest of this section focuses on Kubernetes, but the concepts (scheduling, scaling, self-healing, networking) are universal to orchestration.
Running Kubernetes yourself is complex. Most organizations use managed Kubernetes services: Amazon EKS, Google GKE, Azure AKS, or DigitalOcean Kubernetes. These handle the control plane so you focus on running workloads, not managing Kubernetes itself.
Kubernetes follows a control plane / worker node architecture. The control plane makes global decisions about the cluster, while worker nodes run the actual containerized workloads.
High-Level Architecture:
KUBERNETES CLUSTER ARCHITECTURE================================ ┌───────────────────────────────────────────────────────────┐ │ CONTROL PLANE │ │ (manages cluster state & decisions) │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ API SERVER (kube-apiserver) │ │ │ │ • Central communication hub │ │ │ │ • REST API for all operations │ │ │ │ • Authentication & authorization │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌─────────────────────────┐ │ │ │ etcd │ │Controller│ │ Scheduler │ │ │ │ │ │ Manager │ │ │ │ │ │ Cluster │ │ │ │ Assigns pods to nodes │ │ │ │ state │ │ Control │ │ based on resources │ │ │ │ storage │ │ loops │ │ and constraints │ │ │ └──────────┘ └──────────┘ └─────────────────────────┘ │ └───────────────────────────────────────────────────────────┘ │ │ API calls │ ┌────────────────────────────────────────────────────────────────────────────┐ │ WORKER NODES │ │ (run containerized workloads) │ │ │ │ ┌────────────────────────────┐ ┌────────────────────────────┐ │ │ │ NODE 1 │ │ NODE 2 │ │ │ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ │ │ │ │ kubelet │ │ │ │ kubelet │ │ │ │ │ │ • Manages pods │ │ │ │ • Reports status │ │ │ │ │ │ • Reports status │ │ │ │ • Manages pods │ │ │ │ │ └─────────────────────┘ │ │ └─────────────────────┘ │ │ │ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ │ │ │ │ kube-proxy │ │ │ │ kube-proxy │ │ │ │ │ │ • Network routing │ │ │ │ • Load balancing │ │ │ │ │ └─────────────────────┘ │ │ └─────────────────────┘ │ │ │ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ │ │ │ │ Container Runtime │ │ │ │ Container Runtime │ │ │ │ │ │ (containerd) │ │ │ │ (containerd) │ │ │ │ │ └─────────────────────┘ │ │ └─────────────────────┘ │ │ │ │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ │ │ Pod │ │ Pod │ │ │ │ Pod │ │ Pod │ │ Pod │ │ │ │ │ │┌─┬─┐│ │┌───┐│ │ │ │┌───┐│ │┌───┐│ │┌───┐│ │ │ │ │ ││C│C││ ││ C ││ │ │ ││ C ││ ││ C ││ ││ C ││ │ │ │ │ │└─┴─┘│ │└───┘│ │ │ │└───┘│ │└───┘│ │└───┘│ │ │ │ │ └─────┘ └─────┘ │ │ └─────┘ └─────┘ └─────┘ │ │ │ └────────────────────────────┘ └────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────────┘ C = Container Pod = Group of co-scheduled containersA Pod is the smallest deployable unit in Kubernetes. It represents one or more containers that share storage, network, and specifications for how to run. Pods are ephemeral—designed to be created, destroyed, and replaced.
Key Pod Concepts:
12345678910111213141516171819202122232425262728293031323334353637383940
# Pod definition (declarative configuration)apiVersion: v1kind: Podmetadata: name: web-server labels: app: frontend environment: productionspec: containers: - name: nginx image: nginx:1.25.3 ports: - containerPort: 80 resources: # Resource management requests: # Minimum guaranteed memory: "128Mi" cpu: "100m" # 100 millicores = 0.1 CPU limits: # Maximum allowed memory: "256Mi" cpu: "500m" livenessProbe: # Is container alive? httpGet: path: /healthz port: 80 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: # Is container ready for traffic? httpGet: path: /ready port: 80 initialDelaySeconds: 3 periodSeconds: 5 volumeMounts: - name: config-volume mountPath: /etc/nginx/conf.d volumes: # Shared storage - name: config-volume configMap: name: nginx-configMulti-Container Pod Patterns:
| Pattern | Description | Example Use Case |
|---|---|---|
| Sidecar | Helper container that augments the main container | Log collector, service mesh proxy (Envoy) |
| Ambassador | Proxy outgoing connections on behalf of main container | Database proxy, API gateway |
| Adapter | Transforms data from main container for external consumption | Log format conversion, metrics export |
| Init Container | Runs before main containers, sets up prerequisites | Database migration, config generation |
In production, you rarely create Pods directly. Instead, you create higher-level abstractions (Deployments, StatefulSets, Jobs) that manage Pods for you. These controllers handle scaling, updates, and self-healing that bare Pods don't provide.
A Deployment is the standard way to manage stateless applications in Kubernetes. It provides declarative updates for Pods, handling scaling, rolling updates, and rollbacks automatically.
How Deployments Work:
Deployment (manages)
└── ReplicaSet (ensures N pods exist)
└── Pod, Pod, Pod...
When you update a Deployment, it creates a new ReplicaSet with the updated Pod spec and gradually scales down the old ReplicaSet while scaling up the new one—a rolling update.
123456789101112131415161718192021222324252627282930313233343536373839404142434445
apiVersion: apps/v1kind: Deploymentmetadata: name: web-app labels: app: web-appspec: replicas: 3 # Desired number of pods selector: matchLabels: app: web-app # Which pods this deployment manages strategy: type: RollingUpdate # Update strategy rollingUpdate: maxSurge: 1 # Max extra pods during update maxUnavailable: 0 # Min available pods during update template: # Pod template (what gets created) metadata: labels: app: web-app spec: containers: - name: web image: myapp:v1.0.0 ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 10 periodSeconds: 5 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 3123456789101112131415161718192021222324252627282930313233343536
# Create deployment$ kubectl apply -f deployment.yamldeployment.apps/web-app created # View deployment status$ kubectl get deploymentsNAME READY UP-TO-DATE AVAILABLE AGEweb-app 3/3 3 3 2m # View pods created by deployment$ kubectl get pods -l app=web-appNAME READY STATUS RESTARTS AGEweb-app-6d889f6bcd-7xz8q 1/1 Running 0 2mweb-app-6d889f6bcd-k2n4m 1/1 Running 0 2mweb-app-6d889f6bcd-p9x3v 1/1 Running 0 2m # Scale deployment$ kubectl scale deployment/web-app --replicas=5deployment.apps/web-app scaled # Rolling update (change image version)$ kubectl set image deployment/web-app web=myapp:v2.0.0deployment.apps/web-app image updated # Watch rollout progress$ kubectl rollout status deployment/web-appWaiting for deployment "web-app" rollout to finish: 1 out of 3 new replicas...Waiting for deployment "web-app" rollout to finish: 2 out of 3 new replicas...deployment "web-app" successfully rolled out # View rollout history$ kubectl rollout history deployment/web-app # Rollback to previous version$ kubectl rollout undo deployment/web-appdeployment.apps/web-app rolled backWith proper liveness and readiness probes and the RollingUpdate strategy, deployments achieve zero-downtime updates. Kubernetes only shifts traffic to new pods after they pass readiness checks, and old pods continue serving until new ones are ready.
Pods are ephemeral and get new IP addresses when recreated. Services provide stable endpoints for accessing pods, abstracting away the dynamic nature of pods.
Service Types:
| Type | Accessibility | Use Case | How It Works |
|---|---|---|---|
ClusterIP | Within cluster only | Internal services | Virtual IP routable only inside cluster |
NodePort | External via node IP | Development/testing | Exposes service on each node's IP at a static port |
LoadBalancer | External via LB | Production traffic | Provisions cloud load balancer (AWS ELB, etc.) |
ExternalName | DNS alias | External services | Maps service to external DNS name (no proxy) |
12345678910111213141516171819202122232425262728
# ClusterIP Service (internal access)apiVersion: v1kind: Servicemetadata: name: web-app-servicespec: type: ClusterIP # Default type selector: app: web-app # Selects pods with this label ports: - protocol: TCP port: 80 # Service port (how other pods access) targetPort: 8080 # Container port (where app listens) ---# LoadBalancer Service (external access)apiVersion: v1kind: Servicemetadata: name: web-app-publicspec: type: LoadBalancer selector: app: web-app ports: - protocol: TCP port: 80 targetPort: 8080Service Discovery:
Kubernetes provides built-in service discovery through DNS. Every service gets a DNS entry:
<service-name>.<namespace>.svc.cluster.local
Pods can connect to web-app-service or web-app-service.default.svc.cluster.local and Kubernetes DNS resolves it to the service's ClusterIP. The service then load-balances across healthy pods.
SERVICE NETWORKING FLOW======================= External Request → LoadBalancer → Service → Pod ┌──────────────────────────────────────────────────────────────────────────────┐│ ││ CLIENT (internet) ││ │ ││ ▼ ││ ┌────────────────────────────────────────────┐ ││ │ CLOUD LOAD BALANCER (AWS ELB, etc.) │ ││ │ External IP: 54.23.145.67:80 │ ││ └────────────────────────────────────────────┘ ││ │ ││ │ Routes to any healthy node ││ ▼ ││ ┌────────────────────────────────────────────────────────────────────────┐ ││ │ KUBERNETES CLUSTER │ ││ │ │ ││ │ ┌───────────────────────────────────────────────────────────┐ │ ││ │ │ SERVICE: web-app-public │ │ ││ │ │ ClusterIP: 10.96.45.123:80 │ │ ││ │ │ Type: LoadBalancer │ │ ││ │ │ Selector: app=web-app │ │ ││ │ └───────────────────────────────────────────────────────────┘ │ ││ │ │ │ ││ │ ┌───────────────┼───────────────┐ │ ││ │ │ │ │ │ ││ │ ▼ ▼ ▼ │ ││ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ ││ │ │ Pod 1 │ │ Pod 2 │ │ Pod 3 │ │ ││ │ │ 10.244.1.5 │ │ 10.244.2.8 │ │ 10.244.1.9 │ │ ││ │ │ :8080 │ │ :8080 │ │ :8080 │ │ ││ │ │ app=web-app │ │ app=web-app │ │ app=web-app │ │ ││ │ └─────────────┘ └─────────────┘ └─────────────┘ │ ││ │ │ ││ │ kube-proxy on each node handles routing and load balancing │ ││ └────────────────────────────────────────────────────────────────────────┘ ││ │└──────────────────────────────────────────────────────────────────────────────┘For HTTP/HTTPS traffic, Ingress provides URL-based routing, SSL termination, and virtual hosting. Instead of one LoadBalancer per service (expensive), a single Ingress controller routes traffic to multiple services based on hostname and path.
Kubernetes separates configuration from container images using ConfigMaps (for non-sensitive data) and Secrets (for sensitive data). This enables using the same image across environments with different configurations.
ConfigMaps:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
# ConfigMap: Non-sensitive configurationapiVersion: v1kind: ConfigMapmetadata: name: app-configdata: DATABASE_HOST: "postgres.database.svc.cluster.local" DATABASE_NAME: "myapp" LOG_LEVEL: "info" config.json: | { "feature_flags": { "new_ui": true, "beta_features": false } }---# Secret: Sensitive data (base64 encoded in manifest, encrypted at rest)apiVersion: v1kind: Secretmetadata: name: app-secretstype: Opaquedata: # Values are base64 encoded DATABASE_PASSWORD: cGFzc3dvcmQxMjM= # password123 API_KEY: c2VjcmV0LWFwaS1rZXk= # secret-api-key---# Using ConfigMap and Secret in a PodapiVersion: v1kind: Podmetadata: name: myappspec: containers: - name: app image: myapp:v1.0.0 env: # Individual values from ConfigMap - name: DATABASE_HOST valueFrom: configMapKeyRef: name: app-config key: DATABASE_HOST # Individual values from Secret - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: app-secrets key: DATABASE_PASSWORD envFrom: # All keys from ConfigMap as env vars - configMapRef: name: app-config volumeMounts: # Mount ConfigMap as files - name: config-volume mountPath: /etc/config readOnly: true volumes: - name: config-volume configMap: name: app-configKubernetes Secrets are only base64 encoded, not encrypted, in etcd by default. Enable encryption at rest in your cluster configuration. For production, consider external secret management (HashiCorp Vault, AWS Secrets Manager) with operators that sync secrets to Kubernetes.
We've explored container orchestration from the problems it solves through Kubernetes architecture and key abstractions. Let's consolidate the essential concepts:
Module Complete:
You've now completed the Containers module, covering:
This knowledge forms the foundation for working with modern cloud-native applications. The concepts apply whether you're developing on your laptop, deploying to production Kubernetes, or architecting systems at scale.
You now understand containers from first principles through production orchestration. This module provided the conceptual foundation for the next module on Namespaces and cgroups, where we'll dive into the Linux kernel features that make containers possible.