10.12 - Core Concepts: The Mental Model

Kubernetes is a declarative control loop. You declare desired state (a manifest), the control plane reconciles reality toward it forever. There are no “start this container” commands; there are desired-state objects that controllers watch and enforce.

The two planes

Control plane (runs on “server” nodes):

  • API server: the only thing you ever talk to. Everything goes through kubectl -> API server (REST, port 6443). It validates requests and stores state in etcd (k3s: SQLite by default, etcd for HA).
  • Scheduler: picks which node each new pod lands on (filters + scores).
  • Controller manager: runs all the built-in controllers (deployment, replicaset, namespace, serviceaccount, endpoint, etc.).

Data plane (runs on every node, including “server” nodes):

  • kubelet: the node agent. Watches for pods assigned to its node, talks to the container runtime, reports status.
  • Container runtime: k3s bundles containerd.
  • kube-proxy: implements Service networking (iptables/IPVS rules for ClusterIP, NodePort).
  • Plus the CNI plugin (Flannel in k3s) for pod-to-pod networking.

The objects you will use all week

Pod: the smallest unit. One or more containers sharing a network namespace and volume mounts. Pods are EPHEMERAL: assume any pod can die at any moment. You almost never create a bare pod; you create a controller that makes pods for you.

Controller (workload): a reconciliation loop watching a label selector.

  • Deployment: stateless apps. N replicas, rolling updates, rollbacks, scaling. The workhorse. Owns ReplicaSets.
  • ReplicaSet: the thing that actually makes/removes pods, owned by a Deployment. You rarely touch it directly.
  • StatefulSet: stable identity and storage per pod (db-0, db-1, …). Day 5.
  • DaemonSet: exactly one pod per node (metrics collectors, log shippers).
  • Job / CronJob: run-to-completion work.

Service: stable network endpoint in front of a changing set of pods. Selects pods by label, load-balances among them, gets a stable ClusterIP + DNS name.

  • ClusterIP (default): internal only.
  • NodePort: ClusterIP + a port in 30000-32767 on every node.
  • LoadBalancer: asks the platform for an external LB (k3s: servicelb, or MetalLB). Disabled in this setup, see 10.11.
  • Headless: no ClusterIP; returns pod IPs directly (StatefulSets).

Ingress: HTTP-level routing (host/path -> Service). In k3s that is Traefik, disabled for week 1; see 10.41.

Other objects: Namespace (scoping), ConfigMap/Secret (config, day 4), PV/PVC/StorageClass (storage, day 5), HorizontalPodAutoscaler (scaling), Role/RoleBinding/ServiceAccount (RBAC, beyond this week).

The declarative loop, with a worked example

Manifest says: kind: Deployment, replicas: 3, image: nginx, selector: app=web.

  1. kubectl apply -f -> API server validates, writes to etcd.
  2. Deployment controller sees “3 replicas wanted, 0 exist”, creates a ReplicaSet (with a new revision).
  3. ReplicaSet controller creates 3 Pod objects.
  4. Scheduler assigns each pod to a node.
  5. kubelet on that node pulls the image, starts the container via containerd, reports Ready.
  6. kube-proxy programs Service endpoints to the new pod IPs.

Now kill a pod: kubectl delete pod <name>. ReplicaSet notices 2 of 3 and immediately creates a replacement. THIS is the core idea: nobody “restarts” anything. Desired state is reconciled. Docker’s restart: always is a degenerate case of this.

Labels and selectors: the glue

Everything is glued together by labels, not by names or IDs.

kubectl get pods --show-labels
kubectl get pods -l app=web                # label selector
kubectl get pods -l 'app in (web,api),tier!=db'
kubectl label pod <name> env=prod          # add/change labels live
kubectl label pod <name> env-              # remove

Service -> pods: the Service’s selector must match the pods’ labels. Deployment -> pods: the Deployment’s selector must match its template labels (and the selector is immutable after creation, so pick it carefully).

Namespaces: logical partitions

kubectl get namespaces
kubectl create namespace lab
kubectl -n lab get pods          # scope a command to a namespace
kubectl get all -n lab
  • Default objects: kube-system (control plane), kube-public, kube-node-lease, default (yours).
  • DNS names are namespace-scoped: svc-name.namespace.svc.cluster.local.
  • kubectl config set-context --current --namespace=lab makes lab your default for the rest of the week. Use separate namespaces for challenges to keep cleanup easy: kubectl delete namespace lab removes everything in it.

kubectl basics: the only verbs you need this week

kubectl get    <resource>                 # list (add -o wide / -o yaml / -o json)
kubectl describe <resource> <name>        # rich detail + events, the debug tool
kubectl apply -f file.yaml                # create/update desired state
kubectl delete <resource> <name>          # delete (controllers reconcile!)
kubectl logs <pod> [--previous]           # stdout (previous = crashed container)
kubectl exec -it <pod> -- bash            # enter a container
kubectl port-forward <pod|svc|deploy> <host>:<port>
kubectl explain <resource>.<field>        # offline API docs

kubectl get all does NOT show everything (no PVCs, Ingresses, etc.). Use kubectl api-resources to see every resource kind.

The three most common mental traps

  1. “I restarted the container”: there is no restart. You edited desired state (or the controller replaced a dead pod). The pod is a different object now. kubectl get pods shows the new one with a new name/UID.
  2. “I edited the YAML but nothing changed”: you must re-apply or use kubectl edit/kubectl patch. Editing a running pod’s manifest does nothing; the API server stores what you last applied.
  3. “I deleted the pod, why is it back”: a controller (Deployment) owns it. Delete the Deployment, or scale it to 0.

Terminology quick map (k3s vs vanilla)

k3s name Standard name
server node control-plane node (runs kubelet too, by default)
agent node worker node
–disable traefik disable default ingress controller
servicelb k3s’s built-in LoadBalancer provider
local-path provisioner StorageClass “local-path” for PVCs

Next: 10.21 - Week 1 Plan for the schedule, then 10.22 - Challenges to start practicing.


This site uses Just the Docs, a documentation theme for Jekyll.