{"id":"l-dk-4-1","courseId":"docker-kubernetes","lang":"en","kind":"lesson","summary":"Explain how Kubernetes turns declared desired state into running containers through its control loops, name the main cluster components, and create a local kind cluster to work in.","html":"<p>Compose runs your stack on one machine. When that machine dies, so does your service, and nothing brings it back until a person notices. Kubernetes exists for the other case: many machines, containers that must keep running when any one of them fails, and changes rolled out without downtime. To use it well, you need one idea more than any command.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The control loop</h2>\n<p>In Kubernetes you don’t say “start three containers”. You say “there should be three replicas of notes-api”, and store that as an object in the cluster. Then a <strong>controller</strong> runs a loop, continuously:</p>\n<ol>\n<li><strong>Observe</strong> the actual state: how many notes-api Pods exist and are running?</li>\n<li><strong>Compare</strong> it with the desired state in the object.</li>\n<li><strong>Act</strong> to close the gap: create a Pod, delete one, replace one.</li>\n</ol>\n<p>The loop never finishes. If a node dies and takes a Pod with it, the next pass sees two where three are wanted and creates one. If you change the desired count to five, it creates two more. If someone deletes a Pod by hand, it’s replaced within seconds. This is what people mean by <strong>declarative</strong>: you describe the destination, and the system keeps steering towards it.</p>\n<p>The thermostat comparison helps. You set 21°C; the thermostat doesn’t run a fixed “heat for 20 minutes” script. It measures, compares and switches the heater on or off for as long as it’s powered.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">The parts of a cluster</h2>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 330\" role=\"img\" aria-labelledby=\"l-dk-4-1-t1\"><defs><marker id=\"arrow-l-dk-4-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-dk-4-1-t1\">kubectl sends objects to the API server, which stores them in etcd. The scheduler and controller manager watch the API server. On each worker node a kubelet watches for Pods assigned to it and starts containers through the container runtime.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"40\" width=\"120\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"80\" y=\"70\" text-anchor=\"middle\">kubectl</text>\n  <rect class=\"d-box-primary\" x=\"180\" y=\"20\" width=\"500\" height=\"130\" rx=\"14\"/>\n  <text class=\"d-label-muted\" x=\"430\" y=\"42\" text-anchor=\"middle\">control plane</text>\n  <rect class=\"d-box-accent\" x=\"200\" y=\"56\" width=\"150\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"275\" y=\"86\" text-anchor=\"middle\">API server</text>\n  <rect class=\"d-box-warn\" x=\"200\" y=\"112\" width=\"150\" height=\"32\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"275\" y=\"133\" text-anchor=\"middle\">etcd</text>\n  <rect class=\"d-box\" x=\"380\" y=\"56\" width=\"130\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"445\" y=\"86\" text-anchor=\"middle\">scheduler</text>\n  <rect class=\"d-box\" x=\"530\" y=\"56\" width=\"140\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"600\" y=\"80\" text-anchor=\"middle\">controller</text>\n  <text class=\"d-label\" x=\"600\" y=\"98\" text-anchor=\"middle\">manager</text>\n  <path class=\"d-arrow\" d=\"M140 65 L198 78\" marker-end=\"url(#arrow-l-dk-4-1-1)\"/>\n  <path class=\"d-line\" d=\"M350 81 L380 81\"/>\n  <path class=\"d-line\" d=\"M275 106 L275 112\"/>\n  <path class=\"d-line\" d=\"M510 72 L530 72\"/>\n  <rect class=\"d-box-success\" x=\"120\" y=\"200\" width=\"250\" height=\"110\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"245\" y=\"224\" text-anchor=\"middle\">node 1</text>\n  <text class=\"d-label\" x=\"245\" y=\"252\" text-anchor=\"middle\">kubelet → containerd</text>\n  <text class=\"d-code\" x=\"245\" y=\"282\" text-anchor=\"middle\">pod  pod</text>\n  <rect class=\"d-box-success\" x=\"410\" y=\"200\" width=\"250\" height=\"110\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"535\" y=\"224\" text-anchor=\"middle\">node 2</text>\n  <text class=\"d-label\" x=\"535\" y=\"252\" text-anchor=\"middle\">kubelet → containerd</text>\n  <text class=\"d-code\" x=\"535\" y=\"282\" text-anchor=\"middle\">pod</text>\n  <path class=\"d-arrow\" d=\"M260 150 L245 198\" marker-end=\"url(#arrow-l-dk-4-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M300 150 L520 198\" marker-end=\"url(#arrow-l-dk-4-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"420\" y=\"182\" text-anchor=\"middle\">watch: Pods for my node</text>\n</svg><figcaption>Every component talks to the API server; controllers and kubelets reconcile state</figcaption></figure>\n<ul>\n<li>The <strong>API server</strong> is the front door. kubectl, controllers and kubelets all talk to it, and it validates, authenticates and authorises every request.</li>\n<li><strong>etcd</strong> is a consistent key-value store holding every object. Only the API server talks to it.</li>\n<li>The <strong>scheduler</strong> watches for Pods without a node and picks one, based on free resources, constraints and spreading.</li>\n<li>The <strong>controller manager</strong> runs the built-in controllers: Deployments, ReplicaSets, Jobs, node health, and more.</li>\n<li>On every worker node, the <strong>kubelet</strong> watches for Pods assigned to its node and starts their containers through a <strong>container runtime</strong>, usually containerd. Your Docker-built images run there unchanged, because images follow the OCI standard.</li>\n</ul>\n<p>Managed services like Amazon EKS run the control plane for you. You still need to understand it, because every error message you’ll read refers to one of these parts.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">A cluster on your laptop</h2>\n<p>kind (“Kubernetes in Docker”) runs a whole cluster inside Docker containers. It’s quick to create and quick to throw away, which is exactly what you want for learning:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kind</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> cluster</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> config</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> current-context</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">      # kind-notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> nodes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kind</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> load</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> docker-image</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.0.0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span></code></pre></div>\n<p>The last command copies your local image into the kind node, so the cluster can run it without a registry. minikube and Docker Desktop’s built-in Kubernetes work too; the <code dir=\"ltr\">kubectl</code> commands in this course are the same everywhere.</p>\n<p>In kind, the control plane itself runs as Pods on the control-plane node. Run <code dir=\"ltr\">kubectl get pods -n kube-system</code> and you’ll see the API server, etcd, the scheduler and the controller manager listed like any other workload, along with the cluster DNS (CoreDNS) and kube-proxy. On a managed cluster you won’t see the control plane there, because the provider runs it out of sight.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Watch the loop work</h2>\n<p>Create a Deployment imperatively, once, to see reconciliation with your own eyes:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --image=notes-api:1.0.0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --replicas=3</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> pods</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> delete</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> pod</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">one-of-the-pod-name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">s</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> pods</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --watch</span></span></code></pre></div>\n<p>Within seconds a new Pod with a new name appears. You didn’t ask for it; the controller did its job. Now look at what you created:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -o</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> yaml</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> explain</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment.spec.replicas</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> delete</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api</span></span></code></pre></div>\n<p><code dir=\"ltr\">-o yaml</code> shows the full object Kubernetes stored, including many defaults you didn’t set. <code dir=\"ltr\">kubectl explain</code> documents any field from the cluster itself; it’s the fastest reference you have. From the next lesson on you’ll write these objects as files and apply them, which is how real teams work.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>The wrong context</span></p>\n<p>kubectl acts on its <em>current context</em>. People who work with several clusters eventually run a delete against production while thinking they’re in staging. Check with <code dir=\"ltr\">kubectl config current-context</code>, put the context in your shell prompt, and switch deliberately with <code dir=\"ltr\">kubectl config use-context kind-notes</code>. Some teams give production a separate kubeconfig file entirely.</p>\n</aside>\n<p>Next: the objects you just glimpsed, written properly. Pods, ReplicaSets and Deployments.</p>\n","toc":[{"id":"sec-1","text":"The control loop","level":2},{"id":"sec-2","text":"The parts of a cluster","level":2},{"id":"sec-3","text":"A cluster on your laptop","level":2},{"id":"sec-4","text":"Watch the loop work","level":2}],"takeaways":["You tell Kubernetes the state you want; controllers keep comparing it with the actual state and act to close the gap, forever.","The API server is the front door and the only component that talks to etcd, where all cluster state is stored.","The scheduler picks a node for each new Pod; the kubelet on that node starts its containers through the container runtime.","Deleting a Pod that a controller manages doesn’t remove it for long, because the controller sees one missing and creates a replacement.","Always check which cluster <code dir=\"ltr\">kubectl</code> is pointed at with <code dir=\"ltr\">kubectl config current-context</code> before changing anything."],"further":[{"title":"Kubernetes components","url":"https://kubernetes.io/docs/concepts/overview/components/"},{"title":"Controllers","url":"https://kubernetes.io/docs/concepts/architecture/controller/"},{"title":"Install and set up kubectl","url":"https://kubernetes.io/docs/tasks/tools/"}],"quiz":[{"q":"<p>A Deployment wants 3 replicas. A node crashes and takes one Pod with it. What restores the third Pod?</p>","options":[{"text":"A controller notices that 2 Pods exist where 3 are desired and creates a new one, which the scheduler places on a healthy node.","why":"Correct. The ReplicaSet controller keeps reconciling actual against desired, so it replaces the lost Pod without anyone running a command."},{"text":"The kubelet on the crashed node restarts the Pod when the node comes back.","why":"If the node is gone, its kubelet is gone too. Recovery has to come from the control plane."},{"text":"Nothing; someone must re-run <code dir=\"ltr\">kubectl apply</code>.","why":"Desired state is already stored. Re-applying the same file changes nothing that controllers aren’t already enforcing."},{"text":"etcd restarts the Pod from its backup.","why":"etcd stores state. It never starts or stops anything."}],"answer":0},{"q":"<p>Which component is the only one that reads and writes etcd directly?</p>","options":[{"text":"The scheduler","why":"The scheduler watches and updates Pods through the API server, like every other component."},{"text":"The kubelet","why":"Kubelets talk to the API server to learn which Pods to run and to report status."},{"text":"kubectl","why":"kubectl is a client of the API server. It never sees etcd."},{"text":"The API server","why":"Correct. Everything else goes through it, which is where validation, authentication and authorisation happen."}],"answer":3},{"q":"<p>You delete a Pod created by a Deployment with <code dir=\"ltr\">kubectl delete pod notes-api-7d9c-xk2lp</code>. What do you see a few seconds later in <code dir=\"ltr\">kubectl get pods</code>?</p>","options":[{"text":"One fewer Pod, until you scale the Deployment again.","why":"The Deployment’s desired count didn’t change, so the shortfall is corrected straight away."},{"text":"A new Pod with a different name, created to replace it.","why":"Correct. The ReplicaSet controller sees one missing and creates a replacement. To remove Pods for good, change the Deployment."},{"text":"The same Pod, restored with the same name and IP.","why":"Pods are never restored. The replacement is a new Pod with a new name and, usually, a new IP."},{"text":"An error, because Deployment Pods can’t be deleted.","why":"You can delete them. The controller just won’t let the count stay low."}],"answer":1},{"q":"<p>Before running <code dir=\"ltr\">kubectl delete deployment notes-api</code>, what should you check?</p>","options":[{"text":"That the image still exists in the registry.","why":"Deleting a Deployment doesn’t depend on the image. Which cluster you’re talking to does matter."},{"text":"That etcd has a recent backup.","why":"Good practice for cluster operators, but the immediate risk here is acting on the wrong cluster."},{"text":"Which cluster and namespace kubectl is pointed at, with <code dir=\"ltr\">kubectl config current-context</code>.","why":"Correct. kubectl acts on whatever context is current. The same command means very different things in kind and in production."},{"text":"That the Deployment has fewer than 10 replicas.","why":"Replica count doesn’t change whether a delete is safe."}],"answer":2}],"exercise":{"kind":"order","items":["send","store","rs","pods","schedule","start","report"],"title":"From kubectl apply to a running container","prompt":"<p>You run <code dir=\"ltr\">kubectl apply -f deployment.yaml</code> for notes-api with 3 replicas on a fresh cluster. Put what happens next in order.</p>\n","hints":["Nothing happens anywhere until the object is stored.","A Deployment doesn’t create Pods itself; it manages something that does.","A Pod has to be assigned to a node before any kubelet will act on it."],"explanation":"<p>kubectl sends the object to the API server, which validates it and stores it in etcd. The Deployment controller sees a new Deployment and creates a ReplicaSet; the ReplicaSet controller creates three Pod objects. The scheduler assigns each unscheduled Pod to a node. The kubelet on each chosen node pulls the image and starts the containers, then reports the Pod’s status back through the API server. Each step is a separate component reacting to a change it watched for.</p>\n","checkLabels":{},"itemText":{"send":"kubectl sends the Deployment to the API server","store":"The API server validates it and stores it in etcd","rs":"The Deployment controller creates a ReplicaSet","pods":"The ReplicaSet controller creates three Pod objects","schedule":"The scheduler assigns each Pod to a node","start":"Each node’s kubelet pulls the image and starts the containers","report":"The kubelet reports Pod status back to the API server"}},"words":684,"readMinutes":3}