Kubernetes » Fundamentals

This is Part I of the Kubernetes Fundamentals. It covers the architecture (control plane and worker nodes) and the core objects you create every day — Pods, ReplicaSets, Deployments, Services, Namespaces — plus the labels and selectors that wire them together. It opens with a hands-on quick start so you can see the system work before meeting the abstractions.

Two companion pages continue the fundamentals once you can deploy a Pod:

  • Networking & Configuration — how Services route traffic in depth, Ingress, NetworkPolicies, and how to inject configuration with ConfigMaps and Secrets.
  • Health & Resource Management — liveness/readiness/startup probes, CPU/memory requests and limits, QoS classes, and autoscaling.

Getting Started with Kubernetes

Before diving into commands and configurations, it helps to understand what Kubernetes actually does. Think of it as a distributed operating system: just as your laptop’s OS manages programs, memory, and files on a single machine, Kubernetes manages containers, resources, and storage across many machines.

Why does this matter? When you run kubectl apply -f myapp.yaml, you are not just starting a container. You are telling Kubernetes: “Here is what I want my application to look like. Make it happen and keep it that way.” Kubernetes then handles container placement, networking, restarts, and scaling automatically.

This page walks you through the fundamentals in the order that actually builds understanding: deploy something first, then learn the core vocabulary, then study each object in depth.

Quick Start Guide

The fastest way to understand Kubernetes is to use it. This section gets you deploying an application in minutes — the concepts behind every command follow in the sections after.

Requirements

  • Container technology knowledge (Docker)
  • Kubernetes cluster access (minikube, kind, k3s, or cloud provider)
  • kubectl CLI v1.28+ installed
  • Optional: Helm 3.x for package management

Your First Deployment

Let us deploy a web server and see Kubernetes in action:

# Deploy nginx and expose it
kubectl create deployment hello-world --image=nginx:alpine
kubectl expose deployment hello-world --type=LoadBalancer --port=80

# Verify it is running
kubectl get pods
kubectl get services

Note: --type=LoadBalancer provisions an external IP only on a cloud provider. On a local cluster (minikube, kind, k3s) the service stays <pending> — run minikube tunnel, or use --type=NodePort instead.

Now try the self-healing feature that makes Kubernetes valuable:

# Scale to 3 replicas and delete one pod
kubectl scale deployment hello-world --replicas=3
kubectl delete pod <pod-name>
kubectl get pods  # A new pod automatically replaces the deleted one

That replacement pod is not magic — it is the reconciliation loop at work, a concept we unpack under Core Concepts. Clean up when done:

kubectl delete deployment hello-world
kubectl delete service hello-world

Core Concepts at a Glance

Before going deeper, here is how the key pieces fit together:

Concept What It Is Analogy
Pod Smallest deployable unit; wraps one or more containers An apartment unit in a building
ReplicaSet Keeps a fixed number of identical Pods running The leasing office tracking how many units must stay occupied
Deployment Manages ReplicaSets and rolls out updates A property manager scheduling renovations unit by unit
Service Stable network address for a set of Pods The building’s front desk that routes visitors
Node A machine (physical or virtual) running pods An apartment building
Cluster A group of nodes managed together The entire apartment complex

The key insight: You rarely work with pods directly. Instead, you tell a Deployment “I want 3 copies of my app” and it creates a ReplicaSet, which creates and manages the pods for you. Services then route traffic to those pods, regardless of which nodes they run on.

The rest of this page explores each concept in depth, showing you how to build production-ready systems.

Understanding Kubernetes: From Containers to Orchestration

Consider the following evolution in how we run applications:

Era Approach Trade-off
Bare Metal One application per server Wasted resources; most servers idle
Virtual Machines Multiple VMs per server Better utilization but heavy overhead
Containers Many containers per server Lightweight but manual management at scale
Orchestration Kubernetes manages containers Automated, scalable, self-healing

Each step solved the previous era’s problems while creating new challenges. Containers solved VM overhead but introduced complexity: How do you run hundreds of containers across dozens of servers? How do you ensure they stay healthy? How do you update them without downtime?

Kubernetes answers these questions with a declarative approach and automated operations.

What Kubernetes Provides

Rather than listing features, consider what problems each capability solves:

Challenge Kubernetes Solution Benefit
“My container crashed” Self-healing Automatic restart and replacement
“How do services find each other?” Service discovery Built-in DNS and load balancing
“I need to deploy without downtime” Rolling updates Gradual replacement of old pods
“Traffic is spiking” Horizontal scaling Add replicas automatically or manually
“I need to store passwords securely” Secrets Encrypted storage with access controls
“Different apps need different storage” Storage classes Abstract storage provisioning

Core Concepts

Now that you understand why Kubernetes exists, let us explore how it works. The architecture consists of two main parts: the control plane that makes decisions and the worker nodes that run your applications.

Consider the following: When you run kubectl apply -f deployment.yaml, your request travels through several components. The API Server receives it, stores the desired state in etcd, the Scheduler decides which node should run the pods, and the Controller Manager ensures reality matches your specification. Understanding this flow helps you troubleshoot when things go wrong.

Kubernetes follows a control-plane / worker-node architecture: the control plane manages the cluster while worker nodes run your applications.

Control Plane API Server Gateway etcd State Store Scheduler Pod Placement Controller Manager Controllers Cloud Controller Manager Worker Nodes Node 1 kubelet kube-proxy Container Runtime Pods Node 2 Node 3

The control plane runs the API Server (the API gateway), etcd (the state store), the Scheduler (pod placement), the Controller Manager (the built-in controllers), and the Cloud Controller Manager (cloud-provider integration). Each worker node runs the kubelet (node agent), kube-proxy (service networking), and a container runtime (containerd or CRI-O). The next two sections detail each side.

The Control Plane in Detail

The control plane is the brain of the cluster. It does not run your application containers (on a managed service it is hidden from you entirely); instead it makes every global decision about the cluster and exposes the API you talk to.

Component Responsibility Failure impact
kube-apiserver The only component that reads/writes etcd. Validates every request, enforces authentication/authorization, and is the hub every other component watches. The cluster becomes read-only to operators; running pods keep running, but nothing new can be scheduled or changed.
etcd Consistent, distributed key-value store holding the entire desired and observed state. The single source of truth. Loss of etcd without a backup means loss of the cluster’s state. Always back it up.
kube-scheduler Watches for unscheduled pods and binds each to the best-fit node based on resource requests, affinity, taints, and constraints. New pods stay Pending; existing pods are unaffected.
kube-controller-manager Runs the built-in controllers (Deployment, ReplicaSet, Node, Job, endpoints, and more), each running a reconciliation loop. Self-healing stalls — failed pods are not replaced, rollouts freeze.
cloud-controller-manager Talks to the cloud provider for nodes, load balancers, and routes. Absent on bare-metal clusters. Cloud-backed Services and node lifecycle integration stop updating.

A production control plane runs these components redundantly (typically three or five etcd members for quorum) so the loss of a single node does not take down the cluster.

Worker Nodes in Detail

A node is a machine — a VM or physical server — that runs your pods. Every node runs three pieces of software that the control plane drives:

  • kubelet — the node agent. It watches the API server for pods assigned to its node, instructs the container runtime to pull images and start containers, and continuously reports pod and node status back. It also runs the health probes (covered in Health & Resource Management).
  • kube-proxy — programs the node’s networking (iptables or IPVS rules, or eBPF with some CNIs) so that traffic to a Service’s virtual IP is load-balanced to the backing pods. Service mechanics are covered in Networking & Configuration.
  • Container runtime — the software that actually runs containers: containerd or CRI-O (Docker’s runtime was removed as a direct integration in v1.24). The kubelet talks to it through the Container Runtime Interface (CRI).

Inspect nodes with:

kubectl get nodes -o wide        # IPs, OS image, kernel, runtime
kubectl describe node <node>     # capacity, allocatable, conditions, pods

The conditions in describe node (Ready, MemoryPressure, DiskPressure, PIDPressure) are how the node reports its health; the kubelet renews a heartbeat lease so the control plane can detect a node that has gone offline and reschedule its pods elsewhere.

What Happens When You Apply a Manifest

The diagram below traces a single kubectl apply through the control plane. Notice that the components never talk to each other directly — they all watch the API server, which is the single source of truth backed by etcd. This “level-triggered” design is what makes Kubernetes self-healing: controllers continuously reconcile actual state toward desired state.

sequenceDiagram
    participant U as kubectl
    participant API as API Server
    participant E as etcd
    participant S as Scheduler
    participant K as kubelet (node)
    U->>API: apply deployment.yaml
    API->>E: persist desired state
    API-->>S: new pod (unscheduled)
    S->>API: bind pod to node
    API->>E: persist assignment
    API-->>K: pod assigned to this node
    K->>K: pull image, start container
    K->>API: report status (Running)
    API->>E: persist actual state

The Reconciliation Loop

The single most important idea in Kubernetes is the control loop (or reconciliation loop). Every controller runs the same endless cycle: observe the current state, compare it to the desired state recorded in etcd, and take action to close the gap. There is no one-shot “deploy” step — the system is continuously driven toward your declared intent, which is precisely why a deleted pod reappears and a crashed container restarts.

flowchart LR
    D["Desired state<br/>(your YAML in etcd)"] --> C{"Observe &<br/>compare"}
    A["Actual state<br/>(what's running)"] --> C
    C -->|"drift detected"| ACT["Take corrective action<br/>(create/delete/update pods)"]
    ACT --> A
    C -->|"in sync"| C

This is a level-triggered design (it reacts to the current level of state) rather than edge-triggered (reacting to one-time events). If a controller misses an event, it simply re-observes reality on its next pass and still converges — making Kubernetes robust to restarts, network blips, and lost messages.

Kubernetes Objects: The Building Blocks

With the architecture understood, let us explore the objects you will work with daily. Each object type solves a specific problem, and choosing the right one depends on your application’s needs.

When to use each object type:

Object Use Case Example
Pod Rarely used directly; foundation for other objects Testing, debugging
ReplicaSet Keeps N identical pods running; usually managed by a Deployment Created for you by Deployments
Deployment Stateless applications that can scale horizontally Web servers, APIs
StatefulSet Stateful applications needing stable identity Databases, message queues
DaemonSet Run one pod per node Log collectors, monitoring agents
Job / CronJob Run-to-completion or scheduled tasks Migrations, batch processing, backups

This page covers the foundation — Pods, ReplicaSets, and Deployments. The specialized controllers (StatefulSet, DaemonSet, Job, CronJob) and persistent storage are covered in Workloads & Storage.

Every Kubernetes object shares the same four top-level fields, worth recognizing before reading any manifest:

  • apiVersion — which API group and version defines this object (v1, apps/v1, batch/v1, …).
  • kind — the object type (Pod, Deployment, Service, …).
  • metadata — name, namespace, labels, and annotations.
  • spec — your desired state. Kubernetes fills in a status field with the observed state; the controllers’ job is to make status match spec.

Pods

A Pod is the smallest thing Kubernetes schedules. It is not a single container — it is a wrapper around one or more tightly coupled containers that share:

  • a network namespace — every container in the pod shares one IP address and port space, so they reach each other over localhost;
  • storage volumes — mounted into any container in the pod that asks for them;
  • a lifecycle — they are scheduled, started, and stopped together on the same node.

Most pods hold exactly one container. The multi-container pattern is reserved for helpers that must live beside the main process: a sidecar (for example a log shipper or a service-mesh proxy) and an init container that runs to completion before the main containers start (covered with the operational patterns in Operations).

Pods are ephemeral. This is the most important thing to internalize. A pod is never healed in place — if its node dies, the pod is gone for good and a new pod with a new name and new IP is created elsewhere. That is why you almost never create a bare Pod in production: nothing would recreate it. Instead you let a controller own it.

A bare Pod manifest is rarely used directly, but it shows the minimal shape:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.21
    ports:
    - containerPort: 80
kubectl get pods -o wide              # which node, which IP
kubectl describe pod <name>           # events, why it is Pending/CrashLooping
kubectl logs <name> [-c <container>]  # container stdout/stderr
kubectl exec -it <name> -- sh         # shell inside the container

ReplicaSets

A ReplicaSet is the controller whose single job is to keep a specified number of identical pod replicas running at all times. It watches the pods matching its label selector and, whenever the count drifts from the desired replicas, creates or deletes pods to correct it — the reconciliation loop applied to pod count.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:               # the pod spec the ReplicaSet stamps out
    metadata:
      labels:
        app: nginx        # MUST satisfy the selector above
    spec:
      containers:
      - name: nginx
        image: nginx:1.21

Note the structure that repeats across every workload controller: a selector that says which pods this controller owns, and a template that is the pod spec it creates. The template’s labels must satisfy the selector, or the API server rejects the object.

You almost never write a ReplicaSet directly. It cannot perform a controlled, versioned rollout — if you change the image in a ReplicaSet’s template, existing pods are not updated. That gap is exactly what a Deployment fills. ReplicaSets matter because every Deployment creates and manages them on your behalf, and you will see them in kubectl get rs when you debug a rollout.

Deployments

A Deployment is the object you reach for to run a stateless application. It manages a ReplicaSet and adds rolling updates, rollback, scaling, and self-healing on top:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21
        ports:
        - containerPort: 80

It sits one level above the ReplicaSet and adds the thing a bare ReplicaSet lacks: versioned, controlled rollouts. The ownership chain is:

Deployment  ──owns──►  ReplicaSet  ──owns──►  Pods

When you change the pod template (typically the image tag), the Deployment does not mutate the running pods. Instead it creates a new ReplicaSet for the new template and gradually shifts replicas from the old ReplicaSet to the new one — a rolling update. The pace is governed by two knobs:

  • maxUnavailable — how many pods may be down at once during the rollout.
  • maxSurge — how many extra pods may be created above the desired count.
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1

Because the old ReplicaSet is kept (scaled to zero, not deleted), a rollback is just a matter of scaling the previous ReplicaSet back up — which kubectl does for you:

# Trigger a rollout by changing the image
kubectl set image deployment/nginx-deployment nginx=nginx:1.25

kubectl rollout status deployment/nginx-deployment   # watch it progress
kubectl rollout history deployment/nginx-deployment  # list revisions
kubectl rollout undo deployment/nginx-deployment     # roll back one revision

kubectl scale deployment/nginx-deployment --replicas=5

This is also the source of the self-healing you saw in the quick start: the Deployment’s ReplicaSet notices a missing pod and recreates it, every time, without any intervention.

Services (Overview)

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer

Pods are ephemeral and their IPs change every time they are rescheduled, so you can never hand a pod IP to a client. A Service solves this by giving a stable virtual IP and DNS name that fronts a changing set of pods. The Service finds its backing pods the same way every controller does — by label selector — and load-balances across whichever pods currently match.

The four Service types differ only in how far that stable endpoint reaches:

Service Type Accessible From Use Case Cost
ClusterIP Inside cluster only Internal microservices Free
NodePort Node IP + port (30000-32767) Development, testing Free
LoadBalancer External IP via cloud LB Production web apps Cloud provider charges
ExternalName DNS alias Accessing external services Free

When to use each: Start with ClusterIP for internal services. Use LoadBalancer for production internet-facing services. NodePort is useful for development but rarely appropriate for production due to port limitations.

This is the overview. How kube-proxy programs the routing, how DNS-based service discovery works, how Ingress fans one external IP out to many Services, and how NetworkPolicies restrict pod-to-pod traffic are all covered in depth in Networking & Configuration. Configuration injection with ConfigMaps and Secrets lives on that page as well.

Namespaces

A Namespace is a virtual cluster inside a physical one — a way to partition objects so that names, quotas, and access controls do not collide. Two teams can both have a Deployment named web as long as they live in different namespaces.

Namespaces are the natural boundary for several other features:

  • DNS — a Service is reachable as <service>.<namespace>.svc.cluster.local; within the same namespace you can use the short <service> name.
  • ResourceQuotas cap total CPU/memory/object counts per namespace.
  • RBAC (covered in Networking & Configuration) grants permissions scoped to a namespace.

Note that namespaces are not a hard security boundary by themselves — pods in different namespaces can still reach each other over the flat pod network unless a NetworkPolicy says otherwise.

kubectl get namespaces
kubectl get pods -n kube-system          # target a namespace with -n
kubectl get pods --all-namespaces        # everything, everywhere
kubectl config set-context --current --namespace=development   # set a default

A handful of namespaces exist on every cluster: default (where your objects land if you do not specify one), kube-system (the control-plane and node add-on pods), kube-public (world-readable cluster info), and kube-node-lease (node heartbeat leases).

Labels and Selectors: The Glue

Almost every relationship in Kubernetes is expressed through labels — arbitrary key/value pairs attached to objects — and selectors that query them. A Deployment finds its pods by selector. A Service finds its endpoints by selector. NetworkPolicies, node affinity, and kubectl filters all work the same way. Labels are the loose coupling that lets these objects find each other without hard references.

metadata:
  labels:
    app: storefront        # which application
    tier: backend          # role within the app
    environment: production
    version: v2.3.1

A selector matches a subset of those labels. There are two flavors:

# Equality-based (used by Service spec.selector)
selector:
  app: storefront
  tier: backend

# Set-based (used by Deployment/ReplicaSet spec.selector.matchExpressions)
selector:
  matchLabels:
    app: storefront
  matchExpressions:
  - key: environment
    operator: In
    values: [production, staging]

The same selector syntax drives the CLI, which is how you slice and dice a live cluster:

kubectl get pods -l app=storefront,tier=backend   # AND of two labels
kubectl get pods -l 'environment in (production, staging)'
kubectl get pods -l '!canary'                      # pods without a canary label
kubectl label pod nginx-pod tier=frontend --overwrite

Why this matters in practice: the single most common reason a Service has no endpoints — traffic silently black-holes — is a mismatch between the Service’s selector and the pods’ labels. When something is not receiving traffic, compare the two first:

kubectl get endpoints <service-name>     # empty list == selector mismatch
kubectl get pods --show-labels

Annotations vs labels: labels are for identifying and selecting objects and are indexed for queries. Annotations are also key/value metadata, but they are for arbitrary non-identifying information (build IDs, change-cause, tool configuration) and cannot be used in selectors.

Common Pitfalls

Common Pitfalls

  • Managing pods directly: Never create bare pods in production. Use a Deployment (or StatefulSet) so failed pods are recreated automatically.
  • Mismatched labels: A Service routes to pods by label selector. If the selector and pod labels disagree, the Service has zero endpoints and silently drops traffic.
  • Editing a ReplicaSet directly: Changes to a ReplicaSet's template do not roll out to existing pods. Always drive changes through the owning Deployment.
  • Immutable selectors: A Deployment's spec.selector cannot be changed after creation. Plan your labels before you apply.

Key Takeaways

  • Declarative, not imperative. You describe desired state; controllers continuously reconcile reality toward it. This is the source of self-healing.
  • Everything goes through the API server. Components never talk directly — they watch the API server, which persists state in etcd.
  • Deployments manage pods. Work with Deployments and Services, not raw pods. The Deployment owns the ReplicaSet, replica count, and rollout; the Service owns the stable address.
  • Labels wire it together. Services, NetworkPolicies, and selectors all match by label, so consistent labeling is foundational.

See Also