This article is based on a two-hour Kubernetes internals session I delivered at DevWeekends.
Recording: https://youtu.be/joDE9l759vE?si=YXeMjqnpcJDWJd0r


kubectl apply -f web.yaml

It is one of the most familiar commands in Kubernetes. You run it, Kubernetes accepts the object, and a few moments later your Pods are running. But that simple command hides a distributed system.

Between your local YAML file and a reachable application, several independent components observe state, make decisions, create new objects, assign work to Nodes, start Linux processes, configure networking, and continuously repair the system when reality drifts away from what you declared.

This article follows one workload, one object at a time, through that entire journey.

The workload is intentionally simple:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-api
  template:
    metadata:
      labels:
        app: web-api
    spec:
      containers:
        - name: web-api
          image: nginx:1.27

---
apiVersion: v1
kind: Service
metadata:
  name: web-api
spec:
  selector:
    app: web-api
  ports:
    - port: 80
      targetPort: 80

Our desired result is straightforward:

  • 3 web-api Pods
  • 1 stable Service
  • a reachable application

But the interesting part is everything Kubernetes has to do to make that true.

A single container is easy to manage. If it dies, we can start another one. The problem changes when we have hundreds of containers spread across many machines. Which machine should run each workload? What happens when a container dies? What happens when an entire machine disappears? How do we scale without manually coordinating everything? Kubernetes exists to continuously solve this coordination problem.

The mental model: Kubernetes is a control system

A useful definition of Kubernetes is:

Kubernetes is a distributed control system that continuously tries to make the actual state of your applications match the desired state you declared.

Why “distributed”? Because there is no single Kubernetes process that schedules workloads, starts containers, monitors Nodes, stores state, configures networking, and repairs failures. Those responsibilities are distributed across independent components such as the API Server, controllers, scheduler, kubelets, and runtimes. They coordinate primarily through Kubernetes API state.

That definition matters because it changes how you think about the architecture. Kubernetes is not one large program receiving a command and executing every step itself.

Big Word Alert - Declarative:
Declarative means you describe what state you want, not the exact sequence of commands required to produce it. replicas: 3 says “I want three replicas.” Kubernetes decides how to continuously make that true.

Instead, it is made of multiple components that continuously:

Observe
  ↓
Compare
  ↓
Act
  ↓
Repeat

Big Word Alert - Reconciliation:
Reconciliation is the process of comparing desired state with observed state and taking action to reduce the difference. This loop is one of the most important ideas in Kubernetes.

You declare:

Deployment/web-api
replicas = 3

Suppose the cluster currently has:

Actual Pods = 2
Desired Pods = 3

Kubernetes notices the difference and works to close it. That idea, reconciliation, is the thread connecting almost every component we are about to follow.

If you understand three things, Kubernetes architecture becomes much easier to reason about:

  1. What state exists right now?
  2. Which component is watching that state?
  3. What state change does that component make next?

We will use those questions throughout this article.

The journey we are going to follow

Our workload moves through these stages:

YAML
  ↓
API
  ↓
STORED
  ↓
ReplicaSet
  ↓
Pods
  ↓
BOUND
  ↓
RUNNING
  ↓
REACHABLE

And each transition has an owner:

kubectl
   ↓
kube-apiserver
   ↓
etcd
   ↓
kube-controller-manager
   ↓
ReplicaSet + Pods
   ↓
kube-scheduler
   ↓
Worker Node
   ↓
kubelet + runtime
   ↓
CNI
   ↓
Service + DNS

Let us start where the cluster first sees our request.

1. kubectl reaches the API Server

When we run:

kubectl apply -f web.yaml

kubectl does not create containers or talk to Node. It acts as a client of the Kubernetes API.

The request reaches:

kubectl
   ↓
kube-apiserver

The API Server is the front door to Kubernetes cluster state. Before the object can become durable state, the API Server checks the request.

A useful mental model is:

Authenticate
    ↓
Authorize
    ↓
Admission
    ↓
Validate
    ↓
Persist

Authenticate

Who are you?

The cluster first establishes the identity making the request.

Authorize

Are you allowed to perform this action?

Kubernetes authorization is typically expressed in terms of:

identity + verb + resource + scope

For example:

User:      Talha
Verb:      create
Resource:  deployments
Namespace: default

Admission

Should cluster policy modify or reject this request?

Admission is where policy and defaults can affect the object before storage.

Validate

Is this a valid Kubernetes object?

Persist

Only after acceptance can the desired state become durable.

This distinction is important:

An accepted API request is not the same thing as a running workload.

At this stage, the cluster understands what we asked for, but no container is running yet.

Aside: A successful kubectl apply does not mean your application is running. It means Kubernetes has accepted your desired state. Execution is the responsibility of other components that react afterward.

2. etcd: where Kubernetes remembers

After the API Server accepts the Deployment and Service, their state must become durable.

That is the role of etcd.

kube-apiserver
      ↓
     etcd

etcd stores Kubernetes API state in a strongly consistent key-value store.

Big Word Alert - Strong Consistency:
Strong consistency means clients should observe a coherent view of committed state rather than different etcd members independently disagreeing about the current Kubernetes state.

For our example, Kubernetes now remembers something conceptually like:

Deployment/web-api
replicas = 3

Service/web-api
selector = app=web-api

The important point is what etcd stores and what it does not.

etcd stores

  • Deployments
  • Pods
  • Nodes
  • Services
  • Secrets
  • CRDs
  • other Kubernetes API state

etcd does not store

  • container images
  • application logs
  • your application database
  • your business data

etcd is the persistence layer for cluster API state. In a multi-member etcd cluster, members use Raft consensus so the cluster can agree on a consistent ordered state.

Big Word Alert - Raft Consensus:
Raft is the consensus algorithm etcd uses so multiple members can agree on one ordered sequence of committed state changes. A write requires agreement from a quorum, not every member.

A typical picture is:

etcd-1
  │
etcd-2
  │
etcd-3

with properties such as:

Strong consistency
Raft consensus
Durable cluster state

At this point our desired state is durable.

But there is still a problem.

Desired replicas = 3
Actual Pods      = 0

Who notices that gap?

Aside: Most Kubernetes components do not treat etcd as their shared database client. They coordinate through the Kubernetes API. etcd sits behind the API Server as the durable backing store for API state.

3. kube-controller-manager: where reconciliation begins

This is the point where Kubernetes starts to look less like a normal CRUD API and more like a control system.

kube-controller-manager runs a collection of controllers.

Examples include:

  • Deployment Controller
  • ReplicaSet Controller
  • Node Controller
  • Job Controller
  • and others

The important model is:

Kubernetes API
      ↓ watch
kube-controller-manager
      ↓ reconcile
update API objects

Controllers continuously:

Observe
  ↓
Compare
  ↓
Act
  ↓
Repeat

Big Word Alert - Control Loop:
A control loop repeatedly observes a system, compares what exists with what should exist, and applies corrective action. Kubernetes is composed of many such loops running independently.

They do not wait for you to issue another command. They keep watching cluster state and try to make actual state move toward desired state.

Also note something important:

kube-controller-manager does not run your containers.

Aside: Controllers usually change API state, not the underlying machine directly. A Deployment controller creates a ReplicaSet object, a ReplicaSet controller creates Pod objects, and later components react to those new objects.

Its job is primarily to create or update Kubernetes objects through the API. Now let us apply that generic model to our actual Deployment.

Aside: In practice, Kubernetes controllers commonly use the API's list/watch mechanism, usually through client-side caches and informers. They maintain a local view of relevant objects and react when that observed state changes. They do not read etcd directly.

4. Deployment Controller creates a ReplicaSet

The Deployment object says:

web-api
replicas = 3

The Deployment Controller notices the stored Deployment and creates a ReplicaSet representing that Pod template.

Conceptually:

Deployment/web-api
replicas = 3
      ↓
Deployment Controller
      ↓
ReplicaSet/web-api-7c9f...
desired = 3

Now our state looks like:

Deployment stored
ReplicaSet created
Pods = 0

The Deployment Controller has done its part. But the ReplicaSet now has its own reconciliation problem:

ReplicaSet desired = 3
Actual matching Pods = 0

That leads to the next controller.

5. ReplicaSet Controller creates the Pods

The ReplicaSet Controller sees:

desired = 3
actual  = 0

and closes the gap by creating three Pod objects.

ReplicaSet
desired = 3
     ↓
ReplicaSet Controller
     ↓
Pod/web-api-a
Pod/web-api-b
Pod/web-api-c

Now the cluster has three Pod objects. But these Pods are not yet running. At this stage they still have no Node assignment.

Conceptually:

web-api-a → nodeName: empty
web-api-b → nodeName: empty
web-api-c → nodeName: empty

This is a critical distinction:

Creating a Pod object is not the same thing as executing its container.

Aside: A Kubernetes object can exist long before its real-world effect exists. At this moment the Pods are valid API objects, but no Node has accepted responsibility for running them yet.

The controllers created desired objects. Someone else must decide where those objects should run. That is the scheduler's job.

6. kube-scheduler: choosing where Pods run

The scheduler watches for Pods that do not yet have a Node assignment. For our three Pods:

Pod exists
nodeName = empty

The useful mental model for scheduling is:

Filter
  ↓
Score
  ↓
Bind

Filter

Which Nodes are capable of running this Pod?

Imagine:

worker-1 → yes
worker-2 → yes
worker-3 → yes

Conceptually this stage answers:

Can this Pod run here?

Score

Among feasible Nodes, which one is the better fit?

For example:

worker-1 → 72
worker-2 → 61
worker-3 → 88

The full Scheduling Framework has more extension points, but Filter → Score → Bind is the useful core path for this workload.

Bind

The scheduler commits the placement decision.

Conceptually:

Pod/web-api-a
nodeName = worker-3

Big Word Alert - Binding:
Binding is the scheduler committing the Pod-to-Node placement decision. After scheduling, the Pod is associated with a Node, but the scheduler still does not start the container.

Now the Pod has been bound. But the scheduler still has not started nginx.

That leads to one of the most useful distinctions in Kubernetes:

Scheduler decides WHERE. Kubelet makes sure it RUNS.

7. Requests and limits belong to different phases

While discussing scheduling, it is worth separating resource requests from resource limits.

For example:

resources:
  requests:
    cpu: "500m"
    memory: "256Mi"
  limits:
    cpu: "1"
    memory: "512Mi"

Requests

Requests are scheduling inputs. The scheduler plans placement using requests and Node allocatable resources. It does not place Pods simply by reading current live CPU utilization. It works from declared requests.

Aside: Resource requests are not a prediction of real-time usage. They are declarations that the scheduler uses for placement and accounting. A Node being quiet right now does not automatically mean Kubernetes treats all of that idle capacity as available for new Pods.

Limits

Limits become runtime constraints. The kubelet and runtime translate them into Linux resource controls.

A useful summary is:

Requests → scheduling
Limits   → runtime enforcement

For example:

  • CPU over limit can lead to throttling
  • memory over limit can result in the process being OOM-killed

At this point the scheduler has finished. Our Pods are bound but the execution still has to happen.

8. Crossing the boundary: control plane to Worker Node

This is an important architectural transition. The control plane has made decisions. Now the Worker Node must make those decisions real.

A Worker Node is a physical or virtual machine running the components needed to realize assigned Pods.

A simplified worker looks like:

Worker Node
├── kubelet
├── container runtime
├── networking
├── storage integration
└── Pods

The key idea is:

The control plane decides. The Worker Node executes.

Suppose web-api-a has been assigned to:

worker-3

The scheduler has finished its responsibility. Now the kubelet on worker-3 must notice the assignment.

9. kubelet: the Node agent

Each kubelet watches the API for Pods assigned to its own Node.

Conceptually:

kube-apiserver
      ↓
Pod/web-api-a
nodeName = worker-3
      ↓
kubelet on worker-3

The kubelet's responsibility is to make the assigned PodSpec real on that Node.

Its work includes:

1. Watch assigned Pods
2. Prepare volumes, secrets, configuration
3. Manage container lifecycle and probes
4. Report Pod, container, and Node status

The kubelet also participates in node-side reconciliation.

It keeps comparing:

desired PodSpec
      vs
what is actually running

But the kubelet itself does not directly implement all container execution logic. It delegates container operations through CRI.

10. From PodSpec to a Linux process

The execution path is layered:

kubelet
   ↓
CRI
   ↓
containerd / CRI-O
   ↓
runc
   ↓
Linux kernel
   ↓
nginx process

CRI

The Container Runtime Interface is the standard API between kubelet and the container runtime. It keeps kubelet from being tightly coupled to one specific runtime implementation.

Big Word Alert - CRI:
Container Runtime Interface is the contract between kubelet and a container runtime. kubelet asks for container lifecycle operations through CRI instead of being tightly coupled to a specific runtime implementation.

Container runtime

A runtime such as:

containerd
CRI-O

handles container lifecycle operations.

OCI runtime

A lower-level runtime such as runc creates the container environment.

Linux

At the bottom of the stack, your application is still a Linux process. That is an important grounding point. A container is not magic. The process gets isolation and resource controls through Linux mechanisms such as:

Namespaces

Control what the process can see, including networking, PIDs, mounts, and other isolated views.

cgroups

Control and account for resources such as CPU and memory.

So the stack eventually resolves to:

Kubernetes abstraction
        ↓
container runtime
        ↓
Linux isolation + resource controls
        ↓
application process

Big Word Alert - Namespaces vs cgroups:
Namespaces answer “what can this process see?”
cgroups answer “how much can this process use?”
Together they provide much of the isolation and resource-control foundation that containers rely on.

At this point nginx can be running. But we still need to understand one important piece of Pod-level infrastructure.

Aside: Kubernetes does not fundamentally “run containers.” Eventually the stack reaches the Linux kernel and starts ordinary processes with isolation and resource controls around them.

11. Pod sandbox and the pause container

A Pod can contain multiple containers, and those containers need a shared Pod-level environment.

For example:

Pod/web-api-a

pause / sandbox
nginx
sidecar

The Pod sandbox holds the Pod-level namespaces. The application containers join that shared environment.

In networking terms, containers inside the Pod can share:

same network namespace
same localhost
same Pod IP after CNI setup

Aside: “Pod sandbox” and “pause container” are closely related, but they are not strictly the same abstraction. The sandbox is the runtime-level Pod environment; on Linux, a tiny pause container is commonly used to keep shared namespaces alive.

Why does this matter?

Because the Pod-level infrastructure should not disappear just because one application container restarts.

Imagine nginx crashes. We do not want to destroy the entire Pod-level network identity just because one process died. The sandbox remains, and the replacement application container can rejoin the Pod environment.

A useful model is:

Pod
│
├── sandbox / pause
│      └── holds namespaces
│
├── nginx
└── sidecar

The sandbox gives the Pod-level environment continuity across individual container restarts. But there is one more step.

We have finally reached a running Linux process. But a running process is not yet a usable distributed application. It still needs network identity, routes, stable discovery, and a path for traffic.

Running is not the same as reachable.

The Pod-level network namespace exists. Now Kubernetes needs to configure actual network connectivity inside it. That is where CNI enters.

12. CNI gives the Pod network connectivity

Big Word Alert - CNI:
Container Network Interface is the standard Kubernetes runtimes use to integrate Pod networking implementations. Kubernetes defines the networking expectations; a CNI implementation performs the actual network setup.

Once the Pod sandbox exists, the configured CNI implementation is invoked to configure networking.

Conceptually:

kubelet
   ↓
runtime
   ↓
Pod sandbox
   ↓
CNI
   ↓
IPAM
   ↓
Pod interface + IP + routes

Big Word Alert - IPAM:
IP Address Management is the mechanism responsible for allocating and managing addresses for network endpoints such as Pods.

CNI configures things such as:

  • a network interface in the Pod namespace
  • a Pod IP address
  • routing or connectivity needed to reach the cluster network

For example:

Pod/web-api-a
eth0 = 10.244.3.17

Now our process is not only running. It has network identity inside the cluster. But Pod IPs are dynamic. If a Pod disappears and is replaced, the replacement can receive a different IP.

That creates the next engineering problem:

How do clients reach a workload whose backend addresses can change?

The answer is a Service.

13. Service and EndpointSlice: stable identity over dynamic Pods

The Service provides a stable frontend identity. The Pods behind it remain dynamic.

For example:

Service/web-api
ClusterIP = 10.96.20.10
selector = app=web-api

while the backend Pods may currently be:

10.244.1.12:80
10.244.2.9:80
10.244.3.17:80

Kubernetes represents those backend endpoints through EndpointSlices.

A useful mental model is:

Service = stable frontend

EndpointSlice = current backend set

Aside: An EndpointSlice does not forward traffic. It describes the current backend endpoints. The Service data path, implemented through mechanisms such as kube-proxy rules or eBPF, uses that backend information to route traffic.

That distinction becomes very visible in a real cluster.

For example:

kubectl get endpointslices

might show something like:

NAME            ADDRESSTYPE   PORTS   ENDPOINTS
web-api-clphj   IPv4          80      10.244.1.3,10.244.2.4,10.244.1.6...

Those addresses correspond to current backend Pods. If one Pod dies and a replacement receives a new IP, the Service identity can stay the same while the backend endpoint set changes.

14. DNS and Service forwarding are different jobs

A request path through the cluster involves multiple responsibilities.

For a normal Service:

client Pod
   ↓
CoreDNS
   ↓
Service ClusterIP
   ↓
kube-proxy / eBPF data path
   ↓
backend Pod

Suppose the client asks for:

web-api.default.svc.cluster.local

CoreDNS

CoreDNS resolves the Service name to the Service's stable virtual IP.

Conceptually:

web-api
   ↓
10.96.20.10

Aside: DNS resolution and packet forwarding are separate problems. CoreDNS can tell the client which Service IP to use, but it is not the component that forwards the request from that Service IP to a backend Pod.

Service implementation

The node networking implementation then provides the data path from the Service IP to the current backend endpoints.

The important distinction is:

DNS resolves the Service name. Service networking gets traffic to a backend Pod.

EndpointSlice holds backend state. The Service remains the stable identity. At this point our journey has reached:

RUNNING
   ↓
REACHABLE

15. Running is not the same as Ready

Kubernetes also distinguishes process existence from traffic readiness. Three probes answer different questions.

startupProbe

Has the application finished starting?

readinessProbe

Should this Pod receive traffic?

If readiness fails:

Ready = False

The container can still be running while the Pod should not receive normal Service traffic.

livenessProbe

Should kubelet restart this container?

A liveness failure can cause kubelet to restart the container.

This gives us another useful distinction:

Running, Ready, and Healthy are not the same question.

It also shows why responsibility matters.

Container crash

Kubelet applies the Pod restart policy. The Pod object can remain the same.

Pod disappears

A higher-level controller such as the ReplicaSet Controller creates a replacement Pod.

Different failure scopes are owned by different control loops.

16. Kubernetes becomes clearer when something fails

The best way to understand reconciliation is to break the desired state intentionally.

Suppose our ReplicaSet wants:

desired = 3

and we delete one Pod:

kubectl delete pod <pod-name>

Now:

desired = 3
actual  = 2

The ReplicaSet Controller observes that difference.

It reconciles:

3 - 2 = 1

and creates one new Pod.

The full recovery path looks like:

ReplicaSet Controller
        ↓
creates replacement Pod

Scheduler
        ↓
assigns a Node

kubelet
        ↓
realizes the Pod

runtime
        ↓
starts the process

CNI
        ↓
configures network

EndpointSlice
        ↓
backend membership updates when appropriate

The important insight is:

No single central brain had to imperatively execute the entire recovery sequence.

Independent control loops reacted to changing shared state. This is why the earlier definition matters so much. Kubernetes keeps converging toward desired state.

Big Word Alert - Convergence:
Convergence is the process of the system moving from its current state back toward the declared desired state. During recovery, Kubernetes may temporarily be “wrong,” but its control loops keep working until the gap is closed.

17. Worker Node failure is a different control loop

A full Node failure follows a different path.

Conceptually:

1. kubelet heartbeat stops
2. Node Lease becomes stale
3. Node Controller marks the Node unhealthy
4. workloads on that Node become unavailable
5. controllers create replacement Pods
6. scheduler places the new Pods

One subtle but important point:

The scheduler does not move the old Pod.

The scheduler places new Pod objects.

That distinction helps explain Kubernetes object identity and why Pods are treated as replaceable.

18. Deployment rollout: changing desired state instead of repairing it

Not every reconciliation is caused by failure. Sometimes you intentionally change desired state.

For example:

kubectl set image deployment/web-api web-api=nginx:1.28

Now the Pod template changes. The Deployment creates a new ReplicaSet.

Conceptually:

ReplicaSet v1
image = nginx:1.27
replicas 3 → 0

ReplicaSet v2
image = nginx:1.28
replicas 0 → 3

The Deployment manages rollout across ReplicaSets. This is why Deployment and ReplicaSet solve different problems.

Deployment

Manages application rollout. It owns ReplicaSets, supports rolling updates, and maintains revision history.

ReplicaSet

Maintains the required number of matching Pods for one specific Pod template. That gives us two distinct cases:

replicas: 3 → 5

usually scales the same ReplicaSet.

But:

image: v1 → v2

changes the Pod template, so the Deployment creates a new ReplicaSet.

19. Persistent state requires another lifecycle

Pods are replaceable. Application data often should not be. Kubernetes separates workload lifetime from storage lifetime.

A simplified storage path is:

Pod
 ↓ mount
PVC
 ↓
PV
 ↓
CSI
 ↓
disk / cloud / SAN

PVC

A PersistentVolumeClaim represents a storage request.

PV

A PersistentVolume represents the storage resource.

CSI

The Container Storage Interface standardizes how Kubernetes integrates with storage drivers.

A StorageClass can define how storage is dynamically provisioned for a claim.

For stateful workloads, StatefulSet adds useful properties such as:

  • stable Pod identity
  • stable per-Pod storage
  • ordered behavior

The important architectural idea remains the same:

Kubernetes separates replaceable compute objects from storage that may need a longer lifetime.

20. Kubernetes is extensible because the same control model repeats

One of the most powerful parts of Kubernetes is that the API-and-controller pattern is not limited to built-in objects.

You can extend Kubernetes with:

CRD
 ↓
Custom Resource
 ↓
Controller
 ↓
Observed status / real resources

A CRD adds a new API type. A Custom Resource is an instance of that API. A controller watches the resource and reconciles desired state into real outcomes. This same pattern is why Kubernetes can support domain-specific systems without abandoning its core architecture.

For example, Volcano extends Kubernetes with workload and scheduling concepts for batch, AI, and HPC.

That is particularly interesting because it is not a separate universe.

It is the same core idea:

API state
   ↓
controllers
   ↓
scheduling
   ↓
reconciliation

extended for more specialized workloads.

21. The full journey

We can now trace the complete path of our original command.

kubectl apply -f web.yaml

Step 1: submit desired state

kubectl
   ↓
kube-apiserver

Step 2: persist accepted API state

API Server
   ↓
etcd

Step 3: Deployment Controller reacts

Deployment
   ↓
ReplicaSet

Step 4: ReplicaSet Controller closes the replica gap

ReplicaSet
   ↓
Pods

Step 5: scheduler places unscheduled Pods

Pods
   ↓
Filter
   ↓
Score
   ↓
Bind

Step 6: each Pod receives a Node assignment

Pod.spec.nodeName = worker-x

Step 7: kubelet on that Node notices the Pod

API
 ↓
kubelet

Step 8: kubelet delegates execution through CRI

kubelet
   ↓
CRI
   ↓
container runtime
   ↓
runc
   ↓
Linux process

Step 9: the Pod sandbox provides Pod-level infrastructure

pause / sandbox
      ↓
shared Pod environment

Step 10: CNI configures networking

Pod namespace
   ↓
interface
   ↓
Pod IP
   ↓
routes

Step 11: Service and EndpointSlice provide stable service discovery over dynamic backends

Service
   ↓
current endpoint set
   ↓
Pods

Step 12: DNS and Service networking make the workload reachable

name
 ↓
Service IP
 ↓
backend Pod

We have moved from:

YAML

to:

RUNNING + REACHABLE

The architectural lesson

After following the complete journey, the most useful way to think about Kubernetes is not as a list of components. It is a system of state transitions. Each component owns a specific part of that system.

API Server
accepts and exposes state

etcd
persists state

Controllers
reconcile desired and actual state

Scheduler
decides where Pods should run

kubelet
realizes assigned Pods on Nodes

runtime
creates containers and processes

CNI
configures Pod networking

Service + DNS
provide stable reachability

The deeper pattern is:

Kubernetes is a collection of loosely coupled control loops coordinating through declarative API state.

Each component watches or changes state. The system keeps converging.

Observe → Compare → Act → Repeat

Once you see Kubernetes through that lens, the architecture becomes much less mysterious.

Instead of asking:

What does this component do?

ask more useful engineering questions:

What state is it watching?

What difference is it trying to detect?

What API object does it create or update?

Which component reacts next?

What happens if this component or Node disappears?

Those questions scale much better than memorizing diagrams.

Final takeaway

One command began the story:

kubectl apply -f web.yaml

But no single Kubernetes component performed the whole journey.

  • The API Server accepted state.
  • etcd remembered it.
  • Controllers created new objects.
  • The scheduler made placement decisions.
  • kubelet realized those decisions on Nodes.
  • The runtime created Linux processes.
  • CNI configured networking.
  • Services made dynamic Pods reachable through a stable identity.
  • And after everything was running, those same control loops kept watching.

That is the part of Kubernetes architecture I find most interesting:

The system is never simply “done.” It is continuously observing, comparing, acting, and converging.

Observe
  ↓
Compare
  ↓
Act
  ↓
Repeat

And that is the mental model I would keep.