Build It Layer by Layer
From the data tier upward.
We build from the bottom up. First the data tier, then the A P I, and last the web front end and the ingress. Building in layers lets us check each one before adding the next, which is also how professionals debug. Let's begin.

- Data tier first
- Then the API
- Then web and ingress
The API tier
Here is the A P I deployment. Two replicas, in the shop namespace. The container is a tiny echo server, which answers with a fixed message. It has a readiness probe, so it receives traffic only when it answers. And it has resource requests, and a memory limit. Below it, a service, called api, maps port eighty, to the container's port, five six seven eight.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: shop
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: hashicorp/http-echo:1.0
args: ["-text=Shop API is running"]
ports:
- containerPort: 5678
readinessProbe:
httpGet:
path: /
port: 5678
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: shop
spec:
selector:
app: api
ports:
- port: 80
targetPort: 5678Data tier and API
Let's build. First, the file for the namespace, and the Redis cache. Then the A P I file. We see each object created. And now a check. The deployments, and the services, in the shop namespace. The Redis cache is ready, and the A P I has two of two pods ready. Layer by layer, each one verified before the next.
kubectl apply -f 01-data.yaml namespace/shop created deployment.apps/redis created service/redis created kubectl apply -f 02-api.yaml deployment.apps/api created service/api created kubectl get deploy,svc -n shop NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/api 2/2 2 2 12s deployment.apps/redis 1/1 1 1 25s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/api ClusterIP 10.106.120.214 <none> 80/TCP 12s service/redis ClusterIP 10.109.142.154 <none> 6379/TCP 25s
File used: 01-data.yaml
apiVersion: v1
kind: Namespace
metadata:
name: shop
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: shop
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: shop
spec:
selector:
app: redis
ports:
- port: 6379File used: 02-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: shop
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: hashicorp/http-echo:1.0
args: ["-text=Shop API is running"]
ports:
- containerPort: 5678
readinessProbe:
httpGet:
path: /
port: 5678
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: shop
spec:
selector:
app: api
ports:
- port: 80
targetPort: 5678File used: 03-web.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-html
namespace: shop
data:
index.html: "<h1>Welcome to the Shop</h1>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: shop
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: web-html
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: shop
spec:
selector:
app: web
ports:
- port: 80File used: 04-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
namespace: shop
spec:
ingressClassName: nginx
rules:
- host: shop.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80The front door
Now the ingress. It has two rules, for the host shop dot local. The path slash A P I, goes to the A P I service. The path slash, which means everything else, goes to the web service. The controller picks the longest matching path first, so slash A P I wins over slash. Two paths, two services, one address.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
namespace: shop
spec:
ingressClassName: nginx
rules:
- host: shop.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80Web, ingress, and a check
Finally, we apply the web file, which has a config map holding the page, a deployment, and a service. And the ingress. Then we list everything. Deployments, services, and the ingress, all in the shop namespace. Every piece is in place. The pods are healthy, and the services have endpoints. Time to try it.
kubectl apply -f 03-web.yaml configmap/web-html created deployment.apps/web created service/web created kubectl apply -f 04-ingress.yaml ingress.networking.k8s.io/shop created kubectl get deploy,svc,ingress -n shop NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/api 2/2 2 2 27s deployment.apps/redis 1/1 1 1 27s deployment.apps/web 2/2 2 2 14s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/api ClusterIP 10.106.65.32 <none> 80/TCP 27s service/redis ClusterIP 10.108.248.194 <none> 6379/TCP 27s service/web ClusterIP 10.101.38.106 <none> 80/TCP 14s NAME CLASS HOSTS ADDRESS PORTS AGE ingress.networking.k8s.io/shop nginx shop.local 80 0s
File used: 01-data.yaml
apiVersion: v1
kind: Namespace
metadata:
name: shop
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: shop
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: shop
spec:
selector:
app: redis
ports:
- port: 6379File used: 02-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: shop
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: hashicorp/http-echo:1.0
args: ["-text=Shop API is running"]
ports:
- containerPort: 5678
readinessProbe:
httpGet:
path: /
port: 5678
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: shop
spec:
selector:
app: api
ports:
- port: 80
targetPort: 5678File used: 03-web.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-html
namespace: shop
data:
index.html: "<h1>Welcome to the Shop</h1>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: shop
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: web-html
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: shop
spec:
selector:
app: web
ports:
- port: 80File used: 04-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
namespace: shop
spec:
ingressClassName: nginx
rules:
- host: shop.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80Summary
Wonderful. The data tier, the A P I, the web front end, and the ingress are all running, built and verified layer by layer. Next, we use the shop, scale it, update it, and even break it on purpose.
Key takeaways- Layer by layer
- Verify each step
- Ingress ties it together