What happens step by step when you run kubectl apply on a Deployment YAML?
Quick Answer
kubectl sends the YAML to the API Server via HTTPS, which validates it, runs admission controllers, and stores it in etcd. The Deployment controller creates a ReplicaSet, the Scheduler assigns Pods to nodes, kubelets start containers, and kube-proxy programs routing rules so traffic can reach the new Pods.
Detailed Answer
Think of this like ordering furniture from an online store. You submit your order (kubectl apply) to the website (API Server), which checks your payment and address are valid. The order goes into the warehouse system (etcd). The logistics team (Scheduler) figures out which delivery truck and warehouse has capacity. The warehouse worker (kubelet) picks, packs, and ships your item. Finally, the GPS routing system (kube-proxy) updates so future deliveries know the new delivery route. Each step is handled by a different team, and they coordinate through the central order system, not by talking directly to each other.
Step 1 — kubectl reads your YAML file, validates it client-side (basic schema checking), then sends an HTTPS POST (for create) or PUT/PATCH (for update) request to the API Server endpoint. For a Deployment, that's POST to /apis/apps/v1/namespaces/default/deployments. The request includes your authentication credentials from ~/.kube/config.
Step 2 — The API Server processes the request through its pipeline: Authentication (verifies your identity via certificate, token, or OIDC), Authorization (checks RBAC policies — do you have 'create deployments' permission in this namespace?), Mutating Admission Webhooks (can modify the request — for example, injecting sidecar containers or setting default resource limits), Object Schema Validation (is the YAML structurally valid?), Validating Admission Webhooks (can reject but not modify — for example, enforcing that all Pods must have resource limits), and finally Persistence (writes the Deployment object to etcd). The API Server responds with the created object.
Step 3 — The Deployment controller (inside kube-controller-manager) receives a watch notification that a new Deployment exists. It creates a ReplicaSet object with the Pod template from the Deployment spec. The ReplicaSet controller then receives its own watch notification and creates the specified number of Pod objects (with spec.nodeName empty — they're unscheduled).
Step 4 — The Scheduler receives watch notifications about the new unscheduled Pods. For each Pod, it runs filtering (which nodes meet the Pod's requirements: enough CPU/memory, correct nodeSelector, tolerate any taints?) and scoring (which eligible node is the best fit: most balanced resources, already has the image cached, spreading across zones?). It then updates each Pod's spec.nodeName field via the API Server.
Step 5 — The kubelet on each assigned node receives a watch notification about the new Pod. It calls the container runtime (containerd) via CRI to pull the container image (if not cached) and start the containers. It then begins executing probes (startup probe first if configured, then liveness and readiness probes).
Step 6 — Once a Pod's containers are running and the readiness probe passes, the Endpoint controller adds the Pod's IP to the Service's Endpoints object. kube-proxy on every node picks up this change and updates iptables/IPVS rules so traffic to the Service ClusterIP gets routed to the new Pod.
A production-critical insight: this entire chain is asynchronous and event-driven. kubectl apply returns immediately after Step 2 — it doesn't wait for Pods to actually run. This is why kubectl rollout status exists: it watches the Deployment until all Pods are Ready. In CI/CD pipelines, always follow kubectl apply with a rollout status check, or you'll report deploys as successful before they actually complete (or fail).