A Kubernetes Service gives Pods a stable virtual IP and DNS name. NodePort is the simplest service type that exposes a Pod outside the cluster — fine for labs, edge devices, and on-prem clusters without a cloud LoadBalancer.
Prerequisites
- A running cluster —
kubectl get nodesreturns at least one Ready node. - A Deployment or Pod to expose. See Deploy your first pod if you need one.
Step 1: Three service types — pick wisely
| Type | What it does | When |
| --- | --- | --- |
| ClusterIP | Internal only — default | Pod-to-Pod communication |
| NodePort | Opens a high port (30000-32767) on every node | Bare-metal external access without LB |
| LoadBalancer | Cloud LB sits in front; falls back to NodePort | Public cloud, or k3s with klipper-lb |
This guide uses NodePort.
Step 2: Create a Deployment first
kubectl create deployment hello \
--image=nginxdemos/hello:latest \
--replicas=2
kubectl get deploy hello
kubectl get pods -l app=hello -o wide
Step 3: Expose with NodePort — imperative
kubectl expose deployment hello \
--port=80 \
--target-port=80 \
--type=NodePort
kubectl get svc hello
Output looks like:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello NodePort 10.43.123.45 <none> 80:31472/TCP 5s
31472 is the node port. Now any cluster node serves it:
curl http://node1.example.sa:31472/
curl http://node2.example.sa:31472/
Step 4: Declarative — write the YAML
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
type: NodePort
selector:
app: hello
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30080 # optional: pin instead of letting K8s pick
kubectl apply -f service.yaml
Fixing nodePort is useful when external firewall rules must allow a specific port. Stay in 30000–32767 unless the cluster admin widened the range.
Step 5: Test the load distribution
for i in $(seq 1 20); do
curl -s http://node1.example.sa:30080/ | grep "Server name"
done | sort | uniq -c
You should see hits split across both replica pods.
Step 6: Common debugging
kubectl describe svc hello # Selector matched? Endpoints set?
kubectl get endpoints hello # the actual Pod IPs behind the Service
kubectl logs -l app=hello --tail=20 -f
kubectl get pods -l app=hello -o wide # are pods even Ready?
If kubectl get endpoints hello shows <none>, the selector does not match any Pod labels — the most common newcomer mistake.
Step 7: Move to a real Ingress
NodePort is fine for two pods and a curl test. For anything client-facing in production, put an Ingress controller in front (Traefik ships with k3s; nginx-ingress is the most common alternative) and route hostnames to Services. NodePort + a reverse proxy (Nginx/Caddy) on a dedicated edge box works too.
Verify
kubectl get svc -o wide
kubectl get endpoints
ss -tlnp | grep 30080 # NodePort listening on the node
curl -fI http://node.example.sa:30080/
Conclusion
NodePort is the simplest, most portable way to get traffic into a Pod. Learn it, use it for labs and small deployments, and graduate to LoadBalancer/Ingress when traffic justifies the operational lift.
Next steps
- Run the workload first — Deploy your first pod.
- Set up the cluster — k3s lightweight Kubernetes.
- For raw container deploys see Docker + Compose on Ubuntu.
Comments
0 total · 0 threads