{"id":"l-dk-4-2","courseId":"docker-kubernetes","lang":"en","kind":"lesson","summary":"Write a Deployment manifest for notes-api, understand how it owns ReplicaSets and Pods through labels and selectors, and apply, inspect and scale it from files.","html":"<p>The imperative <code dir=\"ltr\">kubectl create deployment</code> from the last lesson was fine for a demo, and useless for a team. Nobody can review it, nobody knows what flags were used, and next week nobody can reproduce it. From here on, notes-api’s Kubernetes setup lives in YAML files under <code dir=\"ltr\">k8s/</code>, next to the Dockerfile, reviewed like code.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Pods: the unit Kubernetes runs</h2>\n<p>A <strong>Pod</strong> is one or more containers that are scheduled together onto one node and share a network namespace (one IP, one <code dir=\"ltr\">localhost</code>) and any volumes you declare. Most Pods have one main container. A second container is for something tightly coupled to the first, like a log shipper or a proxy that must live and die with it.</p>\n<p>A minimal Pod manifest looks like this:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">YAML</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:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Pod</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api-test</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  containers</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ghcr.io/skylane/notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">containerPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span></span></code></pre></div>\n<p>You won’t create Pods like this in practice. A bare Pod isn’t replaced if it crashes on a dead node, can’t be scaled, and can’t be updated to a new image gradually. Pods are cattle: every one is disposable, gets a new name and IP when replaced, and is managed by something above it.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Deployments, ReplicaSets, Pods</h2>\n<p>A <strong>Deployment</strong> is that something. It owns a <strong>ReplicaSet</strong>, whose only job is to keep N identical Pods running. The Deployment’s job is to manage ReplicaSets over time: when you change the Pod template, it creates a new ReplicaSet and shifts Pods over, which is the rolling update you’ll study in section 5.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">k8s/deployment.yaml</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:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">apps/v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Deployment</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  labels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  replicas</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  selector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    matchLabels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  template</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      labels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    spec</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      containers</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ghcr.io/skylane/notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              containerPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          env</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">PORT</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"3000\"</span></span></code></pre></div>\n<p>Read it in two halves. The top half is the Deployment itself: its name, how many replicas, and which Pods it owns (<code dir=\"ltr\">selector</code>). Everything under <code dir=\"ltr\">template</code> is a Pod spec, the blueprint each replica is stamped from. Note that <code dir=\"ltr\">apiVersion</code> differs by kind: Pods are core <code dir=\"ltr\">v1</code>, Deployments are <code dir=\"ltr\">apps/v1</code>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 260\" role=\"img\" aria-labelledby=\"l-dk-4-2-t1\"><defs><marker id=\"arrow-l-dk-4-2-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-2-t1\">The notes-api Deployment owns a ReplicaSet named with a template hash. The ReplicaSet owns three Pods, each labelled app.kubernetes.io/name=notes-api, which the selector matches.</title>\n  <rect class=\"d-box-primary\" x=\"240\" y=\"16\" width=\"220\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"40\" text-anchor=\"middle\">Deployment</text>\n  <text class=\"d-code\" x=\"350\" y=\"60\" text-anchor=\"middle\">notes-api</text>\n  <path class=\"d-arrow\" d=\"M350 72 L350 98\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <rect class=\"d-box-accent\" x=\"220\" y=\"100\" width=\"260\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"124\" text-anchor=\"middle\">ReplicaSet</text>\n  <text class=\"d-code\" x=\"350\" y=\"144\" text-anchor=\"middle\">notes-api-6c8f9d7b5</text>\n  <path class=\"d-arrow\" d=\"M300 156 L140 188\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M350 156 L350 188\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M400 156 L560 188\" marker-end=\"url(#arrow-l-dk-4-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"40\" y=\"190\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"140\" y=\"220\" text-anchor=\"middle\">pod …-x2k4q</text>\n  <rect class=\"d-box-success\" x=\"250\" y=\"190\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"350\" y=\"220\" text-anchor=\"middle\">pod …-p8mzr</text>\n  <rect class=\"d-box-success\" x=\"460\" y=\"190\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"560\" y=\"220\" text-anchor=\"middle\">pod …-c4tq9</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"256\" text-anchor=\"middle\">each Pod: app.kubernetes.io/name=notes-api</text>\n</svg><figcaption>A Deployment owns ReplicaSets; a ReplicaSet owns Pods; labels tie them together</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">Labels and selectors</h2>\n<p>Kubernetes objects don’t point at each other by name. They find each other by <strong>labels</strong>, key-value pairs in <code dir=\"ltr\">metadata.labels</code>, and <strong>selectors</strong>, queries over those labels. The ReplicaSet counts its Pods by running its selector; next lesson, a Service will find Pods to send traffic to the same way.</p>\n<p>That’s why the Deployment’s <code dir=\"ltr\">selector.matchLabels</code> must match <code dir=\"ltr\">template.metadata.labels</code>: the Deployment must be able to find the Pods its own template creates. The API server checks this and rejects a mismatch. The selector is also <strong>immutable</strong> once the Deployment exists, so choose it carefully. The <code dir=\"ltr\">app.kubernetes.io/name</code> key is one of Kubernetes’ recommended labels; tools and dashboards understand it, and using it consistently pays off.</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>Copying an old manifest</span></p>\n<p>Search results still surface manifests with <code dir=\"ltr\">apiVersion: extensions/v1beta1</code> or <code dir=\"ltr\">apps/v1beta2</code> for Deployments. Those versions were removed years ago, and <code dir=\"ltr\">kubectl apply</code> fails with <code dir=\"ltr\">no matches for kind &quot;Deployment&quot; in version …</code>. Use <code dir=\"ltr\">apps/v1</code>, and check any field you’re unsure of with <code dir=\"ltr\">kubectl explain deployment.spec</code>.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Apply, inspect, scale</h2>\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\">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\"> ghcr.io/skylane/notes-api:1.4.0</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\"> apply</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/deployment.yaml</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\"> deploy,rs,pods</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -l</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> app.kubernetes.io/name=notes-api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> describe</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\">kind load</code> copies the image into the cluster under its full name, so the manifest can use the same reference production will. <code dir=\"ltr\">-l</code> filters by label, the same mechanism the controllers use. Look at the Pod names: <code dir=\"ltr\">notes-api-6c8f9d7b5-x2k4q</code> is the Deployment name, then the ReplicaSet’s <code dir=\"ltr\">pod-template-hash</code>, then a random suffix. Change anything in the template and you’ll get a new ReplicaSet with a new hash.</p>\n<p>To scale, change <code dir=\"ltr\">replicas: 3</code> to <code dir=\"ltr\">5</code> in the file and apply again. <code dir=\"ltr\">kubectl apply</code> compares your file with what’s stored and sends only the difference. <code dir=\"ltr\">kubectl diff -f k8s/</code> shows that difference before you commit to it, and you should make a habit of running it.</p>\n<p><code dir=\"ltr\">kubectl scale deployment/notes-api --replicas=5</code> also works, and has its place in an emergency. But the file still says 3, so the next routine <code dir=\"ltr\">apply</code> quietly scales you back down. Whatever you change by hand, change in Git straight after.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Debug the Pod, not the Deployment</span></p>\n<p>When something’s wrong, the Deployment only tells you counts. <code dir=\"ltr\">kubectl describe pod &lt;name&gt;</code> shows the events for that Pod: scheduling decisions, image pulls, container restarts and their reasons. It’s the first command to reach for, and section 5 builds a whole playbook around it.</p>\n</aside>\n<p>Your Pods are running, but each has its own IP that changes every time it’s replaced. Nothing can reliably reach them yet. That’s what Services are for.</p>\n","toc":[{"id":"sec-1","text":"Pods: the unit Kubernetes runs","level":2},{"id":"sec-2","text":"Deployments, ReplicaSets, Pods","level":2},{"id":"sec-3","text":"Labels and selectors","level":2},{"id":"sec-4","text":"Apply, inspect, scale","level":2}],"takeaways":["A Pod is one or more containers that share a network identity and storage; it’s the smallest thing Kubernetes schedules, and it’s disposable.","You almost never create bare Pods; a Deployment manages a ReplicaSet, which keeps the right number of Pods running.","A Deployment’s <code dir=\"ltr\">spec.selector.matchLabels</code> must match the labels in <code dir=\"ltr\">spec.template.metadata.labels</code>, and the selector can’t be changed after creation.","Keep manifests in Git and change them with <code dir=\"ltr\">kubectl apply -f</code>, so the files stay the source of truth.","Use current API versions such as <code dir=\"ltr\">apps/v1</code>; manifests copied from old posts with <code dir=\"ltr\">extensions/v1beta1</code> no longer work."],"further":[{"title":"Pods","url":"https://kubernetes.io/docs/concepts/workloads/pods/"},{"title":"Deployments","url":"https://kubernetes.io/docs/concepts/workloads/controllers/deployment/"},{"title":"Labels and Selectors","url":"https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/"},{"title":"Recommended Labels","url":"https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/"}],"quiz":[{"q":"<p>Why run notes-api through a Deployment instead of creating three Pods directly?</p>","options":[{"text":"Pods can’t be created from YAML files.","why":"They can. A bare Pod manifest is valid; it’s just not managed by anything."},{"text":"Pods created directly can’t use container images from a registry.","why":"Image pulling works the same for any Pod. Management is the difference."},{"text":"Deployments run containers faster than bare Pods.","why":"Startup speed is identical. The containers run the same way."},{"text":"A bare Pod isn’t replaced if it or its node dies, and it can’t be rolled out to a new version gradually; a Deployment handles both.","why":"Correct. The Deployment, through its ReplicaSet, keeps the count right and manages updates."}],"answer":3},{"q":"<p><code dir=\"ltr\">kubectl apply</code> rejects a Deployment with: <code dir=\"ltr\">selector does not match template labels</code>. What’s wrong?</p>","options":[{"text":"The container name doesn’t match the Deployment name.","why":"Container names are independent of the Deployment name and of labels."},{"text":"<code dir=\"ltr\">spec.selector.matchLabels</code> asks for labels that <code dir=\"ltr\">spec.template.metadata.labels</code> doesn’t set.","why":"Correct. The Deployment must be able to find the Pods its own template creates, so the API server validates that they match."},{"text":"The Deployment is in a different namespace from its Pods.","why":"A Deployment’s Pods are always created in its own namespace."},{"text":"The image tag isn’t a valid label value.","why":"The image isn’t a label at all; it doesn’t take part in selection."}],"answer":1},{"q":"<p>You run <code dir=\"ltr\">kubectl get pods</code> and see <code dir=\"ltr\">notes-api-6c8f9d7b5-x2k4q</code>. What is <code dir=\"ltr\">6c8f9d7b5</code>?</p>","options":[{"text":"The Git commit of the running image.","why":"Kubernetes knows nothing about Git. The value comes from the Pod template."},{"text":"The node the Pod runs on.","why":"Node names aren’t in Pod names. Use <code dir=\"ltr\">kubectl get pods -o wide</code> to see nodes."},{"text":"A hash of the Pod template, identifying the ReplicaSet that created this Pod.","why":"Correct. Each template version gets its own ReplicaSet, and its Pods carry its <code dir=\"ltr\">pod-template-hash</code>. You’ll see why this matters for rollbacks."},{"text":"A random suffix with no meaning.","why":"Only the last part (<code dir=\"ltr\">x2k4q</code>) is random. The middle part is deliberate."}],"answer":2},{"q":"<p>Someone ran <code dir=\"ltr\">kubectl scale deployment/notes-api --replicas=6</code> during an incident. What happens at the next <code dir=\"ltr\">kubectl apply -f k8s/</code> with <code dir=\"ltr\">replicas</code> set to 3 in the file?</p>","options":[{"text":"The Deployment goes back to 3 replicas, because the file sets 3.","why":"Correct. Apply makes the cluster match the file. Update the file when a change should stick, or the next deploy undoes it."},{"text":"It stays at 6, because imperative changes always win.","why":"Imperative changes are just API updates; the next apply overwrites the fields the file manages."},{"text":"Apply fails with a conflict until someone resolves it.","why":"Client-side apply simply sets the field. You get no warning, which is the danger."},{"text":"Kubernetes averages the two and runs 4 or 5.","why":"There’s no averaging; each field has one value at a time."}],"answer":0}],"exercise":{"kind":"spot-bug","language":"yaml","code":"apiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: notes-api\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: notes-api\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: notes\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io/skylane/notes-api:1.4.0\n          ports:\n            - containerPort: 3000","bugLines":[1,13],"title":"A Deployment the API server rejects","prompt":"<p>A teammate adapted this Deployment from a blog post. <code dir=\"ltr\">kubectl apply</code> fails, and after fixing the first error it fails again with a different one. Select the <strong>two</strong> lines to fix.</p>\n","hints":["Which API group and version do Deployments use in current Kubernetes?","Compare the selector’s labels with the labels the Pod template sets, character by character."],"explanation":"<p>Line 1 uses <code dir=\"ltr\">extensions/v1beta1</code>, which was removed long ago; apply fails with <code dir=\"ltr\">no matches for kind &quot;Deployment&quot;</code>. Use <code dir=\"ltr\">apps/v1</code>.</p>\n<p>Line 13 labels the Pods <code dir=\"ltr\">notes</code>, but the selector asks for <code dir=\"ltr\">notes-api</code>, so the API server rejects the Deployment because its selector doesn’t match its own template. Make them identical:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">YAML</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:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">apps/v1</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># …</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  selector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    matchLabels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  template</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      labels</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        app.kubernetes.io/name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api</span></span></code></pre></div>\n","checkLabels":{}},"words":699,"readMinutes":3}