Challenges of Accessing Apps in Pods
Deployment is a great way of deploying your application because it provides higher-level abstraction of resource to declaratively manage, scale and update stateless application. But with just Deployment in place, there comes the challenges of accessing application running in Pods because pods are ephemeral in nature and you can’t trust an individual Pod to be reliable, durable — Pods get killed, dies without consent and appears out of nowhere (ReplicaSet does that though) and suddenly the backend app you were accessing on one IP address is inaccessible. This slows down development, organizational access and makes user level access almost impossible — Service enters the game just here.
Understanding The Problem
Let’s move very slow so that the problem is more vivid. Assume there is active development going on and backend is accessible at port 80 — it is just NGINX index.html file with:
I am backend!which is deployed as:
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
labels:
app: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: backend:v1
ports:
- containerPort: 80Currently your application architecture looks like:
---
title: Your Application
---
flowchart
subgraph K8s Cluster
subgraph backend
P3["Pod1<br>(10.244.0.93)"]
P4["Pod2<br>(10.244.0.94)"]
end
end
Assuming your frontend is already inside the cluster, the team now requires access to backend for further development. The code looks like (noway near real app, just demo):
import socket
import time
import requests
BACKEND_HOST = ""
BACKEND_PORT = 80
LISTEN_HOST = "0.0.0.0"
LISTEN_PORT = 80
PREFIX = "Backend says: "
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((LISTEN_HOST, LISTEN_PORT))
server.listen()
print(f"Listening on {LISTEN_HOST}:{LISTEN_PORT}")
while True:
client_socket, address = server.accept()
print(f"Client connected: {address}")
try:
while True:
url = f"http://{BACKEND_HOST}:{BACKEND_PORT}"
try:
response = requests.get(url=url, timeout=5)
data = PREFIX + str(response)
data = data.encode()
client_socket.sendall(data)
except (BrokenPipeError, ConnectionResetError, OSError):
print(f"Client disconnected: {address}")
break
time.sleep(3)
finally:
client_socket.close()It need BACKEND_HOST to proceed. You as a cluster manager queries K8s API and provide one of the IP address of backend Pods:
kubectl get pods --selector='app=backend' \
-o custom-columns=NAME:.metadata.name,IP:.status.podIPNAME IP
backend-deployment-99c89984c-4rjwf 10.244.0.93
backend-deployment-99c89984c-xs4xc 10.244.0.94Now, frontend app can proceed as:
# ...
BACKEND_HOST = "10.244.0.93"
# ...You then deploy your frontend:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
labels:
app: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: frontend:v1
ports:
- containerPort: 80Now your application architecture is frontend-backend:
---
title: Your Application
---
flowchart
subgraph K8s Cluster
subgraph backend
direction LR
P3["Pod1<br>(10.244.0.93)"]
P4["Pod2<br>(10.244.0.94)"]
end
subgraph frontend
direction LR
P1["Pod3<br>(10.244.0.120)"]
P2["Pod4<br>(10.244.0.121)"]
end
frontend -- connects to --> backend
end
Problem1: Pods Are Ephemeral
Currently we can access frontend on either of 10.244.0.120 or 10.244.0.121 as such:
nc 10.244.0.121 80Backend says: I am backend!
Backend says: I am backend!
Backend says: I am backend!
^CBut if those Pods die and recreated, IP Address changes (often) too and suddenly you can’t access application you were using two minutes ago:
nc 10.244.0.121 80 -vnc: connect to 10.244.0.120 port 80 (tcp) failed: No route to host
---
title: Your Application
---
flowchart
subgraph K8s Cluster
subgraph backend
direction LR
P3["Pod1<br>(10.244.0.93)"]
P4["Pod2<br>(10.244.0.94)"]
end
subgraph frontend
direction LR
P1["Pod3<br>(10.244.0.120)"]
P2["Pod4<br>(10.244.0.122)<br>[CHANGED]"]
end
frontend -- connects to --> backend
end
style P2 fill:#ffff00,stroke:#8b0000,color:#000000,stroke-width:1px
The same ephemeral nature applies to backend. Remember when we specified BACKEND_HOST as:
# ...
BACKEND_HOST = "10.244.0.93"
# ...Now imagine BACKEND_HOST at 10.244.0.93 dies and recreated — frontend hangs forever searching for backend and you will never get response.
---
title: Your Application
---
flowchart
subgraph K8s Cluster
subgraph backend
direction LR
P3["Pod1<br>(10.244.0.95)<br>[CHANGED]"]
P4["Pod2<br>(10.244.0.94)"]
end
subgraph frontend
direction LR
P1["Pod3<br>(10.244.0.120)"]
P2["Pod4<br>(10.244.0.122)"]
end
frontend -- connects to --> backend
end
style P3 fill:#ffff00,stroke:#8b0000,color:#000000,stroke-width:1px
To simulate this, I tried connecting frontend on the new IP address:
nc 10.244.0.122 80 -vConnection to 10.244.0.122 80 port [tcp/http] succeeded!
^CYou were able to connect to frontend on it’s new IP 10.244.0.122 but frontend isn’t able to find backend at 10.244.0.93.
The naive solution would be to connect frontend with new IP Address and also modify backend8 application with different IP Address anytime it changes but WHO WILL WATCH FOR THE CHANGES?
Problem2: No Load Balancing
Even if you update backend with new IP Address — What’s the point of having two replicas when all you are using is one? The same applies for frontend.
---
title: Your Application
---
flowchart
subgraph K8s Cluster
subgraph backend
direction LR
P3["Pod1<br>(10.244.0.95)<br>[OVERLOAD]"]
P4["Pod2<br>(10.244.0.94)<br>[NO LOAD]"]
end
subgraph frontend
direction LR
P1["Pod3<br>(10.244.0.120)"]
P2["Pod4<br>(10.244.0.122)"]
end
frontend -- connects to --> backend
end
style P3 fill:#ff3f00,stroke:#8b0000,color:#000000,stroke-width:1px
Deployment Can’t Solve
Kubernetes Deployment resource doesn’t have any mechanism to solve any problem mentioned above. You can’t stop the change in IP Address, it’s dynamic. You can’t load balance unless there is something that keeps track of all the endpoints and expose them through one virtual endpoint (IP:PORT).
Who Solves Then?
The responsibility of exposing apps in Kubernetes is deligated to Service. Roughly speaking, Service keeps list of all endpoints and expose your application onto one endpoint which you can connect to — internal load balancing is automatically taken care. You are eager to know how Service does what it does — see you in the next chapter.