Chuyện gì xảy ra khi App chạy nhanh hơn DB?
Hồi mình mới làm mấy con Microservices cho sàn thương mại điện tử, mình dính một vố cực kỳ đau đầu. Cứ mỗi lần bảo trì server hoặc gõ docker-compose up -d, ứng dụng Node.js lại lăn ra chết (crash) liên tục. Log thì báo lỗi Connection Refused đỏ rực, dù trong file Compose mình đã để depends_on: - db rất cẩn thận.
Sau cả buổi thức đêm soi log, mình mới vỡ lẽ: Docker chỉ quan tâm container DB đã ở trạng thái Running hay chưa. Nó không hề biết dịch vụ MySQL hay Postgres bên trong đã thực sự “mở cửa” đón khách chưa. Thực tế, MySQL 8.0 thường mất khoảng 12-15 giây để khởi tạo database lần đầu, trong khi App của chúng ta chỉ mất 2 giây để nổ máy. Kết quả là App réo gọi DB khi nó còn đang bận nạp cấu hình, và thế là… sập!
Để trị dứt điểm tình trạng này, chúng ta cần bộ đôi depends_on (dạng nâng cao) và healthcheck. Đây là cách giúp cụm container của bạn tự điều phối nhịp nhàng mà không cần bạn phải can thiệp thủ công.
Cấu hình Docker Compose “thực chiến”
Nếu bạn muốn áp dụng ngay, đây là template chuẩn để bắt Web App phải đợi cho đến khi Postgres thực sự SẴN SÀNG chứ không chỉ là đang chạy.
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: my_app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d my_app"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
web-app:
build: .
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://user:password@db:5432/my_app
Thêm vài dòng healthcheck này giúp bạn tiết kiệm ít nhất 3 lần restart server thủ công mỗi khi deploy code mới. Cực kỳ đáng giá!
Tại sao chỉ dùng depends_on là chưa đủ?
Nhiều bạn mới làm quen với Docker thường mặc định rằng depends_on sẽ lo hết mọi thứ. Tuy nhiên, Docker Engine chỉ quản lý ở mức độ tiến trình (process).
- Lớp Container: Docker báo container đã chạy vì tiến trình chính (PID 1) đã khởi động.
- Lớp Ứng dụng: Các dịch vụ nặng như Java Spring Boot hay Oracle DB cần thời gian để nạp cấu hình, chiếm port và sẵn sàng xử lý request.
Docker không thể nhìn xuyên vào bên trong để biết App của bạn đã ổn chưa trừ khi bạn chỉ cho nó cách kiểm tra. Đó là lý do Health Check ra đời.
Đào sâu về Health Check – “Anh bảo vệ” tận tâm
Cứ tưởng tượng Health Check là một anh bảo vệ, cứ 5 giây lại chạy qua gõ cửa container: “Sống không em?”. Nếu container trả lời đúng mật mã (exit code 0), nó được dán nhãn healthy. Nếu im lặng quá lâu, nó sẽ bị coi là “hết cứu”.
Các thông số bạn cần tinh chỉnh:
test: Câu lệnh kiểm tra. Có thể dùngcurlgọi API hoặc lệnh nội bộ nhưmysqladmin ping.interval: Tần suất kiểm tra. Đừng để quá dày (như 1s) kẻo làm server tốn CPU vô ích.timeout: Thời gian chờ phản hồi tối đa trước khi tính là một lần thất bại.retries: Số lần thử lại liên tiếp trước khi Docker chính thức báo container bị lỗi.start_period: Thời gian “ân hạn”. Trong khoảng này, nếu check có fail thì Docker cũng bỏ qua. Rất cần cho các app nặng như Magento hay Java, vốn khởi động cực chậm.
Bí kíp tùy chỉnh cho từng loại dịch vụ
Mỗi loại dịch vụ có một cách “hỏi thăm” riêng. Dưới đây là mấy mẫu mình hay copy-paste vào dự án:
1. Với MySQL / MariaDB
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 3
2. Với Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
3. Với Web API (Node.js, Python, Go)
Nếu App không có sẵn công cụ check, hãy cài thêm curl vào Dockerfile và tạo một endpoint /health đơn giản:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
Kinh nghiệm thực tế: Đừng phó mặc hoàn toàn cho Docker
Sau thời gian dài vận hành hệ thống thực tế, mình rút ra 3 bài học xương máu cho anh em:
Thứ nhất: Luôn có Retry Logic trong code. Dù Docker Compose có xịn đến đâu, mạng nội bộ vẫn có lúc lag nhẹ. Code kết nối DB nên có vòng lặp thử lại khoảng 5-10 lần trước khi quyết định dừng hẳn. Điều này giúp hệ thống tự phục hồi cực tốt.
Thứ hai: Tránh Health Check quá nặng. Đừng dại dột viết lệnh check bằng cách chạy một câu SQL query hàng triệu bản ghi. Mỗi lần check là CPU lại nhảy vọt lên thì lợi bất cập hại. Cứ dùng mấy lệnh nhẹ nhàng nhất như ping là đủ.
Thứ ba: Kiểm tra phiên bản Compose. Tính năng condition: service_healthy yêu cầu Docker Compose file 3.x trở lên. Nếu bạn đang dùng bản cũ từ thời “đồ đá”, hãy nâng cấp ngay để tận hưởng sự tiện lợi này.
Tóm lại những sai lầm dễ mắc phải
Để kết thúc, mình điểm lại 3 lỗi mà anh em hay dính nhất:
- Chỉ dùng
depends_ondạng danh sách ngắn gọn rồi mong nó chờ App sẵn sàng. - Quên set
start_periodkhiến Docker coi container là lỗi và restart liên tục (hiện tượng boot loop). - Để
timeoutquá thấp trong khi ổ đĩa server đang bị nghẽn (I/O wait), dẫn đến báo lỗi giả.
Làm chủ được bộ đôi này không chỉ giúp file cấu hình của bạn chuyên nghiệp hơn mà còn giúp bạn ngủ ngon hơn, không lo bị gọi dậy lúc nửa đêm vì server mới restart. Chúc anh em setup mượt mà!

