0

Lab 2 - Deploy hệ thống Todo Application trên Kubernetes

Series: Kubernetes từ Zero đến Production


Tình huống thực tế

Sau khi bạn hoàn thành Lab 1 và dựng được Kubernetes Cluster, team Backend đưa cho bạn một ứng dụng Todo và nói:

"Deploy giúp bọn mình lên Kubernetes để cả team cùng test nhé!"

Ứng dụng có kiến trúc như sau:

             User
               |
               |
            Ingress
               |
        +------+------+
        |             |
    Frontend       Backend
     (React)    (Spring Boot)
                      |
                      |
                  PostgreSQL

Đây là mô hình rất phổ biến trong các công ty hiện nay:

  • Frontend React
  • Backend Spring Boot
  • Database PostgreSQL
  • Truy cập thông qua Ingress

Trong bài lab này, chúng ta sẽ triển khai toàn bộ hệ thống lên Kubernetes theo đúng tư duy production cơ bản.


Mục tiêu

Sau khi hoàn thành lab, bạn sẽ biết cách sử dụng:

  • Namespace
  • Deployment
  • Service
  • ConfigMap
  • Secret
  • PersistentVolume
  • Ingress

Đồng thời hiểu được luồng request thực tế:

Browser
 ↓
Ingress
 ↓
React Frontend
 ↓
Spring Boot Backend
 ↓
PostgreSQL

Chuẩn bị môi trường

Yêu cầu đã hoàn thành Lab 1:

  • Docker đã cài
  • Minikube đang chạy
  • kubectl hoạt động

Kiểm tra trạng thái minikube:

minikube status

Khởi động minikube:

minikube start --driver=docker

Kiểm tra cluster:

kubectl get nodes

Kết quả mong muốn:

NAME       STATUS   ROLES           AGE    VERSION
minikube   Ready    control-plane   3d1h   v1.35.1

Bước 1. Tạo Namespace riêng

Trong thực tế, mỗi ứng dụng thường có namespace riêng để dễ quản lý.

Tạo file namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: todo-app

Apply:

kubectl apply -f namespace.yaml

Kiểm tra:

kubectl get ns

Bước 2. Build Docker image cho Backend, Frontend

Cấu hình Dockerfile cho FE và BE.

Build image và load image vào cluster:

minikube image build -t todo-frontend:v1 ./frontend
minikube image build -t todo-backend:v1 ./backend

Bước 3. Tạo ConfigMap

Không nên hardcode cấu hình database trong code.

Tạo file configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: todo-config
  namespace: todo-app
data:
  DATABASE_HOST: todo-postgres-service
  DATABASE_NAME: todo

Apply:

kubectl apply -f configmap.yaml

Kiểm tra:

kubectl get configmap -n todo-app

Ví dụ:

NAME               DATA   AGE
todo-config        2      2m12s

Bước 4. Tạo Secret

Password không nên lưu trong ConfigMap.

Tạo file secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: todo-secret
  namespace: todo-app
type: Opaque
stringData:
  DATABASE_USER: todo_user
  DATABASE_PASSWORD: supersecret

Apply:

kubectl apply -f secret.yaml

Kiểm tra:

kubectl get secret -n todo-app

Ví dụ:

NAME          TYPE     DATA   AGE
todo-secret   Opaque   2      5s

Bước 5. Tạo PersistentVolume cho PostgreSQL

Database cần lưu dữ liệu lâu dài.

Tạo file postgres-pv.yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  hostPath:
    path: /data/postgres

Apply:

kubectl apply -f postgres-pv.yaml

Bước 6. Tạo PersistentVolumeClaim

Tạo file postgres-pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: todo-app
spec:
  volumeName: postgres-pv
  storageClassName: ""
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Apply:

kubectl apply -f postgres-pvc.yaml

Bước 7. Deploy PostgreSQL

Tạo file postgres-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo-postgres
  namespace: todo-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: todo-postgres
  template:
    metadata:
      labels:
        app: todo-postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          env:
            - name: POSTGRES_DB
              value: todo
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: todo-secret
                  key: DATABASE_USER
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: todo-secret
                  key: DATABASE_PASSWORD
          volumeMounts:
            - mountPath: /var/lib/postgresql/data
              name: postgres-storage
      volumes:
        - name: postgres-storage
          persistentVolumeClaim:
            claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: todo-postgres-service
  namespace: todo-app
spec:
  selector:
    app: todo-postgres
  ports:
    - port: 5432
      targetPort: 5432

Apply:

kubectl apply -f postgres-deployment.yaml

Kiểm tra:

kubectl get pods -n todo-app

Ví dụ:

NAME                            READY   STATUS    RESTARTS   AGE
todo-postgres-xxxxxx-xxxxx   1/1     Running   0          27s

Bước 8. Deploy Backend Spring Boot

Tạo file backend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo-backend
  namespace: todo-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: todo-backend
  template:
    metadata:
      labels:
        app: todo-backend
    spec:
      containers:
        - name: backend
          image: todo-backend:v1
          imagePullPolicy: Never
          env:
            - name: DATABASE_HOST
              valueFrom:
                configMapKeyRef:
                  name: todo-config
                  key: DATABASE_HOST
            - name: DATABASE_NAME
              valueFrom:
                configMapKeyRef:
                  name: todo-config
                  key: DATABASE_NAME
            - name: DATABASE_USER
              valueFrom:
                secretKeyRef:
                  name: todo-secret
                  key: DATABASE_USER
            - name: DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: todo-secret
                  key: DATABASE_PASSWORD
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: todo-backend-service
  namespace: todo-app
spec:
  selector:
    app: todo-backend
  ports:
    - port: 8080
      targetPort: 8080

Apply:

kubectl apply -f backend-deployment.yaml

Kiểm tra số lượng Pod:

kubectl get pods -n todo-app -l app=todo-backend

Bạn sẽ thấy 3 Pod đúng như yêu cầu.

Ví dụ:

todo-backend-7664d4df69-df9hq   1/1     Running       0                9s
todo-backend-7664d4df69-lw9ll   1/1     Running       0                16s
todo-backend-7664d4df69-txffq   1/1     Running       0                13s

Bước 9. Deploy Frontend React

Giả sử frontend đã build thành image todo-frontend:v1.

Tạo file frontend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo-frontend
  namespace: todo-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: todo-frontend
  template:
    metadata:
      labels:
        app: todo-frontend
    spec:
      containers:
        - name: frontend
          image: todo-frontend:v1
          imagePullPolicy: Never
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: todo-frontend-service
  namespace: todo-app
spec:
  selector:
    app: todo-frontend
  ports:
    - port: 80
      targetPort: 80

Apply:

kubectl apply -f frontend-deployment.yaml

Mở trên browser:

minikube service todo-frontend-service -n todo-app

Kết quả:

Screenshot 2026-07-27 at 23.52.20.png


Bước 10. Kiểm tra các thành phần đã tạo

Thành phần trong todo-app:

kubectl get all -n todo-app
NAME                                 READY   STATUS    RESTARTS   AGE
pod/todo-backend-65d968b89f-j44bk    1/1     Running   0          5m8s
pod/todo-backend-65d968b89f-nnx95    1/1     Running   0          5m5s
pod/todo-backend-65d968b89f-xmcfw    1/1     Running   0          5m9s
pod/todo-frontend-6566c9b7fd-c9zw5   1/1     Running   0          37m
pod/todo-frontend-6566c9b7fd-csgh4   1/1     Running   0          37m
pod/todo-postgres-9fcc6fd7b-86hsn    1/1     Running   0          42m

NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/todo-backend-service    ClusterIP   10.98.10.244     <none>        8080/TCP   76m
service/todo-frontend-service   ClusterIP   10.97.11.133     <none>        80/TCP     37m
service/todo-postgres-service   ClusterIP   10.106.175.128   <none>        5432/TCP   78m

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/todo-backend    3/3     3            3           76m
deployment.apps/todo-frontend   2/2     2            2           37m
deployment.apps/todo-postgres   1/1     1            1           42m

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/todo-backend-5cd66bffbd    0         0         0       20m
replicaset.apps/todo-backend-5f4cb76f55    0         0         0       72m
replicaset.apps/todo-backend-65d968b89f    3         3         3       5m9s
replicaset.apps/todo-backend-66db5bfc8b    0         0         0       25m
replicaset.apps/todo-backend-7664d4df69    0         0         0       39m
replicaset.apps/todo-backend-7d77dd5587    0         0         0       76m
replicaset.apps/todo-frontend-6566c9b7fd   2         2         2       37m
replicaset.apps/todo-postgres-9fcc6fd7b    1         1         1       42m

Bước 11. Bật Ingress Controller

Với Minikube:

minikube addons enable ingress

Đợi vài phút để controller chạy.

Kiểm tra:

kubectl get pods -n ingress-nginx

Ví dụ:

NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-qsqch        0/1     Completed   0          84s
ingress-nginx-admission-patch-6d4f9         0/1     Completed   0          84s
ingress-nginx-controller-596f8778bc-wn4qx   1/1     Running     0          84s

Bước 12. Tạo Ingress

Tạo file ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: todo-ingress
  namespace: todo-app
spec:
  ingressClassName: nginx
  rules:
    - host: todo.company.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: todo-frontend-service
                port:
                  number: 80

Apply:

kubectl apply -f ingress.yaml

Kiểm tra:

kubectl get ingress -n todo-app

Ví dụ:

NAME           CLASS   HOSTS                ADDRESS        PORTS   AGE
todo-ingress   nginx   todo.company.local   192.168.49.2   80      91s

Bước 13. Thêm host vào máy local

Lấy IP của Minikube:

minikube ip

Ví dụ:

192.168.49.2

Thêm vào /etc/hosts:

echo "192.168.49.2 todo.company.local" | sudo tee -a /etc/hosts
#orecho "127.0.0.1 todo.company.local" | sudo tee -a /etc/hosts for macOS

Kiểm tra:

grep todo.company.local /etc/hosts

Ví dụ:

192.168.49.2    todo.company.local
#or 127.0.0.1 todo.company.local for macOS

Bước 14. Kiểm tra hệ thống

Mở tunnel trong macos:

sudo minikube tunnel
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

Mở trình duyệt:

http://todo.company.local

Nếu mọi thứ đúng, bạn sẽ thấy giao diện Todo App.

Luồng request lúc này:

Browser
 ↓
Ingress (todo.company.local)
 ↓
Frontend Service
 ↓
Frontend Pod (React)
 ↓
Backend Service
 ↓
Backend Pod (Spring Boot)
 ↓
PostgreSQL Service
 ↓
PostgreSQL Pod

Đây chính là kiến trúc microservice cơ bản mà bạn sẽ gặp ở rất nhiều công ty.


Bước 15. Kiểm tra từng thành phần

Xem tất cả tài nguyên:

kubectl get all -n todo-app

Xem ConfigMap:

kubectl get configmap -n todo-app

Xem Secret:

kubectl get secret -n todo-app

Xem PVC:

kubectl get pvc -n todo-app

Xem Ingress:

kubectl get ingress -n todo-app

Debug nhanh khi có lỗi

Backend không chạy

kubectl logs -n todo-app deploy/todo-backend

Kiểm tra biến môi trường:

kubectl describe pod -n todo-app <pod-name>

Không kết nối được Database

Kiểm tra Service PostgreSQL:

kubectl get svc -n todo-app

Kiểm tra Endpoint:

kubectl get endpoints -n todo-app

Ingress không hoạt động

Kiểm tra controller:

kubectl get pods -n ingress-nginx

Kiểm tra host đã thêm đúng chưa:

cat /etc/hosts

Dọn dẹp môi trường

kubectl delete namespace todo-app

Namespace bị xóa thì toàn bộ Deployment, Service, Pod, ConfigMap, Secret, PVC bên trong cũng bị xóa theo.


Bạn đã học được gì?

Sau Lab 2, bạn đã biết cách triển khai một hệ thống web hoàn chỉnh trên Kubernetes:

Thành phần Kubernetes Object
React Frontend Deployment + Service
Spring Boot Backend Deployment + Service
PostgreSQL Deployment + PVC + Service
Cấu hình DB ConfigMap
Password DB Secret
Domain truy cập Ingress
Môi trường riêng Namespace

Bài học quan trọng nhất

Điểm khác biệt lớn giữa việc chạy Docker và chạy Kubernetes là mọi thứ đều được khai báo bằng object.

Thay vì nhớ hàng loạt lệnh docker run, bạn mô tả trạng thái mong muốn:

  • Tôi muốn 3 backend pod
  • Tôi muốn frontend có 2 pod
  • Tôi muốn database có storage riêng
  • Tôi muốn cấu hình nằm trong ConfigMap
  • Tôi muốn password nằm trong Secret
  • Tôi muốn truy cập qua domain todo.company.local

Kubernetes sẽ tự tạo, duy trì và phục hồi hệ thống theo mô tả đó.

Đây chính là bước chuyển từ quản lý container thủ công sang quản lý hạ tầng khai báo (Declarative Infrastructure) - tư duy cốt lõi của DevOps hiện đại.

Trong Lab 3, chúng ta sẽ tiếp tục nâng cấp hệ thống này để tiến gần hơn tới môi trường production: readiness probe, liveness probe, resource limits, rolling update và autoscaling.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí