You wil be assigned a random port between 30000 and 32767. An example output could be:
On a different node, or even a node from a different experiment, runs:
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
In a real apps, multiple services are talking to each other.
Quote of the Day AppArchitecture
API Service: returns some random quotes
Time Service: return current server time
Frontend Service: aggregate both responses and presents a combined message to the user.
API Service: quote
Create and deploy a deployment manifest called quote-deployment.yaml
apiVersion:apps/v1kind:Deploymentmetadata:name:quotespec:replicas:2selector:matchLabels:app:quotetemplate:metadata:labels:app:quotespec:containers:-name:quoteimage:ealen/echo-serverenv:-name:QUOTESvalue:|"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:v1kind:Servicemetadata:name:quote-svcspec:selector:app:quoteports:-port:80targetPort:80
Time Service
Create and deploy a deployment manifest called time-deployment.yaml
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
Network Address Translation (NAT) is a technique where a network device (typically a router or firewall) rewrites the source or destination IP address of packets as they pass through.
Why it exists:
IPv4 has a limited address space: NAT lets many internal devices share a single public IP.
Provides a layer of isolation/security by hiding internal addresses.
Types of NAT:
SNAT (Source NAT): Rewrites the source IP of outbound traffic (e.g., your laptop 192.168.1.10 to public IP 203.0.113.5).
DNAT (Destination NAT): Rewrites the destination IP of inbound traffic (e.g., packets to 203.0.113.5 to 192.168.1.10).
PAT (Port Address Translation, aka masquerading): Multiple devices share one public IP by mapping connections to different ports.
In Kubernetes context:
Kube-proxy may use NAT (via iptables/ipvs) to redirect Service ClusterIP to Pod IP.
The Kubernetes networking model tries to minimize NAT inside the cluster: every Pod gets a unique, routable IP so pods can talk directly, no hidden rewrites. NAT is mostly used only at the cluster boundary (e.g., NodePort, LoadBalancer, egress to the Internet).
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
NAT: Network Address Translation
Per K8s design, Kubernetes assumes a flat, non-NATted network between all entities:
All pods can communicate with all other pods without NAT.
All nodes can communicate with all pods without NAT.
Pod IPs are the same inside and outside the pod. (No masquerading from the pod’s perspective).
Services (ClusterIP, NodePort, LoadBalancer) are implemented via virtual IPs and iptables/ipvs rules that redirect traffic to backing pods.
This model makes things simple at the app level: each pod just gets an IP and DNS name, no special networking code.
How Services Work Under the Hood
ClusterIP: The kube-proxy component sets up iptables or ipvs rules to redirect traffic from the service’s virtual IP to one of the pod IPs behind it.
NodePort: Kube-proxy additionally opens a port (30000–32767) on each node, then DNATs traffic to the service ClusterIP.
Container Networking Interface (CNI)
Kubernetes itself does not implement networking.
It relies on CNI plugins to configure pod networking.
CNI is a specification: containers call CNI when they’re created, and CNI sets up network interfaces, IP assignment, and routing.
kubelet launches a pod and calls the configured CNI plugin.
The plugin sets up a veth pair (virtual Ethernet) to connect the pod’s network namespace to the host.
IP address is assigned (via IPAM plugin or cluster-wide allocator).
Routes and bridges are created to connect pod-to-pod and pod-to-node traffic.