DEV Community

janak0ff
janak0ff

Posted on

Expose Application Using NodePort Service in Kubernetes

The Nautilus DevOps team has already deployed a ReplicaSet to host an application that requires a highly available infrastructure. Your task is to expose the application running in the existing ReplicaSet by creating a Kubernetes NodePort Service.

Follow the specifications below to create the Service and ensure the application pods are accessible:

  1. A ReplicaSet named httpd-replicaset is already running in the cluster.
  2. The pods managed by the ReplicaSet use the following labels: Assign labels app as httpd_app, and type as front-end.
  3. Create a NodePort Service named httpd-service to expose the application.
  4. Set the NodePort to 30080.
  5. Expose port 80 of the application. Note: Do not delete or modify the configuration of the deployed ReplicaSet application.

Introduction

You've deployed your application in Kubernetes, but how do users access it? This is one of the most common questions beginners face when working with Kubernetes. In this comprehensive guide, we'll walk through exposing an application using a NodePort Service—one of the simplest ways to make your application accessible from outside the cluster.

We'll use a real-world scenario from the Nautilus DevOps team: they have an application running in a ReplicaSet, and they need to expose it to users. By the end of this guide, you'll understand how to create and configure NodePort Services like a pro!


What You'll Learn

  • What NodePort Services are and when to use them
  • How to expose applications using NodePort
  • How services work with ReplicaSets and pods
  • How to verify your service is working correctly
  • Best practices for production environments

Table of Contents

  1. Understanding NodePort Services
  2. The Scenario: Exposing an Application
  3. Step 1: Verify the Existing Application
  4. Step 2: Create the NodePort Service
  5. Step 3: Verify the Service
  6. Step 4: Test Application Access
  7. Understanding How It Works
  8. Common Issues and Solutions
  9. Advanced Configuration
  10. Best Practices
  11. Conclusion

Understanding NodePort Services

What is a NodePort Service?

A NodePort Service is a type of Kubernetes Service that exposes your application on a static port on each node's IP address. Think of it as a door into your cluster:

┌─────────────────────────────────────────────────────────────┐
│                    Kubernetes Cluster                      │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Node 1 (10.244.164.51)                           │   │
│  │  ┌─────────────────────────────────────────────┐  │   │
│  │  │  NodePort: 30080                            │  │   │
│  │  │  ┌─────────────────────────────────────┐   │  │   │
│  │  │  │  Pod 1 (10.22.0.9:80)              │   │  │   │
│  │  │  └─────────────────────────────────────┘   │  │   │
│  │  └─────────────────────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Node 2 (10.244.164.52)                           │   │
│  │  ┌─────────────────────────────────────────────┐  │   │
│  │  │  NodePort: 30080                            │  │   │
│  │  │  ┌─────────────────────────────────────┐   │  │   │
│  │  │  │  Pod 2 (10.22.0.10:80)             │   │  │   │
│  │  │  └─────────────────────────────────────┘   │  │   │
│  │  └─────────────────────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  External Traffic: http://10.244.164.51:30080              │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Service Types Comparison

Type Description When to Use
ClusterIP Internal only, accessible within the cluster Internal services, microservices
NodePort External access on each node's IP Development, testing, simple production
LoadBalancer Cloud provider load balancer Production, public applications
ExternalName DNS alias to external services Integrating external services

NodePort Range

Valid NodePort Range: 30000 - 32767
Our Port: 30080 ✅ (Within valid range)
Enter fullscreen mode Exit fullscreen mode

The Scenario: Exposing an Application

The Challenge

The Nautilus DevOps team has already deployed an application using a ReplicaSet. Now they need to expose it to users. Here are the requirements:

Existing Resources:

  • ✅ ReplicaSet: httpd-replicaset
  • ✅ Pods: 3 running pods
  • ✅ Labels: app=httpd_app, type=front-end

Task:

  • Create a NodePort Service named httpd-service
  • Set NodePort to 30080
  • Expose port 80 of the application

Current State

┌─────────────────────────────────────────────────────────────┐
│                    Current State                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  ReplicaSet: httpd-replicaset                       │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │  Pod 1 (Running)                          │   │   │
│  │  │  Labels: app=httpd_app,type=front-end    │   │   │
│  │  │  Port: 80                                │   │   │
│  │  └─────────────────────────────────────────────┘   │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │  Pod 2 (Running)                          │   │   │
│  │  │  Labels: app=httpd_app,type=front-end    │   │   │
│  │  │  Port: 80                                │   │   │
│  │  └─────────────────────────────────────────────┘   │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │  Pod 3 (Running)                          │   │   │
│  │  │  Labels: app=httpd_app,type=front-end    │   │   │
│  │  │  Port: 80                                │   │   │
│  │  └─────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────┘   │
│  ❌ No Service → No external access                       │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Desired State

┌─────────────────────────────────────────────────────────────┐
│                    Desired State                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  ReplicaSet: httpd-replicaset                       │   │
│  │  3 Pods with labels: app=httpd_app,type=front-end  │   │
│  └────────────────────┬────────────────────────────────┘   │
│                       │                                     │
│                       ▼                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Service: httpd-service                            │   │
│  │  Type: NodePort                                    │   │
│  │  NodePort: 30080                                   │   │
│  │  Selector: app=httpd_app,type=front-end           │   │
│  └────────────────────┬────────────────────────────────┘   │
│                       │                                     │
│                       ▼                                     │
│  ✅ External Access: http://<node-ip>:30080               │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Step 1: Verify the Existing Application

Before creating the service, let's verify the existing resources.

Check the ReplicaSet

kubectl get replicasets
Enter fullscreen mode Exit fullscreen mode

Output:

NAME               DESIRED   CURRENT   READY   AGE
httpd-replicaset   3         3         3       108s
Enter fullscreen mode Exit fullscreen mode

What This Tells Us:

  • DESIRED: 3 - We want 3 pods
  • CURRENT: 3 - 3 pods are running
  • READY: 3 - All 3 pods are ready to serve traffic

Check the Pods and Their Labels

kubectl get pods --show-labels
Enter fullscreen mode Exit fullscreen mode

Output:

NAME                     READY   STATUS    RESTARTS   AGE    LABELS
httpd-replicaset-5jbg6   1/1     Running   0          108s   app=httpd_app,type=front-end
httpd-replicaset-5zttg   1/1     Running   0          108s   app=httpd_app,type=front-end
httpd-replicaset-jpn7g   1/1     Running   0          108s   app=httpd_app,type=front-end
Enter fullscreen mode Exit fullscreen mode

What This Tells Us:

  • All pods are running (READY: 1/1)
  • Labels are correctly set: app=httpd_app, type=front-end
  • These labels will be used to select the pods

Get ReplicaSet Details

kubectl describe replicaset httpd-replicaset
Enter fullscreen mode Exit fullscreen mode

Key Output:

Name:         httpd-replicaset
Selector:     app=httpd_app,type=front-end
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Containers:
  httpd-container:
    Image:         httpd:latest
    Port:          80/TCP
Enter fullscreen mode Exit fullscreen mode

What This Tells Us:

  • The ReplicaSet uses selector labels
  • The container runs HTTPD on port 80
  • All pods are healthy

Step 2: Create the NodePort Service

Now let's create the NodePort Service to expose the application.

Option A: Using YAML (Recommended)

Create a file or use inline YAML:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: httpd-service
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
    protocol: TCP
  selector:
    app: httpd_app
    type: front-end
EOF
Enter fullscreen mode Exit fullscreen mode

Expected Output:

service/httpd-service created
Enter fullscreen mode Exit fullscreen mode

Understanding the YAML:

apiVersion: v1                    # Kubernetes API version
kind: Service                      # Resource type
metadata:
  name: httpd-service              # Service name
spec:
  type: NodePort                   # Service type
  ports:
  - port: 80                       # Service port
    targetPort: 80                 # Pod port
    nodePort: 30080               # Node port (external)
    protocol: TCP                  # Protocol
  selector:                        # Pod selector
    app: httpd_app                 # Match label
    type: front-end                # Match label
Enter fullscreen mode Exit fullscreen mode

Option B: Using kubectl expose (Quick Command)

kubectl expose replicaset httpd-replicaset \
  --name=httpd-service \
  --type=NodePort \
  --port=80 \
  --target-port=80 \
  --node-port=30080
Enter fullscreen mode Exit fullscreen mode

Step 3: Verify the Service

Check Service Status

kubectl get services
Enter fullscreen mode Exit fullscreen mode

Output:

NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
httpd-service   NodePort    10.43.118.61   <none>        80:30080/TCP   11s
kubernetes      ClusterIP   10.43.0.1      <none>        443/TCP        26m
Enter fullscreen mode Exit fullscreen mode

What This Tells Us:

  • TYPE: NodePort - It's a NodePort service
  • PORT(S): 80:30080/TCP - Port 80 maps to NodePort 30080
  • AGE: 11s - Just created!

Get Service Details

kubectl describe service httpd-service
Enter fullscreen mode Exit fullscreen mode

Output:

Name:                     httpd-service
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=httpd_app,type=front-end
Type:                     NodePort
IP:                       10.43.118.61
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30080/TCP
Endpoints:                10.22.0.10:80,10.22.0.9:80,10.22.0.11:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
Enter fullscreen mode Exit fullscreen mode

What This Tells Us:

  • Selector: Matches our pod labels
  • Endpoints: 3 pods are available
  • NodePort: 30080 is exposed

Check Endpoints

kubectl get endpoints httpd-service
Enter fullscreen mode Exit fullscreen mode

Output:

NAME            ENDPOINTS                                  AGE
httpd-service   10.22.0.10:80,10.22.0.11:80,10.22.0.9:80   29s
Enter fullscreen mode Exit fullscreen mode

What This Tells Us:

  • The service has found 3 pods (endpoints)
  • All pods are on port 80
  • Traffic will be load-balanced between these pods

Step 4: Test Application Access

Get Node IP

NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
echo "Node IP: $NODE_IP"
Enter fullscreen mode Exit fullscreen mode

Output:

Node IP: 10.244.164.51
Enter fullscreen mode Exit fullscreen mode

Test with curl

curl http://$NODE_IP:30080
Enter fullscreen mode Exit fullscreen mode

Output (HTML from Apache HTTPD):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>It works! Apache httpd</title>
</head>
<body>
<p>It works!</p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Success! 🎉 The application is accessible!


Understanding How It Works

Service-to-Pod Communication Flow

┌─────────────────────────────────────────────────────────────┐
│                  External User                             │
│              http://10.244.164.51:30080                   │
└──────────────────────┬──────────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────────────┐
│                    Node (10.244.164.51)                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  NodePort: 30080                                   │   │
│  │  Receives request on port 30080                    │   │
│  └────────────────────┬────────────────────────────────┘   │
│                       │                                     │
│                       ▼                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Service: httpd-service                            │   │
│  │  1. Receives request on port 80                    │   │
│  │  2. Selects pods with matching labels              │   │
│  │  3. Load balances to available pods                │   │
│  └────────────────────┬────────────────────────────────┘   │
│                       │                                     │
│         ┌─────────────┼─────────────┐                      │
│         │             │             │                      │
│         ▼             ▼             ▼                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐               │
│  │  Pod 1   │  │  Pod 2   │  │  Pod 3   │               │
│  │  10.22.  │  │  10.22.  │  │  10.22.  │               │
│  │  0.9:80  │  │  0.10:80 │  │  0.11:80 │               │
│  └──────────┘  └──────────┘  └──────────┘               │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Service Selector Matching

Service Selector:

selector:
  app: httpd_app
  type: front-end
Enter fullscreen mode Exit fullscreen mode

Pod Labels:

labels:
  app: httpd_app     ✅ Matches!
  type: front-end    ✅ Matches!
Enter fullscreen mode Exit fullscreen mode

Result: The service finds all 3 pods!

Load Balancing

NodePort Services use round-robin load balancing:

Request 1 → Pod 1 (10.22.0.9:80)
Request 2 → Pod 2 (10.22.0.10:80)
Request 3 → Pod 3 (10.22.0.11:80)
Request 4 → Pod 1 (10.22.0.9:80)
...
Enter fullscreen mode Exit fullscreen mode

Common Issues and Solutions

Issue 1: No Endpoints

Symptom:

Endpoints: <none>
Enter fullscreen mode Exit fullscreen mode

Solutions:

# 1. Check pod labels
kubectl get pods --show-labels | grep httpd-replicaset

# 2. Check service selector
kubectl get service httpd-service -o jsonpath='{.spec.selector}'

# 3. Ensure labels match
# Service selector: app=httpd_app,type=front-end
# Pod labels must match exactly
Enter fullscreen mode Exit fullscreen mode

Issue 2: NodePort Already in Use

Error:

Error: node port 30080 is already in use
Enter fullscreen mode Exit fullscreen mode

Solution:

# 1. Check what's using the port
kubectl get services --all-namespaces | grep 30080

# 2. Use a different port
kubectl patch service httpd-service -p '{"spec":{"ports":[{"nodePort":30081,"port":80,"targetPort":80}]}}'
Enter fullscreen mode Exit fullscreen mode

Issue 3: Connection Refused

Symptom:

curl: (7) Failed to connect to 10.244.164.51 port 30080: Connection refused
Enter fullscreen mode Exit fullscreen mode

Solutions:

# 1. Check service status
kubectl get service httpd-service

# 2. Check endpoints
kubectl get endpoints httpd-service

# 3. Check node IP
kubectl get nodes -o wide

# 4. Check if pods are running
kubectl get pods
Enter fullscreen mode Exit fullscreen mode

Issue 4: Service Not Created

Error:

error: service httpd-service already exists
Enter fullscreen mode Exit fullscreen mode

Solution:

# 1. Check existing service
kubectl get service httpd-service

# 2. Delete and recreate
kubectl delete service httpd-service
kubectl create -f httpd-service.yaml
Enter fullscreen mode Exit fullscreen mode

Advanced Configuration

1. Multiple Ports

apiVersion: v1
kind: Service
metadata:
  name: httpd-service
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: 80
    nodePort: 30080
    protocol: TCP
  - name: https
    port: 443
    targetPort: 443
    nodePort: 30443
    protocol: TCP
  selector:
    app: httpd_app
    type: front-end
Enter fullscreen mode Exit fullscreen mode

2. Session Affinity

apiVersion: v1
kind: Service
metadata:
  name: httpd-service
spec:
  type: NodePort
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
  selector:
    app: httpd_app
    type: front-end
Enter fullscreen mode Exit fullscreen mode

3. Preserve Client IP

apiVersion: v1
kind: Service
metadata:
  name: httpd-service
spec:
  type: NodePort
  externalTrafficPolicy: Local
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
  selector:
    app: httpd_app
    type: front-end
Enter fullscreen mode Exit fullscreen mode

4. Service with Annotations

apiVersion: v1
kind: Service
metadata:
  name: httpd-service
  annotations:
    description: "HTTPD application service"
    owner: "devops-team"
    environment: "production"
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
  selector:
    app: httpd_app
    type: front-end
Enter fullscreen mode Exit fullscreen mode

Best Practices

✅ DO's

1. Use Specific Labels

selector:
  app: httpd_app    # ✅ Good
  type: front-end   # ✅ Good
Enter fullscreen mode Exit fullscreen mode

2. Use Annotations for Documentation

metadata:
  annotations:
    description: "HTTPD NodePort service for frontend"
    owner: "devops-team"
Enter fullscreen mode Exit fullscreen mode

3. Monitor Endpoints

kubectl get endpoints httpd-service -w
Enter fullscreen mode Exit fullscreen mode

4. Test Locally First

kubectl port-forward service/httpd-service 8080:80
Enter fullscreen mode Exit fullscreen mode

5. Use Namespaces

metadata:
  namespace: production
Enter fullscreen mode Exit fullscreen mode

6. Set Appropriate NodePort

nodePort: 30080  # Within valid range
Enter fullscreen mode Exit fullscreen mode

7. Document Service Purpose

metadata:
  labels:
    purpose: exposing-web-app
Enter fullscreen mode Exit fullscreen mode

❌ DON'Ts

1. Don't Use Invalid NodePorts

# BAD - Below valid range
nodePort: 80

# BAD - Above valid range
nodePort: 40000

# GOOD - Within valid range
nodePort: 30080
Enter fullscreen mode Exit fullscreen mode

2. Don't Use Conflicting Selectors

# BAD - No pods match
selector:
  app: wrong_app

# GOOD - Correct labels
selector:
  app: httpd_app
Enter fullscreen mode Exit fullscreen mode

3. Don't Forget TargetPort

# BAD - Missing targetPort
ports:
- port: 80

# GOOD - With targetPort
ports:
- port: 80
  targetPort: 80
Enter fullscreen mode Exit fullscreen mode

4. Don't Expose Internally Without Need

# Only expose externally if needed
# Use ClusterIP for internal services
Enter fullscreen mode Exit fullscreen mode

Quick Reference Commands

Service Commands

Command Description
kubectl get services List all services
kubectl get svc Short form
kubectl describe service NAME Service details
kubectl get endpoints NAME Check endpoints
kubectl get service NAME -o yaml Service YAML

Creation Commands

Command Description
kubectl expose replicaset NAME --type=NodePort --port=80 --node-port=30080 Expose ReplicaSet
kubectl apply -f service.yaml Create from YAML
kubectl create service nodeport NAME --tcp=80:80 --node-port=30080 Create service

Testing Commands

Command Description
kubectl port-forward service/NAME LOCAL:REMOTE Port forward
kubectl get nodes -o wide Get node IPs
curl http://NODE_IP:NODE_PORT Test access

Complete Verification Checklist

Use this checklist to verify your NodePort Service:

#!/bin/bash
echo "=== NodePort Service Verification ==="

echo -e "\n1. ReplicaSet Status:"
kubectl get replicaset httpd-replicaset

echo -e "\n2. Pod Status:"
kubectl get pods -l app=httpd_app,type=front-end

echo -e "\n3. Service Status:"
kubectl get service httpd-service

echo -e "\n4. Endpoints:"
kubectl get endpoints httpd-service

echo -e "\n5. Service Selector:"
kubectl get service httpd-service -o jsonpath='{.spec.selector}'

echo -e "\n6. NodePort Value:"
kubectl get service httpd-service -o jsonpath='{.spec.ports[0].nodePort}'

echo -e "\n7. Testing Access:"
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
NODE_PORT=$(kubectl get service httpd-service -o jsonpath='{.spec.ports[0].nodePort}')
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" http://$NODE_IP:$NODE_PORT

echo -e "\n8. Service Description:"
kubectl describe service httpd-service | grep -E "Name:|Type:|Selector:|NodePort:|Endpoints:"
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! You've successfully exposed an application using a Kubernetes NodePort Service. This is a fundamental skill that every DevOps engineer needs.

Key Takeaways

  1. NodePort Services expose applications on a static port on each node
  2. Selectors connect services to pods using labels
  3. Endpoints show which pods are being targeted
  4. Port mapping: nodePort → service port → targetPort → pod port
  5. Load balancing happens automatically
  6. Verification is crucial - always test your services

What You Learned

✅ What NodePort Services are and how they work
✅ How to expose a ReplicaSet using NodePort
✅ How to verify service creation
✅ How to test application accessibility
✅ Advanced configuration options
✅ Common issues and solutions
✅ Best practices for production

Final Architecture

┌─────────────────────────────────────────────────────────────┐
│                  ✅ Successfully Exposed!                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  ReplicaSet: httpd-replicaset                       │   │
│  │  ✅ 3 pods running                                 │   │
│  │  ✅ Labels: app=httpd_app,type=front-end          │   │
│  └─────────────────────────────────────────────────────┘   │
│                      │                                     │
│                      ▼                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Service: httpd-service                            │   │
│  │  ✅ Type: NodePort                                 │   │
│  │  ✅ NodePort: 30080                               │   │
│  │  ✅ Endpoints: 3 pods                             │   │
│  └─────────────────────────────────────────────────────┘   │
│                      │                                     │
│                      ▼                                     │
│  ✅ Access: http://10.244.164.51:30080                    │
│  ✅ It works! 🎉                                          │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Next Steps

Now that you've mastered NodePort Services, consider exploring:

  1. LoadBalancer Services - For cloud environments
  2. Ingress Controllers - For advanced routing
  3. Service Meshes - For service-to-service communication
  4. Network Policies - For security
  5. ExternalDNS - For automatic DNS records

Resources


Did this guide help you? Share your thoughts, questions, or experiences in the comments below! If you found this helpful, consider sharing it with your network.


Tags

Kubernetes #NodePort #Service #DevOps #ContainerOrchestration #CloudNative #K8s #CKA #KodeKloud #ExposeApplication


Top comments (0)