Lab 3 - Productionize Todo Application
Series: Kubernetes từ Zero đến Production. Github của lab 3
Tình huống thực tế
Sau khi hoàn thành Lab 2, hệ thống Todo đã được deploy thành công.
Kiến trúc hiện tại:
Browser
│
Ingress
│
Frontend
│
Backend
│
PostgreSQL
Team bắt đầu mở hệ thống cho người dùng nội bộ sử dụng.
Mọi thứ đều ổn...
- Cho đến một ngày backend bị treo. Người dùng không thể tạo Todo mới.
- Một tuần sau, bạn cần deploy phiên bản mới.
- Trong quá trình cập nhật, toàn bộ hệ thống bị gián đoạn khoảng 30 giây.
- Lần khác nữa, backend tiêu tốn quá nhiều RAM khiến các Pod khác cũng bị ảnh hưởng.
Đây là những vấn đề rất phổ biến khi đưa ứng dụng lên production.
Trong bài lab này, chúng ta sẽ giải quyết chúng bằng các tính năng sẵn có của Kubernetes.
Mục tiêu
Sau khi hoàn thành bài lab, bạn sẽ biết cách:
- Thêm Health Check cho ứng dụng
- Cấu hình
readinessProbe - Cấu hình
livenessProbe - Giới hạn CPU và RAM của Pod
- Thực hiện Rolling Update
- Quan sát quá trình cập nhật không downtime
Kiến trúc sau khi Productionize
User
│
Ingress
│
+-------+-------+
│ │
Frontend Backend (3 Pods)
│
PostgreSQL
Mỗi Backend Pod sẽ:
- Có Health Check
- Có Resource Requests
- Có Resource Limits
- Có khả năng Rolling Update
Chuẩn bị
Tiếp tục sử dụng cluster từ Lab 2.
Kiểm tra trạng thái:
kubectl get all -n todo-app
Đảm bảo:
- Frontend Running
- Backend Running
- PostgreSQL Running
Nếu chưa chạy thì hãy thực hiện deploy kubernetes lần nữa:
- Tách namespace.yaml ra khỏi thư mục kubernetes và ưu tiên chạy trước. Để đảm bảo ko lỗi thiếu namespace khi chạy các file yaml khác trong kubernetes
- Sau đó thực thi lệnh sau để deploy lại
kubectl apply -f namespace.yaml && until kubectl get ns todo-app >/dev/null 2>&1; do sleep 1; done && kubectl apply -f kubernetes/
Kiểm tra lại:
kubectl get all -n todo-app
Ví dụ mong đợi:
NAME READY STATUS RESTARTS AGE
pod/todo-backend-5f4cb76f55-4nhxz 1/1 Running 0 32s
pod/todo-backend-5f4cb76f55-59nzd 1/1 Running 0 32s
pod/todo-backend-5f4cb76f55-k6wsd 1/1 Running 0 32s
pod/todo-frontend-6566c9b7fd-6l7jg 1/1 Running 0 32s
pod/todo-frontend-6566c9b7fd-9tssq 1/1 Running 0 32s
pod/todo-postgres-9fcc6fd7b-25bwd 1/1 Running 0 32s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/todo-backend-service ClusterIP 10.105.34.249 <none> 8080/TCP 32s
service/todo-frontend-service ClusterIP 10.109.155.152 <none> 80/TCP 32s
service/todo-postgres-service ClusterIP 10.99.208.196 <none> 5432/TCP 32s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/todo-backend 3/3 3 3 32s
deployment.apps/todo-frontend 2/2 2 2 32s
deployment.apps/todo-postgres 1/1 1 1 32s
NAME DESIRED CURRENT READY AGE
replicaset.apps/todo-backend-5f4cb76f55 3 3 3 32s
replicaset.apps/todo-frontend-6566c9b7fd 2 2 2 32s
replicaset.apps/todo-postgres-9fcc6fd7b 1 1 1 32s
Phần 1. Thêm Spring Boot Actuator
Đầu tiên, backend cần cung cấp API để Kubernetes kiểm tra sức khỏe.
Thêm dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Sau đó cấu hình:
- Mở file backend/src/main/resources/application.yml
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always
Mong đợi khi chạy sau khi deploy lại:
curl http://todo.company.local/actuator/health
Kết quả:
{"status":"UP","components":{"db":{"status":"UP","details":{"database":"PostgreSQL","validationQuery":"isValid()"}},"diskSpace":{"status":"UP","details":{"total":86289817600,"free":8040321024,"threshold":10485760,"path":"/app/.","exists":true}},"livenessState":{"status":"UP"},"ping":{"status":"UP"},"readinessState":{"status":"UP"}},"groups":["liveness","readiness"]}%
Đây chính là endpoint mà Kubernetes sẽ sử dụng để xác định ứng dụng còn hoạt động hay không.
Tuy nhiên tạm thời ta chưa khởi động lại để áp dụng health check trên vì phần khởi động lại sẽ được viết ở bước phía dưới.
Phần 2. Readiness Probe
Readiness Probe trả lời câu hỏi:
"Ứng dụng đã sẵn sàng nhận request chưa?"
Nếu câu trả lời là chưa, Kubernetes sẽ không gửi traffic đến Pod.
Thêm vào Deployment Backend:
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
Ý nghĩa:
- Đợi 10 giây sau khi Pod khởi động.
- Sau đó cứ mỗi 5 giây kiểm tra một lần.
Nếu Probe thất bại:
User
↓
Service
↓
❌ Pod chưa Ready
Traffic sẽ tự động chuyển sang các Pod khác.
Phần 3. Liveness Probe
Liveness Probe trả lời câu hỏi:
"Ứng dụng còn sống không?"
Nếu backend bị deadlock hoặc treo nhưng process vẫn tồn tại, Kubernetes sẽ tự khởi động lại Pod.
Thêm vào Deployment Backend:
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
Ý nghĩa:
- Đợi 30 giây sau khi container vừa được tạo ra mới tiến hành đợt kiểm tra đầu tiên.
- Tần suất kiểm tra định kỳ: cứ 10 giây một lần, K8s lại gửi một request HTTP GET tới /actuator/health.
Nếu Probe liên tục thất bại:
Liveness Failed
│
Restart Pod
│
Application chạy lại
Đây là cơ chế tự phục hồi rất quan trọng trong Kubernetes.
Phần 4. Resource Requests và Limits
Nếu không giới hạn tài nguyên, một Pod có thể sử dụng quá nhiều CPU hoặc RAM và ảnh hưởng đến toàn bộ Node.
Thêm vào Deployment Backend:
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
Ý nghĩa:
- Requests: Lượng tài nguyên tối thiểu Kubernetes đảm bảo cho Pod.
- CPU: 500m: Kubernetes chỉ xếp Pod vào Node nào còn đủ 0.5 CPU core rảnh
- RAM: 512Mi: Yêu cầu tối thiểu 512 Megabytes RAM
Scheduler sẽ chỉ đặt Pod lên Node có đủ tài nguyên. Ngoài ra, chú ý ko để requests.cpu quá thấp, khiến ko đủ cpu để chạy cả Liveness và Readiness.
- Limits: Giới hạn tối đa Pod được phép sử dụng.
- CPU: 1 Core: Giới hạn tối đa không được vượt quá 1 CPU core.
- RAM: 1Gi: Giới hạn tối đa 1 Gigabyte RAM.
Nếu vượt quá RAM:
- Nếu ứng dụng ngốn quá 1GiB RAM, Kubernetes sẽ kill ngay lập tức với lỗi OOMKilled (Out Of Memory).
Nếu vượt quá CPU:
- Pod sẽ bị CPU Throttling (chạy chậm lại)
Kiểm tra:
kubectl describe pod -n todo-app <pod-name>
Bạn sẽ thấy sau khi deploy lại:
Limits:
cpu: 1
memory: 1Gi
Requests:
cpu: 500m
memory: 512Mi
Phần 5. Rolling Update
Đây cũng là phần quan trọng nhất, để áp dụng đầy đủ các cập nhật mới (Readiness/Liveness Probes, Resource Limits, Actuator) bên trên, bạn có 2 bước chính cần thực hiện:
Build image v2 cho Backend:
minikube image build -t todo-backend:v2 ./backend
Cập nhật image v2 trong deployment Backend:
image: todo-backend:v2
Deploy lên Minikube:
- Rolling Update là chiến lược cập nhật mặc định của Deployment trong Kubernetes
- Chỉ cần chạy lệnh sau
kubectl apply -f backend-deployment.yaml
Quan sát quá trình rollout:
kubectl rollout status deployment/todo-backend -n todo-app
Hoặc:
kubectl get pods -w -n todo-app
Bạn sẽ thấy:
Waiting for deployment "todo-backend" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "todo-backend" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "todo-backend" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "todo-backend" rollout to finish: 1 old replicas are pending termination...
...
deployment "todo-backend" successfully rolled out
Kubernetes không xóa toàn bộ Pod cũ cùng lúc.
Thay vào đó, Pod mới chỉ bắt đầu nhận traffic khi đã vượt qua Readiness Probe.
Nhờ vậy, người dùng gần như không nhận thấy quá trình cập nhật.
Lúc này có thể kiểm tra health check ở phần 1 rồi.
Phần 6. Kiểm tra lịch sử Deployment
Xem lịch sử:
kubectl rollout history deployment/todo-backend -n todo-app
Lịch sử hiện ra:
deployment.apps/todo-backend
REVISION CHANGE-CAUSE
1 <none>
2 <none>
Kiểm tra trạng thái:
kubectl rollout status deployment/todo-backend -n todo-app
Trạng thái hiện ra:
deployment "todo-backend" successfully rolled out
Nếu cập nhật gặp lỗi, bạn có thể quay lại phiên bản trước:
- Chạy lệnh rollout undo là cách nhanh nhất để cứu app đang hỏng
- App sẽ quay lại bản v1
- Nhưng hãy nhớ thay đổi image thành v1 trong deployment backend để để file YAML đồng nhất với cluster
kubectl rollout undo deployment/todo-backend -n todo-app
Rollback là một trong những tính năng mạnh mẽ giúp giảm rủi ro khi triển khai phiên bản mới.
Phần 7. Kiểm tra Resource
Xem tài nguyên Pod:
kubectl top pods -n todo-app
Tài nguyên pod hiện ra:
NAME CPU(cores) MEMORY(bytes)
todo-backend-6f4d78586b-fvzfz 7m 212Mi
todo-backend-6f4d78586b-kwbqn 10m 222Mi
todo-backend-6f4d78586b-s5xhc 9m 224Mi
todo-frontend-6566c9b7fd-6l7jg 0m 8Mi
todo-frontend-6566c9b7fd-9tssq 0m 8Mi
todo-postgres-9fcc6fd7b-25bwd 3m 65Mi
Xem tài nguyên Node:
kubectl top nodes
Tài nguyên Node hiện ra:
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
minikube 310m 2% 1773Mi 22%
So sánh mức sử dụng thực tế với Requests và Limits để điều chỉnh cấu hình phù hợp.
Lưu ý:
metrics-servercần được cài đặt và bật để các lệnhkubectl tophoạt động.Nếu ko lỗi sau sẽ xảy ra:
error: Metrics API not availableHãy cài ngay:
minikube addons enable metrics-serverKiểm tra:
kubectl get pods -n kube-system -l k8s-app=metrics-serverMong đợi:
NAME READY STATUS RESTARTS AGE metrics-server-9d74bb658-ds5pg 1/1 Running 0 75s
Thử tạo lỗi
Tình huống 1: Health Check sai
Đổi đường dẫn:
path: /health
Thay vì:
path: /actuator/health
Quan sát:
kubectl describe pod -n todo-app <pod-name>
Bạn sẽ thấy các sự kiện Probe thất bại và Pod liên tục bị đánh dấu là Not Ready hoặc bị khởi động lại.
Tình huống 2: Thiếu tài nguyên
Giảm Memory Limit xuống:
memory: 128Mi
Nếu ứng dụng cần nhiều RAM hơn, Pod có thể bị:
OOMKilled
Kiểm tra:
kubectl describe pod -n todo-app <pod-name>
Mở rộng và tự thực hành
Nhận xét:
- Backend probe hiện tại đang dùng cùng một endpoint /actuator/health cho cả readiness và liveness, nên chỉ cần DB chậm hoặc tạm DOWN là pod dễ bị restart liên tục thay vì chỉ bị rút khỏi load balancer
- Sửa health check cho backend theo đúng readiness, liveness, và thêm startupProbe để giảm lỗi restart lặp
- Backend chưa có startupProbe, nên lúc app Java khởi động chậm hoặc chờ DB, kube có thể đánh là unhealthy quá sớm rồi vào vòng lặp restart.
- Bật actuator probe support trong Spring Boot
- Postgres chưa có probe, nên backend có thể cố kết nối khi DB chưa sẵn sàng và fail theo dây chuyền.
- Thêm probe cho Postgres để backend không đâm vào DB khi DB chưa sẵn sàng
- OOMKilled có thể xảy ra nếu RAM cho Postgres quá ít
- Thêm cấu hình resource cho Postgres để tránh OOMKilled
Tham khảo ở đây nhé.
Dọn dẹp
kubectl delete namespace todo-app && \
kubectl wait --for=delete namespace/todo-app --timeout=60s && \
kubectl delete pv postgres-pv
Namespace bị xóa thì toàn bộ Deployment, Service, Pod, ConfigMap, Secret, PVC bên trong cũng bị xóa theo. Còn PV thì phải xoá riêng vì nó là tài nguyên cấp cluster, không nằm trong namespace.
Những gì bạn đã học
Sau bài lab này, bạn đã biết:
✅ Health Check với Spring Boot Actuator
✅ Readiness Probe
✅ Liveness Probe
✅ Resource Requests
✅ Resource Limits
✅ Rolling Update
✅ Rollback Deployment
✅ Theo dõi tài nguyên với kubectl top
✅ Debug các lỗi liên quan đến Probe và tài nguyên
Bài học rút ra
Một ứng dụng chạy được chưa đồng nghĩa với việc sẵn sàng cho production.
Muốn hệ thống ổn định, bạn cần đảm bảo:
- Kubernetes chỉ gửi traffic đến các Pod đã sẵn sàng.
- Pod bị treo sẽ được tự động khởi động lại.
- Mỗi Pod có giới hạn tài nguyên rõ ràng để tránh ảnh hưởng lẫn nhau.
- Việc cập nhật phiên bản mới diễn ra từng bước, không gây downtime cho người dùng.
Đó chính là nền tảng của một hệ thống có khả năng vận hành ổn định trong môi trường thực tế.
Ở bài lab tiếp theo, chúng ta sẽ tiếp tục mở rộng hệ thống Todo theo kiến trúc Microservices, nơi nhiều dịch vụ cùng phối hợp để tạo thành một ứng dụng hoàn chỉnh.
All Rights Reserved