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ả:

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