AI Hints
Home›Kubernetes Tutorial›Workshop: A Complete Application
Kubernetes · Lesson 68 of 86

Build It Layer by Layer

From the data tier upward.

⏱ 2 min read▶ Video at 2:43:33🎓 Free
Watch this lesson in the Kubernetes courseWatch on YouTube ↗Full Kubernetes course

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.

Build It Layer by Layer
From the video: Build It Layer by Layer
  • 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.

02-api.yamlyaml
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: 5678

Data 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
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: 6379
File 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: 5678
File 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: 80
File 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: 80

The 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.

04-ingress.yamlyaml
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: 80

Web, 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
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: 6379
File 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: 5678
File 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: 80
File 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: 80

Summary

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
Next up: Run and Operate It — visit, scale, update, roll back