0

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-server cần được cài đặt và bật để các lệnh kubectl top hoạt động.

Nếu ko lỗi sau sẽ xảy ra:

error: Metrics API not available

Hãy cài ngay:

minikube addons enable metrics-server

Kiểm tra:

kubectl get pods -n kube-system -l k8s-app=metrics-server

Mong đợ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

Viblo
Let's register a Viblo Account to get more interesting posts.