How to Expose Services with LoadBalancer and NodePort
Learn different ways to expose Kubernetes services externally using LoadBalancer, NodePort, and ExternalIPs. Compare options for various environments.
The Problem
You need to expose your Kubernetes application to external traffic, but youβre unsure which service type to use.
Service Types Comparison
| Type | Use Case | External Access | Port Range |
|---|---|---|---|
| ClusterIP | Internal only | No | Any |
| NodePort | Development/Testing | Node IP:Port | 30000-32767 |
| LoadBalancer | Production (cloud) | External IP | Any |
| ExternalName | DNS alias | N/A | N/A |
ClusterIP (Default)
Internal access only:
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
type: ClusterIP # Default, can be omitted
ports:
- port: 80
targetPort: 8080
selector:
app: myappAccess from within cluster:
curl http://myapp.default.svc.cluster.localNodePort
Exposes service on each nodeβs IP at a static port:
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
spec:
type: NodePort
ports:
- port: 80 # Service port (internal)
targetPort: 8080 # Container port
nodePort: 30080 # External port (optional, auto-assigned if omitted)
selector:
app: myappAccess from external:
curl http://<node-ip>:30080Get node IPs:
kubectl get nodes -o wideNodePort Considerations
β Pros:
- Works without cloud provider
- Simple to set up
β Cons:
- Limited port range (30000-32767)
- Need to know node IPs
- No load balancing across nodes
LoadBalancer
Provisions an external load balancer (cloud providers):
apiVersion: v1
kind: Service
metadata:
name: myapp-lb
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: myappCheck external IP:
kubectl get svc myapp-lb
# NAME TYPE EXTERNAL-IP PORT(S)
# myapp-lb LoadBalancer 34.123.45.67 80:31234/TCPCloud-Specific Annotations
AWS:
metadata:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/aws-load-balancer-internal: "true"GCP:
metadata:
annotations:
cloud.google.com/load-balancer-type: InternalAzure:
metadata:
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"LoadBalancer on Bare Metal
Use MetalLB for LoadBalancer support on bare metal:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yamlConfigure IP pool:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.240-192.168.1.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-systemExternalName
Creates a DNS alias:
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: database.example.comNow pods can access external-db which resolves to database.example.com.
External IPs
Expose on specific external IPs:
apiVersion: v1
kind: Service
metadata:
name: myapp-external
spec:
ports:
- port: 80
targetPort: 8080
externalIPs:
- 192.168.1.100
selector:
app: myappMultiple Ports
apiVersion: v1
kind: Service
metadata:
name: myapp-multi
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
- name: grpc
port: 9090
targetPort: 9090
selector:
app: myappSession Affinity
Route same client to same pod:
apiVersion: v1
kind: Service
metadata:
name: myapp-sticky
spec:
type: LoadBalancer
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
ports:
- port: 80
targetPort: 8080
selector:
app: myappHealth Check Configuration
For LoadBalancer health checks:
apiVersion: v1
kind: Service
metadata:
name: myapp-lb
annotations:
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /health
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "30"
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: myappTroubleshooting
LoadBalancer Stuck in Pending
kubectl describe svc myapp-lbCommon causes:
- No cloud provider configured
- Quota exceeded
- Network misconfiguration
Service Not Reachable
# Check endpoints
kubectl get endpoints myapp
# Should show pod IPs
# NAME ENDPOINTS
# myapp 10.1.0.5:8080,10.1.0.6:8080If empty, check selector labels:
kubectl get pods --show-labelsTest from Inside Cluster
kubectl run test --rm -it --image=curlimages/curl -- curl http://myappDecision Flow
Need external access?
ββ No β ClusterIP
ββ Yes
ββ Cloud provider available?
β ββ Yes β LoadBalancer
β ββ No β NodePort or Ingress + MetalLB
ββ Development/Testing?
ββ Yes β NodePortKey Takeaways
- Use ClusterIP for internal services
- NodePort for quick testing (limited ports)
- LoadBalancer for production with cloud providers
- Consider Ingress for HTTP/HTTPS routing
- Use MetalLB for LoadBalancer on bare metal
π Go Further with Kubernetes Recipes
Love this recipe? Thereβs so much more! This is just one of 100+ hands-on recipes in our comprehensive Kubernetes Recipes book.
Inside the book, youβll master:
- β Production-ready deployment strategies
- β Advanced networking and security patterns
- β Observability, monitoring, and troubleshooting
- β Real-world best practices from industry experts
βThe practical, recipe-based approach made complex Kubernetes concepts finally click for me.β
π Get Your Copy Now β Start building production-grade Kubernetes skills today!
π Get All 100+ Recipes in One Book
Stop searching β get every production-ready pattern with detailed explanations, best practices, and copy-paste YAML.
Want More Kubernetes Recipes?
This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.