ClusterIP, NodePort, and Multi-Service Communication


Motivation


Services Overview

flowchart TB
    subgraph Cluster["Kubernetes Cluster"]
        subgraph Pods["Pods"]
            B1["Backend Pod 1"]
            B2["Backend Pod 2"]
        end
        subgraph ClusterIPService["Service: ClusterIP"]
            C1["Virtual IP\n(10.x.x.x)"]
        end
        subgraph NodePortService["Service: NodePort"]
            N1["ClusterIP\n+ NodePort"]
        end
    end

    %% ClusterIP flow
    ClientIn["Internal Pod (curl)"] -->|"DNS: backend-svc"| C1 --> B1
    C1 --> B2

    %% NodePort flow
    ExternalClient["External Client\n(Browser or curl)"] -->|"http://NodeIP:Port"| N1 --> B1
    N1 --> B2

    %% Styling
    classDef cluster fill:#f0f8ff,stroke:#4682b4,stroke-width:2px;
    classDef service fill:#ffe4b5,stroke:#d2691e,stroke-width:2px;
    class Cluster,Pods cluster
    class ClusterIPService,NodePortService service


ClusterIP Services

Concept
ClusterIP Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 2
selector:
  matchLabels:
    app: backend
template:
  metadata:
    labels:
        app: backend
  spec:
    containers:
    - name: backend
      image: nginxdemos/hello:plain-text
      ports:
      - containerPort: 80
1
kubectl apply -f echo-deployment.yaml
1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
  name: backend-svc
spec:
  selector:
    app: backend
  ports:
    - port: 80
    targetPort: 80
1
kubectl apply -f echo-deployment.yaml
1
2
3
4
5
kubectl run curl --image=alpine -it --rm
# inside pod:
apk update
apk add curl
curl backend-svc

NodePort Services

Concept
NodePort
1
kubectl expose deployment backend --name=backend-nodeport --type=NodePort --port=80 --target-port=80
1
kubectl get svc backend-nodeport
1
2
3
# NODE_IP should be the hostname/IP address of the CloudLab node you are on. 
# NODE_PORT is the value between 30000 and 32767 that Kubernetes give your backend-nodeport service
curl NODE_IP:NODE_PORT

Multi-Service Communication

Concept
Quote of the Day App
Architecture
API Service: quote
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1
kind: Deployment
metadata:
  name: quote
spec:
  replicas: 2
  selector:
    matchLabels:
      app: quote
  template:
    metadata:
      labels:
        app: quote
    spec:
      containers:
      - name: quote
        image: ealen/echo-server
        env:
        - name: QUOTES
          value: |
            "The journey of a thousand miles begins with a single step."
            "What you do today can improve all your tomorrows."
            "In the middle of difficulty lies opportunity."
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: quote-svc
spec:
  selector:
    app: quote
  ports:
  - port: 80
    targetPort: 80
Time Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: apps/v1
kind: Deployment
metadata:
  name: time
spec:
  replicas: 1
  selector:
    matchLabels:
      app: time
template:
    metadata:
      labels:
        app: time
    spec:
      containers:
      - name: time
        image: busybox
        command: ["sh","-c"]
        args:
          - |
            while true; do
            printf "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n$(date)\n" \
            | nc -l -p 8080 -s 0.0.0.0;
            done
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: time-svc
spec:
  selector:
    app: time
  ports:
  - port: 80
    targetPort: 8080
Frontend Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: busybox
        command: ["sh","-c"]
        args:
          - |
            while true; do
            Q=$(wget -qO- http://quote-svc);
            T=$(wget -qO- http://time-svc);
            printf "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nQuote: $Q\nTime: $T\n" \
            | nc -l -p 8080 -s 0.0.0.0 ;
            done
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-svc
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 8080

Test these services with the following:

1
2
kubectl get svc frontend-svc
curl http://<NODE-IP>:<NODEPORT>
flowchart TB
%% User
U["External User<br/>curl http://NodeIP:NodePort"]

%% NodePort Service
subgraph Node["frontend-svc (Service: NodePort)"]
    NP["NodePort: 31080<br/>(maps → targetPort 8080)"]
end

%% Frontend Pod
subgraph Frontend["Frontend Pod"]
    FE["containerPort: 8080<br/>(aggregator)"]
end

%% ClusterIP Services
subgraph Services["ClusterIP Services (internal only)"]
    QS["quote-svc<br/>targetPort 80"]
    TS["time-svc<br/>targetPort 80"]
end

%% Quote Pods
subgraph QuotePods["Quote Pods"]
    Q1["containerPort: 80"]
    Q2["containerPort: 80"]
end

%% Time Pod
subgraph TimePod["Time Pod"]
    T1["containerPort: 80"]
end

%% Connections
U --> NP --> FE
FE --> QS
FE --> TS
QS --> Q1
QS --> Q2
TS --> T1

%% Styling
classDef user fill:#f0f8ff,stroke:#4169e1,stroke-width:2px;
classDef service fill:#fffacd,stroke:#daa520,stroke-width:2px;
classDef pod fill:#f0fff0,stroke:#2e8b57,stroke-width:2px;
class U user
class Node,Services service
class Frontend,QuotePods,TimePod pod

Kubernetes Networking Theory

Review: NAT
flowchart LR
subgraph Private["Private Network (LAN)"]
    A["Pod / Host<br/>192.168.1.10"]
    B["Pod / Host<br/>192.168.1.11"]
end

subgraph Router["Router / NAT Device"]
    N1["Source NAT (SNAT)<br/>Change source 192.168.x.x → 203.0.113.5"]
    N2["Destination NAT (DNAT)<br/>Change destination 203.0.113.5 → 192.168.1.10"]
end

subgraph Internet["Public Internet"]
    S["Public Server<br/>198.51.100.20"]
end

%% Outbound
A -->|"Outbound packet<br/>Src=192.168.1.10, Dst=198.51.100.20"| N1
N1 -->|"Translated packet<br/>Src=203.0.113.5, Dst=198.51.100.20"| S

%% Inbound
S -->|"Inbound packet<br/>Dst=203.0.113.5"| N2
N2 -->|"Translated packet<br/>Dst=192.168.1.10"| A

%% Styling
classDef private fill:#f0fff0,stroke:#2e8b57,stroke-width:2px;
classDef nat fill:#fffacd,stroke:#daa520,stroke-width:2px;
classDef internet fill:#f0f8ff,stroke:#4169e1,stroke-width:2px;
class Private private
class Router nat
class Internet internet
The Four Networking Requirements
How Services Work Under the Hood
Container Networking Interface (CNI)
Popular CNI Implementations
Suggested Readings