Sign in
← Kubernetes: networking
6 min · easy · Lesson 2 of 8

NodePort: a port on every node

The most straightforward way to let traffic into the cluster from outside, and its cost.

When ClusterIP isn't enough

ClusterIP gives a Service an internal address inside the cluster. Pods and other services see it, but it's unreachable from outside the cluster. Admin tasks or a quick check of the app need external access. NodePort solves this in the most direct way: it opens a port on every node.

What NodePort is

When a Service is created with type: NodePort, Kubernetes does three things:

  1. Creates a ClusterIP, like a regular Service: an internal address.
  2. Opens an extra port on every node in the cluster in the range 30000-32767. kube-proxy (the component on every node that sets up the network rules for Services) routes this port to the same Pods that sit behind the ClusterIP.
  3. Any request to <any-node>:<NodePort> lands on one of the Service's Pods.

Minimal manifest

apiVersion: v1
kind: Service
metadata:
  name: nodeapp
spec:
  type: NodePort
  selector:
    app: nodeapp
  ports:
    - port: 80          # internal ClusterIP port
      targetPort: 8080  # container port
      nodePort: 30090   # external port on every node

Fields

After creation, kubectl get svc shows both ports separated by a colon: first port, then nodePort.

kubectl get svc nodeapp
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nodeapp   NodePort   10.96.113.229   <none>        80:30090/TCP   31s

Port range

By default 30000-32767. It's a compromise: high enough to not conflict with system ports, and wide enough for a single cluster to handle hundreds of services.

If you try to set nodePort: 80, the API server responds:

The Service "nodeapp" is invalid: spec.ports[0].nodePort: Invalid value: 80: provided port is not in the valid range. The range of valid ports is 30000-32767

The range is changed with the --service-node-port-range flag on kube-apiserver. That's a control plane setting for the whole cluster, nobody touches it for a single Service.

How to check

First, check that the Service was created correctly and found the Pods:

kubectl get svc nodeapp
# PORT(S) column: 80:30090/TCP

kubectl get endpoints nodeapp
# a non-empty list of IPs = the Service really points at Pods

The v1 Endpoints object is deprecated since 1.33, and kubectl prints a warning:

Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice

The same list of addresses through the current object:

kubectl get endpointslice -l kubernetes.io/service-name=nodeapp

You can hit the NodePort with a request from inside the cluster: a temporary Pod sends a request to node-IP:30090.

NODE_IP=$(kubectl get nodes \
  -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')

kubectl run np-check --rm -it --restart=Never \
  --image=busybox:1.36.1 -- wget -qO- http://$NODE_IP:30090
# should get the nginx welcome page

NodePort in kind

In kind, a cluster node lives inside a Docker container. From inside the cluster, a request to node-IP:<nodePort> gets through. But from a laptop on macOS and Windows, the node IP isn't visible: Docker Desktop keeps containers inside its own virtual machine. That's why the node port is mapped to the laptop ahead of time in the cluster config, through extraPortMappings:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30090
        hostPort: 30090
        listenAddress: "127.0.0.1"

Now http://127.0.0.1:30090 on the laptop leads to port 30090 on the node, and from there to the NodePort Service. The mapping is only set when the cluster is created. Details in the kind documentation, Extra Port Mappings.

What matters here

One port for the whole cluster

If two different Services ask for nodePort: 30090, the second one won't get created:

spec.ports[0].nodePort: Invalid value: 30090: provided port is already allocated

NodePort is unique within the cluster, even if the Services live in different namespaces.

Every node listens on this port

node-1 · :30090 nodeapp node-2 · :30090 no Pod of this Service node-3 · :30090 nodeapp kube-proxy forwards to the Pod on node-1
The port is open on every node, even where there's no Pod of this Service.

Even on the ones with no Pod of this Service. kube-proxy still routes the traffic to a Pod on another node. This simplifies external load balancers: any node address works.

externalTrafficPolicy: Cluster vs Local

The default is Cluster: a request to node A can land on a Pod on node B. This balances the load, but loses the client IP: the Pod sees the node's address.

Local: traffic only goes to a Pod on the same node the request arrived at. The client IP is preserved. But if there's no Pod of this Service on the node, kube-proxy doesn't forward the request anywhere, and the client won't get a response.

ValueWhere the request landsClient IPIf there's no Pod on the node
Cluster (default)any Pod of the Service, even on another nodelostthe request still gets through
Localonly a Pod on this same nodepreservedthe request doesn't reach a Pod
spec:
  type: NodePort
  externalTrafficPolicy: Local

When to use NodePort

When not to use it

NodePort opens a port on all nodes in the cluster, including the ones with no Pod of this Service. Without a firewall in front of the nodes, this port is visible from the internet. In production, nodes are hidden behind a load balancer or restricted with network rules.

The quick path without YAML

kubectl create deployment nodeapp --image=nginxinc/nginx-unprivileged:1.25
kubectl expose deployment nodeapp --port=80 --target-port=8080 --type=NodePort

# which port the cluster handed out
kubectl get svc nodeapp -o jsonpath='{.spec.ports[0].nodePort}'

kubectl expose without an explicit nodePort takes a random port from the range. For a predictable address, add the port to the manifest by hand.

Common mistakes

What you'll need for the task

In short

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 1 lab for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-network-02-service-nodeport