{"id":"l-dk-4-3","courseId":"docker-kubernetes","lang":"en","kind":"lesson","summary":"Give notes-api and its database stable names and addresses with Services, wire <code dir=\"ltr\">port</code>, <code dir=\"ltr\">targetPort</code> and <code dir=\"ltr\">containerPort</code> correctly, and choose between ClusterIP, NodePort and LoadBalancer.","html":"<p>Run <code dir=\"ltr\">kubectl get pods -o wide</code> twice, with a rollout in between, and every IP is different. Pods are replaced constantly: new versions, rescheduling, scaling. Anything that remembered a Pod’s IP is now pointing at nothing. A Service is the stable thing you point at instead.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">A Service for notes-api</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">k8s/service.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\">Service</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\">  selector</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\">  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\">      port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">80</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      targetPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span></code></pre></div>\n<p>This creates a virtual IP and a DNS name, <code dir=\"ltr\">notes-api</code>, that last as long as the Service exists. Connections to it are spread across the Pods its <code dir=\"ltr\">selector</code> matches, the same label query the Deployment uses. Kubernetes tracks the current set of matching <em>ready</em> Pods in <strong>EndpointSlices</strong> and updates them as Pods come and go. Clients never notice.</p>\n<p>A ClusterIP isn’t a machine and no process listens on it. Each node’s networking layer (kube-proxy, or an eBPF-based replacement in many clusters) programs rules that rewrite packets sent to the Service’s IP and port so they go to one of the ready Pods. That’s why you can’t <code dir=\"ltr\">ping</code> a Service IP and shouldn’t worry when it doesn’t answer: only the declared ports exist. It’s also why a Service costs almost nothing, so create one for every workload that receives traffic.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Three ports, three meanings</h2>\n<p>This is where most Service bugs live.</p>\n<ul>\n<li><strong><code dir=\"ltr\">containerPort</code></strong> in the Pod spec is the port your process listens on: 3000. It’s informational, like <code dir=\"ltr\">EXPOSE</code>, but naming it (<code dir=\"ltr\">name: http</code>) lets other objects refer to it.</li>\n<li><strong><code dir=\"ltr\">targetPort</code></strong> on the Service is where traffic is delivered on the Pod. It must match where the app actually listens. Here it uses the name <code dir=\"ltr\">http</code>, which resolves to 3000.</li>\n<li><strong><code dir=\"ltr\">port</code></strong> on the Service is what clients connect to: 80.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 250\" role=\"img\" aria-labelledby=\"l-dk-4-3-t1\"><defs><marker id=\"arrow-l-dk-4-3-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-3-t1\">A client Pod calls notes-api on port 80. The Service forwards to targetPort http, which is containerPort 3000, on one of three ready notes-api Pods selected by label.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"95\" width=\"130\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"121\" text-anchor=\"middle\">client Pod</text>\n  <text class=\"d-code\" x=\"85\" y=\"143\" text-anchor=\"middle\">notes-api:80</text>\n  <path class=\"d-arrow\" d=\"M150 125 L228 125\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <rect class=\"d-box-primary\" x=\"230\" y=\"80\" width=\"190\" height=\"90\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"325\" y=\"106\" text-anchor=\"middle\">Service notes-api</text>\n  <text class=\"d-code\" x=\"325\" y=\"130\" text-anchor=\"middle\">port: 80</text>\n  <text class=\"d-code\" x=\"325\" y=\"152\" text-anchor=\"middle\">targetPort: http</text>\n  <path class=\"d-arrow\" d=\"M420 110 L508 52\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 125 L508 125\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 140 L508 198\" marker-end=\"url(#arrow-l-dk-4-3-1)\"/>\n  <rect class=\"d-box-success\" x=\"510\" y=\"28\" width=\"170\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"595\" y=\"57\" text-anchor=\"middle\">pod :3000</text>\n  <rect class=\"d-box-success\" x=\"510\" y=\"101\" width=\"170\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"595\" y=\"130\" text-anchor=\"middle\">pod :3000</text>\n  <rect class=\"d-box-success\" x=\"510\" y=\"174\" width=\"170\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"595\" y=\"203\" text-anchor=\"middle\">pod :3000</text>\n  <text class=\"d-label-muted\" x=\"325\" y=\"200\" text-anchor=\"middle\">selector: app.kubernetes.io/name=notes-api</text>\n  <text class=\"d-label-muted\" x=\"595\" y=\"242\" text-anchor=\"middle\">containerPort http = 3000</text>\n</svg><figcaption>Clients use the Service port; the Service forwards to targetPort on a ready Pod</figcaption></figure>\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>targetPort pointing at the wrong port</span></p>\n<p><code dir=\"ltr\">port: 80, targetPort: 80</code> is the classic. It looks symmetrical and tidy, the Service has endpoints, and every request fails with <code dir=\"ltr\">connection refused</code>, because nothing in the Pod listens on 80. Point <code dir=\"ltr\">targetPort</code> at the container’s named port and the numbers can never drift apart.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Finding Services by name</h2>\n<p>Cluster DNS gives every Service a name of the form <code dir=\"ltr\">&lt;service&gt;.&lt;namespace&gt;.svc.cluster.local</code>. From a Pod in the same namespace, the short name <code dir=\"ltr\">notes-api</code> is enough; from another namespace, use <code dir=\"ltr\">notes-api.notes</code> or the full name.</p>\n<p>notes-api needs a database, so give it one the same way. For practice in kind, run Postgres as a single-replica Deployment with a ClusterIP Service:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">k8s/db.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-db</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\">1</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-db</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-db</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\">postgres</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\">postgres:17</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\">POSTGRES_USER</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\">notes</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\">POSTGRES_PASSWORD</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\">notes</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">          # moved to a Secret in the next lesson</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\">POSTGRES_DB</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\">notes</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\">postgres</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\">5432</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">---</span></span>\n<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\">Service</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-db</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\">  selector</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-db</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\">postgres</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5432</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      targetPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">postgres</span></span></code></pre></div>\n<p>notes-api’s <code dir=\"ltr\">DATABASE_URL</code> host is now <code dir=\"ltr\">notes-db</code>, exactly as <code dir=\"ltr\">db</code> was in Compose. The <code dir=\"ltr\">---</code> separates two objects in one file.</p>\n<aside class=\"callout callout-note\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">Note</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Databases in production</span></p>\n<p>This practice database keeps its data inside the Pod, so it’s lost whenever the Pod is replaced. In production, use a managed database such as Amazon RDS, or run Postgres with a StatefulSet and persistent volumes under an operator maintained by people who know Postgres well. Stateless apps are where Kubernetes is easy; stateful ones need deliberate design.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Service types</h2>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Type</th>\n<th>Reachable from</th>\n<th>Use it for</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">ClusterIP</code> (default)</td>\n<td>Inside the cluster</td>\n<td>Service-to-service traffic, like notes-api → notes-db</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">NodePort</code></td>\n<td>Every node’s IP on a port in 30000–32767</td>\n<td>Quick tests, or as a building block for external load balancers</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">LoadBalancer</code></td>\n<td>A cloud load balancer’s address</td>\n<td>Exposing one service publicly on a managed cluster</td>\n</tr>\n</tbody>\n</table></div><p>There’s also the <strong>headless</strong> Service, with <code dir=\"ltr\">clusterIP: None</code>. It gets no virtual IP; instead its DNS name returns the IPs of the individual Pods. Databases and other clustered systems use it when each member needs to be addressed directly. You won’t need it for notes-api.</p>\n<p><code dir=\"ltr\">LoadBalancer</code> builds on the other two: the cloud provider creates a load balancer that typically forwards to node ports, which forward to the Service (some controllers send traffic straight to Pod IPs instead). One per public service gets expensive and repetitive when you have many HTTP services; lesson 5.4 covers Gateway API, which puts many routes behind one entry point.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Reaching it from your laptop</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\">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/</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\"> svc</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\"> endpointslices</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -l</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> kubernetes.io/service-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\"> port-forward</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> svc/notes-api</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 8080:80</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">curl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> localhost:8080/readyz</span></span></code></pre></div>\n<p><code dir=\"ltr\">port-forward</code> tunnels a local port to the Service through the API server. It’s for debugging, not for real traffic. If the EndpointSlice lists no addresses, the Service matches no ready Pods: compare the selector with <code dir=\"ltr\">kubectl get pods --show-labels</code>, then check readiness.</p>\n<p>Two problems are now visible in the YAML: a password sits in plain text, and configuration is mixed into the Deployment. The next lesson moves both out.</p>\n","toc":[{"id":"sec-1","text":"A Service for notes-api","level":2},{"id":"sec-2","text":"Three ports, three meanings","level":2},{"id":"sec-3","text":"Finding Services by name","level":2},{"id":"sec-4","text":"Service types","level":2},{"id":"sec-5","text":"Reaching it from your laptop","level":2}],"takeaways":["A Service gives a changing set of Pods one stable virtual IP and DNS name, and spreads connections across the ready Pods its selector matches.","<code dir=\"ltr\">port</code> is what clients connect to on the Service; <code dir=\"ltr\">targetPort</code> is where traffic goes on the Pod and must match the container’s listening port.","Inside the cluster, Pods reach a Service by name, such as <code dir=\"ltr\">notes-db</code> in the same namespace or <code dir=\"ltr\">notes-db.notes.svc.cluster.local</code> from anywhere.","Use ClusterIP for internal traffic, LoadBalancer to expose one service through a cloud load balancer, and NodePort mainly for testing.","An empty EndpointSlice means the Service matches no ready Pods; check the selector and readiness first."],"further":[{"title":"Service","url":"https://kubernetes.io/docs/concepts/services-networking/service/"},{"title":"DNS for Services and Pods","url":"https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/"},{"title":"Use Port Forwarding to Access Applications in a Cluster","url":"https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/"}],"quiz":[{"q":"<p>notes-api listens on 3000. The Service has <code dir=\"ltr\">port: 80</code> and <code dir=\"ltr\">targetPort: 3000</code>. Which URL do other Pods in the same namespace use?</p>","options":[{"text":"<code dir=\"ltr\">http://notes-api:3000</code>","why":"3000 is the Pod’s port. Clients of the Service connect to the Service’s <code dir=\"ltr\">port</code>, which is 80."},{"text":"<code dir=\"ltr\">http://&lt;pod-ip&gt;:80</code>","why":"Pod IPs change on every replacement, and nothing in the Pod listens on 80."},{"text":"<code dir=\"ltr\">http://notes-api</code> (port 80)","why":"Correct. The Service name resolves through cluster DNS, and port 80 is forwarded to 3000 on a ready Pod."},{"text":"<code dir=\"ltr\">http://localhost:80</code>","why":"<code dir=\"ltr\">localhost</code> is the calling Pod itself, not the Service."}],"answer":2},{"q":"<p><code dir=\"ltr\">kubectl get endpointslices -l kubernetes.io/service-name=notes-api</code> shows no endpoints, but three notes-api Pods are Running. What do you check first?</p>","options":[{"text":"Whether the Service’s selector matches the Pods’ labels, and whether the Pods are Ready.","why":"Correct. Endpoints list Pods that match the selector and pass readiness. A label typo or failing readiness probe leaves it empty."},{"text":"Whether the Service type is LoadBalancer.","why":"Type changes how traffic reaches the Service from outside, not which Pods back it."},{"text":"Whether the image tag is <code dir=\"ltr\">latest</code>.","why":"Image tags don’t affect Service matching."},{"text":"Whether the cluster DNS is running.","why":"DNS resolves the Service name, but endpoints are computed from labels and readiness, independently of DNS."}],"answer":0},{"q":"<p>You need notes-api reachable from the internet on a managed cluster in AWS, and it’s the only public service. Which Service type fits?</p>","options":[{"text":"ClusterIP","why":"ClusterIP is only reachable inside the cluster."},{"text":"NodePort, with users connecting to a node IP on port 30080","why":"That exposes every node on a high port and breaks when nodes are replaced. It’s a building block, not a public endpoint."},{"text":"Headless (<code dir=\"ltr\">clusterIP: None</code>)","why":"Headless Services give DNS records for each Pod, for things like databases with stable identities. They don’t expose anything externally."},{"text":"LoadBalancer, which provisions a cloud load balancer in front of the Service","why":"Correct. For one public service it’s the simplest option. With many HTTP services you’d share one entry point through Gateway API instead."}],"answer":3},{"q":"<p>Which <code dir=\"ltr\">targetPort</code> is correct for a container that declares <code dir=\"ltr\">ports: [{ name: http, containerPort: 3000 }]</code>?</p>","options":[{"text":"<code dir=\"ltr\">targetPort: 80</code>, to match the Service’s port.","why":"<code dir=\"ltr\">targetPort</code> is where traffic lands in the Pod. Nothing listens on 80 there."},{"text":"<code dir=\"ltr\">targetPort: http</code>, the container port’s name (or <code dir=\"ltr\">3000</code>).","why":"Correct. A named port keeps working even if you later change the number in one place."},{"text":"<code dir=\"ltr\">targetPort: notes-api</code>, the Deployment’s name.","why":"<code dir=\"ltr\">targetPort</code> takes a number or the name of a container port, not an object name."},{"text":"<code dir=\"ltr\">targetPort: 30000</code>, from the NodePort range.","why":"The NodePort range applies to <code dir=\"ltr\">nodePort</code>, a different field on the node side."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"yaml","code":"apiVersion: v1\nkind: Service\nmetadata:\n  name: notes-api\nspec:\n  selector:\n    app.kubernetes.io/name: notes-api\n  ports:\n    - port: 80\n      targetPort: 80\n---\n# excerpt from the Deployment's Pod template\ncontainers:\n  - name: api\n    image: ghcr.io/skylane/notes-api:1.4.0\n    ports:\n      - containerPort: 3000","bugLines":[10],"title":"Endpoints exist, requests fail","prompt":"<p>The Service below has three endpoints, the Pods are Running and Ready, yet <code dir=\"ltr\">curl notes-api</code> from another Pod gets <code dir=\"ltr\">connection refused</code> every time. Select the line that causes it.</p>\n","hints":["The Service found the Pods, so the selector is fine. Follow the port numbers.","Where does the app inside the Pod actually listen?"],"explanation":"<p>Line 10 sends traffic to port 80 on the Pods, but notes-api listens on 3000, so every connection is refused. Point <code dir=\"ltr\">targetPort</code> at the container’s port, ideally by name so the two can’t drift:</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:#6E7781;--shiki-dark:#8B949E\"># Service</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\">port</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">80</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      targetPort</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:#6E7781;--shiki-dark:#8B949E\"># Pod template</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></code></pre></div>\n","checkLabels":{}},"words":717,"readMinutes":3}