Vấn đề: Docker báo xanh nhưng ứng dụng đã “ngỏm”
Docker báo trạng thái container là Up, nhưng truy cập website lại hiện lỗi 502 Bad Gateway. Đây là tình huống cực kỳ ức chế. Bạn kiểm tra log và thấy ứng dụng đã treo cứng từ lâu.
Vấn đề là Docker chỉ theo dõi tiến trình (process) chính. Nếu app bị tràn bộ nhớ (OOM), deadlock hoặc mất kết nối DB nhưng process không thoát, Docker vẫn coi là ổn. Hệ thống lúc này rơi vào trạng thái “sống thực vật”: không chết hẳn nhưng cũng chẳng làm việc được.
Trong dự án đầu tiên, mình từng chủ quan chỉ dùng restart: always. Khi server quá tải khiến Database phản hồi chậm, ứng dụng Node.js treo kết nối nhưng container vẫn báo xanh lơ. Khách hàng phàn nàn liên tục trong khi mình vẫn đinh ninh hệ thống ổn định. Để xử lý triệt để, bạn cần bộ đôi: Restart Policies và Healthcheck.
1. Tự phục hồi với Restart Policies
Restart Policies giúp container tự đứng dậy sau khi sập nguồn hoặc crash. Docker cung cấp 4 lựa chọn chính:
- no: Mặc định. Docker đứng nhìn container chết mà không làm gì cả.
- always: Luôn khởi động lại bất kể lý do dừng. Nếu bạn reboot server, container này cũng tự động chạy lại theo Docker daemon.
- on-failure: Chỉ khởi động lại nếu exit code khác 0. Phù hợp cho các job xử lý dữ liệu cần chạy xong rồi nghỉ.
- unless-stopped: Giống
alwaysnhưng có một điểm cộng. Nếu bạn chủ động dùng lệnhdocker stop, nó sẽ nằm yên cho đến khi bạn bật lại thủ công.
Cấu hình trong docker-compose.yml rất gọn gàng:
services:
web-app:
image: nginx:1.25-alpine
restart: unless-stopped
Mình thường ưu tiên unless-stopped. Nó giúp app tự quay lại sau khi server bảo trì, đồng thời tránh việc container tự bật lại gây phiền toái khi mình đang cần tắt app để debug.
2. Healthcheck: “Bác sĩ” riêng cho Container
Nếu Restart Policies chỉ biết container sống hay chết, Healthcheck lại biết ứng dụng có đang làm việc hiệu quả không. Nó giống như việc bạn định kỳ gửi một tín hiệu hỏi: “Này, còn phản hồi được không?”
Các thông số bạn cần nắm vững
- test: Câu lệnh kiểm tra (thường dùng
curlhoặcpg_isready). - interval: Tần suất kiểm tra (ví dụ: cứ 30 giây một lần).
- timeout: Sau bao lâu không phản hồi thì coi như lần kiểm tra đó thất bại.
- retries: Số lần hỏng liên tiếp (ví dụ: 3 lần) trước khi dán nhãn
unhealthy. - start_period: Thời gian chờ app khởi động. Một app Java Spring Boot có thể mất 45 giây để lên, hãy cho nó thời gian chuẩn bị trước khi bắt đầu soi xét.
3. Cấu hình thực tế cho ứng dụng Node.js
Giả sử bạn có ứng dụng chạy ở cổng 3000. Đừng chỉ hy vọng, hãy ép Docker phải kiểm tra nó.
services:
my-api:
image: node-app:v1
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Trong cấu hình này, lệnh curl -f sẽ trả về lỗi nếu endpoint /health phản hồi mã 500 hoặc timeout. Nhờ start_period: 40s, Docker sẽ kiên nhẫn đợi app load xong thư viện rồi mới bắt đầu tính điểm sức khỏe.
Nhúng Healthcheck vào Dockerfile
Cách tốt nhất là đóng gói cơ chế này vào image để mọi môi trường đều được bảo vệ:
FROM node:18-alpine
RUN apk add --no-cache curl
# ... setup app ...
HEALTHCHECK --interval=1m --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
CMD ["npm", "start"]
4. Phối hợp nhịp nhàng giữa các dịch vụ
Một lỗi phổ biến là app khởi động nhanh hơn Database, dẫn đến lỗi kết nối ngay từ đầu. Thay vì dùng các script chờ đợi phức tạp, hãy dùng depends_on kết hợp với điều kiện sức khỏe:
services:
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 10s
app:
image: my-node-app
depends_on:
db:
condition: service_healthy
Bây giờ, container app sẽ kiên nhẫn đợi cho đến khi db thực sự sẵn sàng nhận kết nối. Cách làm này chuyên nghiệp và tin cậy hơn hẳn việc dùng sleep 10 mù quáng.
5. Lưu ý về tài nguyên: Đừng kiểm tra quá đà
Healthcheck không miễn phí. Mỗi lần chạy, nó tiêu tốn một lượng nhỏ CPU và RAM. Nếu bạn đặt interval: 1s cho 20 container, server sẽ lãng phí tài nguyên chỉ để tự kiểm tra lẫn nhau.
Con số hợp lý thường là 30 giây đến 1 phút cho các dịch vụ thông thường. Hãy ưu tiên các lệnh kiểm tra nhẹ nhàng, tránh thực hiện các câu truy vấn SQL nặng nề chỉ để check xem DB còn sống hay không.
Tổng kết
Kết hợp Restart Policies và Healthcheck giúp bạn có một hệ thống tự chữa lành (self-healing). Bạn sẽ không còn phải thức giấc lúc 2 giờ sáng chỉ để gõ lệnh docker restart.
Ba quy tắc nằm lòng:
- Dùng
unless-stoppedcho hầu hết các web service. - Luôn có
start_periodđể app không bị khai tử khi chưa kịp khởi động. - Dùng
service_healthyđể quản lý thứ tự chạy của các dịch vụ phụ thuộc.
Áp dụng ngay những kỹ thuật này để ứng dụng của bạn hoạt động lì lợm và ổn định hơn.
