In a Kubernetes Service and pod spec, explain the difference between the Service's port, its targetPort, a nodePort, and the container's containerPort — and describe what a caller sees when targetPort is set to the wrong value.
answer
- port = Service · targetPort = pod · nodePort = every node · containerPort = documentation
- targetPort defaults to port — silent trap
- Named targetPort must exist in container ports, or no endpoints
- Refused = reached a pod, nothing listening; timeout = nothing reached
- 127.0.0.1 binding refuses just like a wrong port
basics
~20 sport is what the Service listens on at its cluster IP; targetPort is the pod port traffic is forwarded to; nodePort is an extra port opened on every node for NodePort/LoadBalancer Services; containerPort is documentation of what the container serves. A wrong targetPort gives endpoints but connection refused.
solid answer
~50 s- **`port`** — the port on the Service's cluster IP. Callers connect to `svc-name:port`. - **`targetPort`** — the port on the pod that traffic is forwarded to. It defaults to the same value as `port`, and may be a number or the *name* of a container port. - **`nodePort`** — only for `type: NodePort` or `LoadBalancer`: a port in the 30000–32767 range opened on every node, forwarded to the same backends. - **`containerPort`** — a declaration in the pod spec. It is informational: it does not open or restrict anything, and traffic reaches an undeclared listening port fine. Its practical uses are documentation and giving the port a **name** that `targetPort` can reference. Symptoms discriminate the failure. Wrong `targetPort` (a number nothing listens on) still produces endpoints, so the connection is DNAT'd to the pod and rejected — **connection refused**, fast. A wrong *named* targetPort yields **no endpoints** for that port. A wrong Service name gives a **DNS failure**. NetworkPolicy or a wrong pod IP gives a **timeout**.
code
yaml · 21 lines# Pod template
ports:
- name: http
containerPort: 8080
- name: metrics
containerPort: 9090
---
apiVersion: v1
kind: Service
metadata: {name: payments}
spec:
type: NodePort
selector: {app: payments}
ports:
- name: http
port: 80 # clients call payments:80
targetPort: http # resolves to containerPort 8080
nodePort: 30080 # every node also listens on 30080
- name: metrics
port: 9090
targetPort: 9090go deeper
Be able to state what each of the four fields means and that targetPort defaults to port.
Add named ports and their empty-endpoint failure mode, the nodePort range, and the refused-versus-timeout distinction.
Demonstrate inside-out verification (app socket, pod IP, Service IP) and catch loopback bindings and protocol mismatches quickly.
Treat port naming as an interface convention — named ports in charts and probes, consistent conventions across services — so mappings are not re-derived per team.
## The three hops of a Service call When a client inside the cluster calls `http://payments:80`, three things happen: 1. DNS resolves `payments` to the Service's cluster IP. 2. kube-proxy's rules on the client's node match traffic to `clusterIP:port` and DNAT it to `podIP:targetPort`, choosing one ready endpoint. 3. The pod's network namespace receives the packet on `targetPort`, where the application must actually be listening. Each field belongs to one hop, and understanding that mapping is what makes the failure modes obvious. ## The fields **`spec.ports[].port`** — the Service's own port. This is what appears in the client's URL and what the cluster IP listens on virtually. It is arbitrary and often chosen for convention (80, 443) regardless of what the app uses. **`spec.ports[].targetPort`** — where to forward on the pod. If omitted it defaults to the value of `port`, which is a frequent source of silent misconfiguration: a Service with `port: 80` and no `targetPort` forwards to port 80 on the pod, and if the app listens on 8080 nothing works. `targetPort` may be an integer or a string naming a `containerPort`. **`spec.ports[].nodePort`** — present only for `NodePort` and `LoadBalancer` Services. Kubernetes allocates (or you specify) a port from the service node-port range, default 30000–32767, and every node listens on it, forwarding to the same endpoints. So a NodePort Service has three port numbers in play at once: the node port externally, the Service port internally, and the target port on the pod. **`containerPort`** in the pod spec — the field that surprises people. It does **not** publish, open, or firewall anything. Linux network namespaces do not require declaration: if your process listens on 9000, other pods can reach `podIP:9000` whether or not `containerPort: 9000` appears in the YAML. What it is genuinely good for is (a) documenting the contract for humans and tools, (b) naming the port so `targetPort: http` can refer to it, and (c) `hostPort`, which really does bind on the node. Conversely, declaring `containerPort: 8080` when the app listens on 8081 creates a confident-looking manifest that does not work. **`protocol`** defaults to TCP. A UDP service (DNS, syslog, QUIC) needs it set explicitly, and a Service exposing both must define two port entries with distinct names. ## When names are used Named ports add a layer of indirection that is useful for multi-arch manifests and Helm charts: ```yaml # pod ports: - name: http containerPort: 8080 # service ports: - port: 80 targetPort: http ``` The rule to remember: a **named** `targetPort` must resolve for each pod. If the name is missing or misspelled in the container spec, the endpoint controller cannot produce an address for that port and you get an **empty endpoint list** — a different symptom from a wrong number. ## Reading the symptoms This is the practically valuable part: - **DNS failure** (`nslookup` fails, name not resolved) — wrong Service name, wrong namespace in the name, or a cluster DNS problem. Nothing to do with ports. - **Connection refused, immediately** — the connection reached a pod and nothing was listening on that port. Almost always a wrong numeric `targetPort`, or an app that binds `127.0.0.1` instead of `0.0.0.0` so it refuses connections arriving from outside the loopback interface. - **Timeout / hang** — the packet went nowhere or was dropped silently: no ready endpoints (so nothing to DNAT to), a NetworkPolicy dropping it, or a node-level network fault. - **Works from inside the pod but not through the Service** — either the port mapping is wrong, or the app is loopback-bound. ## Verifying quickly Work inside-out. First `kubectl exec` into the pod and curl `localhost:<app port>` to confirm the app serves at all. Then curl the **pod IP** on the target port from another pod — that skips the Service and proves reachability and the correct port. Then curl the Service's `clusterIP:port`. Whichever step first fails localises the problem exactly, and `kubectl get svc -o wide` plus `kubectl describe svc` shows the mapping and endpoints in one place. ## A note on 0.0.0.0 The loopback-binding mistake deserves emphasis because it produces the same 'connection refused' as a wrong port and is invisible from inside the pod: `curl localhost:8080` succeeds, so the developer concludes the app is fine. Any process meant to be reachable from a Service must listen on all interfaces (`0.0.0.0`, or `::` for dual-stack), not on `localhost`. Confirm with `ss -ltnp` or `netstat -ltnp` inside the container.
- If containerPort does not actually open the port, why declare it at all?Three reasons. It documents the contract so readers and tooling know what the container serves. It gives the port a name, which a Service's targetPort and a probe can reference, decoupling the Service from the number. And it is required for hostPort, which does genuinely bind on the node. Beyond that, network namespaces expose whatever the process listens on, so omitting containerPort does not block traffic and declaring the wrong number does not create it.
- A caller gets 'connection refused' immediately when hitting a Service, but curl to localhost inside the pod works. What are the two likely causes?Either the Service's targetPort points at a port nothing is listening on, or the application is bound to 127.0.0.1 rather than 0.0.0.0, so it accepts connections only from inside its own network namespace. Both produce an immediate refusal rather than a timeout, because the packet did reach the pod and the kernel sent a TCP reset. Check the actual listening sockets with ss -ltnp inside the container and compare with the Service's targetPort.
port is the street number on the building, targetPort is the flat number inside, nodePort is a side entrance on every block, and containerPort is the name plate by the door — helpful, but the postman gets in regardless of whether it is there.
saying these in an interview costs you the question
- Believing containerPort opens or firewalls a port rather than documenting it
- Forgetting that targetPort defaults to port when omitted
- Using a named targetPort that does not exist in the container spec and expecting traffic to flow
- Treating 'connection refused' and 'timeout' as interchangeable symptoms
- Testing only from inside the pod with localhost, which hides a 127.0.0.1 binding