Deploying a multi-tier web application on Kubernetes requires orchestrating frontend and backend services to ensure seamless communication, scalability, and resource limits. A classic example is the Redis Guestbook Application:
NodePort Service.The communication between the frontend tier and backend tier is dynamically resolved via Kubernetes DNS services.
graph TD
subgraph FrontendTier ["Frontend Tier"]
ServiceFE["Service (NodePort): frontend <br> Port: 80 / NodePort: 30009"] -->|Routes HTTP Traffic| PodFE1["Pod: frontend-1"]
ServiceFE -->|Routes HTTP Traffic| PodFE2["Pod: frontend-2"]
ServiceFE -->|Routes HTTP Traffic| PodFE3["Pod: frontend-3"]
end
subgraph BackendStorageTier ["Backend Storage Tier"]
PodFE1 & PodFE2 & PodFE3 -->|Writes to port 6379| ServiceMaster["Service (ClusterIP): redis-master <br> Port: 6379"]
PodFE1 & PodFE2 & PodFE3 -->|Reads from port 6379| ServiceSlave["Service (ClusterIP): redis-slave <br> Port: 6379"]
ServiceMaster -->|Routes Writes| PodMaster["Pod: redis-master <br> 1 Replica"]
ServiceSlave -->|Routes Reads| PodSlave1["Pod: redis-slave-1"]
ServiceSlave -->|Routes Reads| PodSlave2["Pod: redis-slave-2"]
ServiceFollower["Service (ClusterIP): redis-follower <br> Port: 6379"] -->|Selector: app=redis-slave| PodSlave1
ServiceFollower -->|Selector: app=redis-slave| PodSlave2
PodMaster -.->|Replication| PodSlave1 & PodSlave2
end
User["Web Client"] -->|HTTP Request to NodeIP:30009| ServiceFE
In the Guestbook application, the frontend needs to connect to the Redis database without knowing the static IP addresses of the backend pods.
redis-master), a DNS record is registered as redis-master.default.svc.cluster.local.GET_HOSTS_FROM is set to dns. This directs the frontend container’s connection client to lookup hostnames redis-master and redis-slave via the cluster DNS resolver, rather than relying on legacy environment variables.Defining CPU and memory requirements ensures that containers have guaranteed resources to run and prevents rogue containers from exhausting host memory.
resources.requests): Specifies the minimum resources a container requires to start. The Kubernetes scheduler uses this value to determine which node has enough capacity to host the Pod.100m is $0.1$ of a CPU core).100Mi is $100 \times 2^{20}$ bytes).thordefaultredis-master1master-redis-xfusionredis6379100m, Memory 100Miredis-master (Port/TargetPort: 6379)redis-slave2slave-redis-xfusiongcr.io/google_samples/gb-redisslave:v36379GET_HOSTS_FROM = dns100m, Memory 100Miredis-slave (Port: 6379)redis-follower (Port/TargetPort: 6379, Selector: app = redis-slave)frontend3php-redis-xfusiongcr.io/google-samples/gb-frontend@sha256:a908df8486ff66f2c4daa0d3d8a2fa09846a1fc8efd65649c0109695c7c5cbff80GET_HOSTS_FROM = dns100m, Memory 100Mifrontend (Type: NodePort, Port: 80, NodePort: 30009)Establish an SSH connection to the cluster control host:
ssh thor@jump_host_ip
Create a file named redis-master.yaml to define the Redis Master Deployment and Service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-master
labels:
app: redis
role: master
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: redis
role: master
tier: backend
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master-redis-xfusion
image: redis
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-master
labels:
app: redis
role: master
tier: backend
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis
role: master
tier: backend
Apply the manifest file:
kubectl apply -f redis-master.yaml
Create a file named redis-slave.yaml defining the Redis Slave Deployment, redis-slave Service, and redis-follower Service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-slave
labels:
app: redis-slave
role: slave
tier: backend
spec:
replicas: 2
selector:
matchLabels:
app: redis-slave
role: slave
tier: backend
template:
metadata:
labels:
app: redis-slave
role: slave
tier: backend
spec:
containers:
- name: slave-redis-xfusion
image: gcr.io/google_samples/gb-redisslave:v3
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-slave
labels:
app: redis-slave
role: slave
tier: backend
spec:
ports:
- port: 6379
selector:
app: redis-slave
role: slave
tier: backend
---
apiVersion: v1
kind: Service
metadata:
name: redis-follower
labels:
app: redis-slave
role: slave
tier: backend
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis-slave
Apply the slave manifest file:
kubectl apply -f redis-slave.yaml
Create a file named frontend.yaml to define the Frontend Deployment and its NodePort Service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
replicas: 3
selector:
matchLabels:
app: guestbook
tier: frontend
template:
metadata:
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: php-redis-xfusion
image: gcr.io/google-samples/gb-frontend@sha256:a908df8486ff66f2c4daa0d3d8a2fa09846a1fc8efd65649c0109695c7c5cbff
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30009
protocol: TCP
selector:
app: guestbook
tier: frontend
Apply the frontend manifest file:
kubectl apply -f frontend.yaml
Ensure that all pods have reached the Running state and the replica counts are matching:
kubectl get pods
Expected Output:
NAME READY STATUS RESTARTS AGE
frontend-6bdf9c7fdb-abcde 1/1 Running 0 45s
frontend-6bdf9c7fdb-fghij 1/1 Running 0 45s
frontend-6bdf9c7fdb-klmno 1/1 Running 0 45s
redis-master-5b8d4c9d74-xyz12 1/1 Running 0 2m
redis-slave-7c5b6b8d4e-123ab 1/1 Running 0 1m
redis-slave-7c5b6b8d4e-cdef5 1/1 Running 0 1m
Check that the services are registered with the correct IP addresses, selector mappings, and ports:
kubectl get svc
Expected Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d
frontend NodePort 10.96.155.80 <none> 80:30009/TCP 45s
redis-master ClusterIP 10.96.221.14 <none> 6379/TCP 2m
redis-slave ClusterIP 10.96.102.73 <none> 6379/TCP 1m
redis-follower ClusterIP 10.96.90.118 <none> 6379/TCP 1m
Verify that redis-follower endpoint references the correct targets:
kubectl get endpoints redis-follower
Expected Output showing two endpoints corresponding to the redis-slave pods:
NAME ENDPOINTS AGE
redis-follower 10.244.1.4:6379,10.244.2.6:6379 1m
Verify connectivity to the guestbook frontend and verify that data written to the frontend persists correctly in Redis:
# Perform a curl request to check the frontend landing page
curl -I http://<NODE_IP>:30009
Expected Output:
HTTP/1.1 200 OK
Date: ...
Server: Apache/...
Content-Type: text/html; charset=UTF-8
...
The multi-tier Guestbook Application has been successfully deployed and verified on Kubernetes!