0

Bài 4: Xử lý Health Check và thứ tự khởi động với depends_on nâng cao

Ở Bài 3, chúng ta đã ghép nối thành công ứng dụng Go với PostgreSQL và Redis trong cùng một file docker-compose.yml. Tuy nhiên, bạn chắc chắn đã bắt gặp một lỗi kinh điển: Ứng dụng Go khởi động quá nhanh, cố gắng kết nối tới PostgreSQL ngay giây đầu tiên, trong khi database vẫn đang trong quá trình khởi tạo (khởi động daemon, phân bổ bộ nhớ). Kết quả là ứng dụng Go văng lỗi connection refused và crash.

Mặc dù trong file compose chúng ta đã khai báo depends_on: - postgres, nhưng theo cơ chế mặc định của Docker Compose, depends_on chỉ chờ container postgres được tạo và khởi động (started) chứ không hề chờ cho đến khi cơ sở dữ liệu thực sự sẵn sàng nhận câu lệnh SQL.

Trong bài học này, chúng ta sẽ giải quyết triệt để vấn đề trên bằng tính năng healthcheck kết hợp với depends_on nâng cao (condition: service_healthy).

1. Cơ chế Health Check gốc của Docker

Docker cho phép bạn định nghĩa một lệnh kiểm tra sức khỏe trực tiếp bên trong file docker-compose.yml cho từng service. Docker sẽ định kỳ chạy lệnh đó bên trong container; nếu lệnh trả về mã thoát 0 (thành công), container được coi là healthy.

Đối với PostgreSQL, hình thức kiểm tra chuẩn xác nhất là sử dụng lệnh pg_isready có sẵn bên trong image.

Hãy xem cách chúng ta định nghĩa health check cho PostgreSQL:

YAML

postgres:
  image: postgres:16-alpine
  container_name: postgres_db
  environment:
    POSTGRES_USER: root
    POSTGRES_PASSWORD: secretpassword
    POSTGRES_NAME: gocourse
  ports:
    - "5432:5432"
  # Cấu hình Health Check cho PostgreSQL
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U root -d gocourse"]
    interval: 5s      # Kiểm tra mỗi 5 giây một lần
    timeout: 5s       # Timeout sau 5 giây nếu không phản hồi
    retries: 5        # Thử lại tối đa 5 lần trước khi đánh dấu là unhealthy
    start_period: 3s  # Thời gian đệm cho phép database khởi động ban đầu

2. Sử dụng depends_on nâng cao (condition: service_healthy)

Khi dịch vụ postgres đã có một cơ chế healthcheck rõ ràng, chúng ta có thể cấu hình service app (Golang) đứng chờ cho đến khi trạng thái của postgres chuyển thành healthy thì mới chính thức được phép khởi động.

Cập nhật lại service app trong docker-compose.yml:

YAML

  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: go_app_dev
    ports:
      - "8080:8080"
    volumes:
      - .:/app
      - go_mod_cache:/go/pkg/mod
    environment:
      - PORT=8080
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_USER=root
      - DB_PASSWORD=secretpassword
      - DB_NAME=gocourse
    # ĐIỀU KIỆN KHỞI ĐỘNG NÂNG CAO
    depends_on:
      postgres:
        condition: service_healthy # Chờ đến khi postgres hoàn toàn khỏe mạnh
      redis:
        condition: service_started # Redis chỉ cần khởi động là đủ
    restart: always

3. Kiểm chứng quá trình khởi động

Khi bạn chạy lệnh:

Bash

docker compose up --build

Hãy quan sát kỹ các dòng log trên Terminal:

  1. Docker sẽ khởi động container postgres và redis trước tiên.

  2. Bạn sẽ thấy trạng thái của postgres chuyển sang starting và liên tục chạy lệnh kiểm tra pg_isready.

  3. Ngay khi lệnh kiểm tra trả về thành công, trạng thái của postgres chuyển sang healthy.

  4. Lúc này, container app (Golang) mới chính thức được kích hoạt và khởi chạy. Ứng dụng Go kết nối thẳng tới PostgreSQL thành công ngay từ lần thử đầu tiên mà không hề xuất hiện bất kỳ lỗi connection refused nào nữa.

Tổng kết

Bằng cách kết hợp healthcheck và depends_on: condition: service_healthy, bạn đã biến môi trường Docker Compose từ một tập hợp các container rời rạc thành một hệ sinh thái có trật tự, biết chờ đợi và tương tác mượt mà theo đúng thứ tự logic của ứng dụng thực tế.

Trong bài học cuối cùng của series, chúng ta sẽ tối ưu hóa tốc độ build bằng Docker BuildKit Cache và tìm hiểu cách cấu hình Remote Debugging để debug code Go đang chạy bên trong container trực tiếp từ VS Code.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí