Currently Empty: $0.00
Legendary Ways Academy · Orchestration
Kubernetes, Running Containers at Real Scale
Docker runs one container. Kubernetes runs thousands, across a fleet of servers, self-healing when something fails and scaling automatically under load. This is how it actually works.
Real YAML manifests
kubectl commands
Debugging a pod
Docker solves how to package and run one container reliably. Kubernetes solves a different, larger problem: how do you run hundreds or thousands of containers across a fleet of servers, restart them automatically when they crash, scale them up under load and back down when it passes, roll out new versions without downtime, and route traffic to the right place, all without a human manually managing each individual container. Kubernetes is the orchestration platform that’s become the industry standard answer to that problem, and understanding it, at least at the level covered here, is close to a baseline expectation for a mid-level or senior DevOps role today.
This guide covers the core objects Kubernetes is built from, a real working Deployment and Service manifest, configuration and secrets, autoscaling, rolling updates and self-healing, exposing applications externally, the kubectl commands you’ll use constantly, and how to actually debug a pod that isn’t behaving. It also covers StatefulSets for workloads that need stable identity, namespaces and resource quotas for safely sharing a cluster across teams, and Helm for packaging complex applications as reusable, versioned charts.
Kubernetes has a genuine reputation for complexity, and that reputation is fair; it introduces real new concepts beyond what Docker alone requires. The good news is that the core mental model, declare the desired state, let the control plane continuously reconcile actual state to match it, applies consistently across nearly every object type it manages, so once that model clicks, most of what follows is learning new object types that all follow the same underlying pattern.
The Core Building Blocks
Pod
The smallest deployable unit, one or more tightly coupled containers sharing network and storage. Usually just one container per pod in practice.
Deployment
Manages a set of identical pod replicas, handling rolling updates, rollbacks, and keeping the desired number of pods running.
Service
A stable network endpoint that routes traffic to a set of pods, even as individual pods are replaced or rescheduled.
Namespace
A way to logically partition a cluster, commonly one per environment or team, keeping resources organized and isolated.
A Real Deployment and Service
Kubernetes objects are defined declaratively in YAML manifests, describing the desired state, then applied to the cluster, which continuously works to make reality match what you declared.
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myregistry.io/my-app:v1.2.0
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
The Deployment declares that three replicas of the my-app container should always be running, with resource requests and limits telling the scheduler how much CPU and memory each pod needs and preventing any single pod from consuming unbounded resources. The Service selects any pod labeled app: my-app and gives them a single stable internal address, so other services in the cluster can reach my-app reliably even as individual pods are created, destroyed, and rescheduled onto different nodes over time.
ConfigMaps and Secrets
Configuration and secrets are kept separate from the container image itself, so the same image can run across different environments (staging, production) with different configuration, without rebuilding it. ConfigMaps hold non-sensitive configuration; Secrets hold sensitive values like credentials, base64-encoded (not encrypted by default, so they still need proper RBAC access control).
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
data:
LOG_LEVEL: "info"
API_TIMEOUT: "30s"
---
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
type: Opaque
data:
DATABASE_PASSWORD: cGFzc3dvcmQxMjM=
Referenced from a Deployment’s env or envFrom field, these values get injected into the running container as environment variables, exactly as covered in our Linux and command line guide, without the values ever being hardcoded into the container image itself.
Self-Healing and Rolling Updates
Kubernetes continuously compares the actual state of the cluster against the desired state declared in your manifests, and reconciles any difference automatically. If a pod crashes, the Deployment notices the replica count has dropped below the desired three and schedules a replacement immediately, without any human intervention. When you update a Deployment’s image to a new version, Kubernetes performs a rolling update by default: starting new pods with the new version, waiting for them to become healthy, then terminating old pods gradually, so there’s no moment where the application is entirely down.
bash
kubectl set image deployment/my-app my-app=myregistry.io/my-app:v1.3.0
kubectl rollout status deployment/my-app
kubectl rollout undo deployment/my-app
kubectl rollout undo is a genuinely valuable safety net: if a new version turns out to be broken, this rolls the Deployment back to its previous known-good state in seconds, the same rollback pattern covered conceptually in our CI/CD pipelines guide, implemented here at the orchestration layer instead of inside a custom deploy script.
Scaling Automatically Under Load
A Horizontal Pod Autoscaler (HPA) automatically adjusts the number of running replicas based on observed metrics, most commonly CPU utilization, keeping the application responsive under traffic spikes without needing a human to manually scale it up, and scaling back down automatically once demand drops to avoid paying for unused capacity.
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
This configuration keeps between 3 and 10 replicas running, scaling up whenever average CPU utilization across pods exceeds 70%. Setting sensible minReplicas and maxReplicas bounds matters: too low a minimum risks a cold-start delay under a sudden spike, and too high a maximum risks a runaway scaling event consuming far more cluster capacity (and cost) than intended.
Exposing Applications With Ingress
A Service of type ClusterIP (used above) is only reachable from inside the cluster. To expose an application to the outside world with a real domain name, path-based routing, and TLS, most clusters use an Ingress resource, which works alongside an Ingress controller (commonly NGINX or a cloud provider’s native load balancer integration) to route external HTTP traffic to the right internal Service.
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
This routes any request to app.example.com to the my-app Service, which then load-balances across whichever pods are currently healthy. Most production Ingress setups also handle automatic TLS certificate provisioning through an integration like cert-manager, so HTTPS is configured declaratively rather than managed by hand.
kubectl: The Commands You’ll Use Constantly
bash
kubectl get pods
kubectl describe pod my-app-7d9f8c-x2k9p
kubectl logs -f my-app-7d9f8c-x2k9p
kubectl exec -it my-app-7d9f8c-x2k9p -- sh
kubectl apply -f deployment.yaml
kubectl get events --sort-by='.lastTimestamp'
kubectl get pods is the most frequently run command in any Kubernetes workflow, showing what’s currently running and its status. describe shows detailed information about a specific object, including recent events, often the fastest way to understand why a pod isn’t starting. logs -f streams a container’s output live, the same pattern from our Docker guide, applied at the cluster level. apply -f is how you actually push a manifest’s declared state to the cluster.
Debugging a Pod That Won’t Start
A pod stuck in Pending usually means the scheduler can’t find a node with enough available resources to satisfy its requests, check kubectl describe pod for scheduling events explaining why. A pod in CrashLoopBackOff means the container starts and then exits repeatedly, almost always an application-level error visible in kubectl logs, or a misconfigured health check killing an otherwise-healthy container before it finishes starting up. A pod stuck in ImagePullBackOff means Kubernetes can’t pull the specified container image, usually a typo in the image name or tag, or a missing registry credential.
A less obvious but common failure mode: a pod that appears to be running fine but is repeatedly removed from a Service’s routing because it’s failing its readiness probe. Readiness and liveness probes are health checks Kubernetes runs against a container to decide whether it should receive traffic (readiness) or be restarted entirely (liveness); a probe pointed at the wrong path or port will cause Kubernetes to correctly conclude the pod is unhealthy, even if the application itself is working perfectly, simply because the health check configuration doesn’t match reality.
StatefulSets: When Pods Need Stable Identity
Deployments treat every pod as interchangeable, which works well for stateless applications but not for things like databases, where each instance needs a stable network identity and its own persistent storage that follows it even if it’s rescheduled to a different node. StatefulSets solve this: each pod gets a predictable, stable name (my-db-0, my-db-1, and so on) and its own PersistentVolumeClaim that isn’t shared or recreated when the pod restarts.
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-db
spec:
serviceName: my-db
replicas: 3
selector:
matchLabels:
app: my-db
template:
metadata:
labels:
app: my-db
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
In practice, many teams run their databases outside Kubernetes entirely, using a managed cloud database service instead, and reserve StatefulSets for cases where running stateful infrastructure inside the cluster genuinely makes sense. It’s still important to understand the distinction, since it comes up constantly when deciding how to architect a real production system.
Namespaces and Resource Quotas in Practice
Namespaces are how a single cluster gets safely shared across multiple teams or environments without everything colliding. A common pattern is one namespace per environment (staging, production) or one per team, with resource quotas limiting how much CPU, memory, and how many objects each namespace can consume, so one team’s misbehaving workload can’t starve the entire cluster of resources.
yaml
apiVersion: v1
kind: Namespace
metadata:
name: staging
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: staging-quota
namespace: staging
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
pods: "20"
Combined with the resource requests and limits set on individual pods earlier in this guide, namespaces and quotas together are the primary mechanism for safe multi-tenancy on a shared cluster, preventing a single team’s workload, whether misconfigured or simply under unexpected load, from degrading everyone else’s applications running on the same underlying hardware.
Helm: Packaging Kubernetes Applications
Real applications often need a dozen or more interrelated manifests, Deployments, Services, ConfigMaps, Ingress rules, and managing them all as separate files becomes unwieldy quickly. Helm is the most widely used package manager for Kubernetes, bundling related manifests into a reusable “chart” with configurable values, letting you install or upgrade an entire application with a single command rather than applying files individually.
bash
helm install my-app ./my-app-chart --set image.tag=v1.3.0
helm upgrade my-app ./my-app-chart --set replicaCount=5
helm rollback my-app 1
Helm is worth learning once you’re comfortable with raw manifests, since it’s how most production Kubernetes deployments, and nearly every third-party application you’d install onto a cluster, are actually packaged and distributed in practice.
How This Connects to the Rest of DevOps
Kubernetes sits directly on top of everything covered so far: it orchestrates the Docker images your CI/CD pipeline builds, runs on Linux nodes managed with the same fundamentals from our command line guide, and is frequently provisioned in the first place using Terraform. It’s the layer where all the other topics in this curriculum converge into a single running production system.
Frequently Asked Questions
Do I need Kubernetes for a small project?
Usually not. Kubernetes adds real operational complexity that only pays off once you have enough scale, team size, or reliability requirements to justify it; a small project is often better served by a simpler managed platform.
What’s the difference between a Deployment and a Pod?
A Pod is a single running instance; a Deployment manages a set of identical Pod replicas, handling scaling, self-healing, and rolling updates, which is why you almost never create Pods directly in production.
Should I run Kubernetes myself or use a managed service?
Use a managed service (EKS, AKS, GKE) in almost every real-world case; running the Kubernetes control plane yourself adds significant operational burden that a managed service handles for you at a reasonable cost.
How is Kubernetes different from Docker Compose?
Compose runs containers on a single machine, fine for local development. Kubernetes orchestrates containers across a cluster of many machines with self-healing, scaling, and rolling updates, built for production workloads at real scale.
What certification is worth pursuing for Kubernetes specifically?
The Certified Kubernetes Administrator (CKA) is the most widely recognized credential and is hands-on rather than multiple-choice, which makes it a genuinely useful signal of practical skill to employers.
How steep is the learning curve realistically?
Steeper than most DevOps topics covered in this curriculum. Expect several weeks of hands-on practice, ideally on a free local cluster tool like Minikube or kind, before core concepts like Deployments, Services, and debugging a failing pod feel routine rather than confusing.
If you’re building this skill toward a job search, prioritize genuinely deploying and debugging a real multi-service application on a local cluster over memorizing every resource type Kubernetes offers. Set up Minikube or kind, deploy an app with a Deployment and Service, intentionally misconfigure something (a wrong image tag, a missing environment variable), and practice diagnosing it using kubectl describe and kubectl logs. That hands-on debugging loop is exactly what most technical interviews for roles involving Kubernetes actually probe, far more than abstract knowledge of every field a manifest supports.
Related reading: continue to Ansible, revisit Docker and containers for the foundation Kubernetes builds on, or return to the full topics overview.




