Kubernetes Tutorial: Learn Container Orchestration from Scratch (2026)
I walked into my first Kubernetes production incident at 2 AM. A deployment had rolled out a bad config, all pods were CrashLoopBackOff, and the on-call engineer had no idea how to roll back. That night taught me what Kubernetes really is: not just a buzzword or a platform, but a declarative operating system for your cluster. Kubernetes handles scheduling, scaling, networking, and healing of containers so you do not have to. But with great power comes a steep learning curve. This tutorial breaks down Kubernetes from the ground up — from Pods to RBAC — with the practical patterns I have used to run clusters in production for years.
Cluster Architecture and Core Components
A Kubernetes cluster has two planes: the control plane and the data plane. The control plane runs the API server, scheduler, controller manager, and etcd. The data plane — the worker nodes — run the kubelet, kube-proxy, and a container runtime like containerd. When I first started, I tried to understand every component at once and got overwhelmed. The most important piece is the API server. Everything in Kubernetes is a REST API call. When you run kubectl apply, it sends a YAML payload to the API server, which validates it, stores it in etcd, and triggers controllers that reconcile the desired state with the actual state. Grasping this reconciliation loop is the key to understanding how Kubernetes works.
kubectl cluster-info
kubectl get nodes -o wide
kubectl describe node worker-1 | grep -A5 "Capacity"
Pods: The Smallest Deployable Unit
A Pod is one or more containers that share a network namespace, storage volumes, and a specification for how to run. The common question is: why not just use containers directly? Pods allow tightly coupled processes — like a web server and a sidecar that ships logs — to share the same IP and port space. In practice, I run single-container Pods for most microservices and reserve multi-container Pods for specific sidecar patterns: log shippers, service mesh proxies, and config reload watchers. Every Pod has a lifecycle: Pending, Running, Succeeded or Failed, and Unknown. Understanding Pod lifecycle is critical for debugging failed deployments.
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
Deployments and ReplicaSets for Scaling
Rarely do you run a single Pod in production. A Deployment manages a ReplicaSet, which in turn manages the desired number of Pod replicas. When you update a Deployment's Pod template, it creates a new ReplicaSet and gradually scales it up while scaling the old one down. This rolling update strategy gives zero-downtime deployments. I have used Deployments to run hundreds of replicas across multiple availability zones. The real power is in the declarative rollback: kubectl rollout undo deployment/myapp reverts to the previous ReplicaSet in seconds. Setting resource requests and limits on every container is essential — without them, the scheduler cannot make intelligent placement decisions and noisy neighbors can starve other Pods.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapp/api:2.4.1
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Services and Ingress for Network Access
Pods are ephemeral — they come and go with new IP addresses. A Service provides a stable network endpoint backed by a label selector. ClusterIP services are internal-only, NodePort exposes on every node's IP, and LoadBalancer provisions a cloud load balancer. For HTTP traffic, Ingress provides host and path-based routing. I have debugged countless networking issues caused by mismatched selectors or missing port names. The key insight is that Services operate at layer 4 (TCP/UDP), while Ingress operates at layer 7 (HTTP/HTTPS). For gRPC or non-HTTP protocols, you need a LoadBalancer Service or a custom Ingress controller that supports protocol forwarding.
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
selector:
app: web
ports:
- port: 80
targetPort: 3000
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
ConfigMaps and Secrets for Configuration
Hard-coding configuration in container images violates the twelve-factor app methodology. ConfigMaps store non-sensitive configuration as key-value pairs or entire files, which you inject into Pods as environment variables or mounted volumes. Secrets are similar but base64-encoded and intended for sensitive data like API keys and database passwords. In practice, I use Sealed Secrets or external secrets operators with Vault or AWS Secrets Manager to avoid storing raw secrets in Git. An important subtlety: updating a ConfigMap or Secret does not automatically restart the Pods using it. You need a rolling restart or a controller like Reloader that watches for changes and triggers a restart.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
NODE_ENV: production
LOG_LEVEL: info
app.properties: |
cache.ttl=300
rate.limit=100
RBAC, Namespaces, and Production Governance
When I started running multi-team clusters, I learned the hard way that default permissions are too permissive. Namespaces provide logical isolation boundaries — each team, environment, or application gets its own namespace with resource quotas and network policies. Role-Based Access Control (RBAC) defines who can do what. I create ServiceAccounts for each application with the minimum permissions needed. A common pattern is to bind a Role to a ServiceAccount within a namespace, then use that ServiceAccount in the Pod spec. This way, even if a container is compromised, the blast radius is limited. Network policies enforce which Pods can communicate, adding a defense-in-depth layer beyond application-level authentication.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: production
Frequently Asked Questions
Do I need Docker to run Kubernetes?
No. Kubernetes uses the Container Runtime Interface (CRI). While Docker was the original runtime, Kubernetes deprecated Docker as a runtime after v1.20. Most clusters today use containerd or CRI-O. You can build images with Docker and run them on a Kubernetes cluster that uses containerd without issue.
How many nodes should a production cluster have?
At minimum, three control plane nodes for high availability and at least two worker nodes. A three-node cluster can tolerate one control plane failure. For production workloads, I recommend at least five to seven worker nodes spread across three availability zones to survive zone outages.
What is the difference between a Deployment and a StatefulSet?
Deployments are for stateless applications where each replica is interchangeable. StatefulSets provide stable network identities and persistent storage for each replica, making them suitable for databases, message queues, and anything with identity or ordering requirements. StatefulSets deploy pods in sequential order and guarantee unique naming.
How do I debug a Pod that stays in CrashLoopBackOff?
Start with kubectl logs pod-name --previous to see the logs from the crashed container. Then kubectl describe pod pod-name to check events and conditions. Common causes include missing ConfigMaps or Secrets, container command failures, resource limits that are too low, or liveness probes that are misconfigured.
Originally published on Ayodhyyya. Last updated June 1, 2026.