Docker Compose Include: Cách “chia để trị” file YAML nghìn dòng cho Microservices

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Cơn ác mộng mang tên docker-compose.yml “khổng lồ”

Nếu từng làm dự án Microservices thực tế, chắc bạn không lạ gì cảnh file docker-compose.yml dài lê thê. Mình từng quản lý một hệ thống E-commerce với 15 microservices và 5 database. Kết quả là file cấu hình phình to hơn 800 dòng code.

Hậu quả rất rõ ràng: tìm kiếm thông tin cực kỳ mệt mỏi. Chỉ một lỗi thụt đầu dòng (indentation) nhỏ cũng đủ làm cả hệ thống sập. Trong team, việc 5-7 người cùng sửa một file YAML nghìn dòng thường xuyên gây ra “thảm họa” conflict khi merge Git. Có lần mình mất trắng cả buổi sáng chỉ để giải quyết xung đột cấu hình cho một đống dịch vụ lằng nhằng.

Tại sao file Compose của bạn cứ ngày một phình to?

Thực ra Docker không lỗi, lỗi tại cách chúng ta “nhồi nhét”. Ban đầu dự án chỉ có Web và DB nên một file là đủ. Nhưng khi quy mô tăng lên, bạn sẽ cần thêm hàng loạt thứ:

  • Hệ thống hàng đợi: RabbitMQ, Redis, Kafka.
  • Các service nghiệp vụ riêng biệt: Auth, Order, Payment, Shipping.
  • Bộ công cụ giám sát: Prometheus, Grafana.
  • Quản lý Log: ELK stack hoặc Loki.

Nhiều người có tâm lý “gom tất cả vào một chỗ cho dễ chạy”. Chính tư duy này biến file cấu hình thành một đống bùi nhùi không thể bảo trì.

Những nỗ lực chia nhỏ trước khi có tính năng include

Trước khi Docker Compose V2 (phiên bản 2.20.0 trở lên) ra đời, chúng ta thường dùng hai cách dưới đây, nhưng cả hai đều bộc lộ hạn chế.

Cách 1: Sử dụng nhiều file với flag -f

Bạn chia nhỏ thành docker-compose.db.yml, docker-compose.app.yml. Khi khởi động, bạn phải gõ một lệnh dài dằng dặc như thế này:

docker compose -f docker-compose.yml -f docker-compose.db.yml -f docker-compose.app.yml up -d

Quên một cái flag -f thôi là “ăn hành” ngay. Container này sẽ không thấy container kia, hoặc các volume mount sai chỗ khiến dữ liệu bay màu.

Cách 2: Sử dụng extends

Từ khóa extends giúp tái sử dụng cấu hình giữa các service. Tuy nhiên, nó chỉ giải quyết được phần ngọn. Bạn không thể dùng nó để module hóa toàn bộ một nhóm tài nguyên gồm cả Networks và Volumes một cách gọn gàng.

Giải pháp tối ưu: Module hóa với tính năng include

Mình đã chuyển toàn bộ stack dự án sang Docker Compose V2 và cảm thấy cực kỳ nhẹ nhõm. Tính năng include cho phép bạn tách các nhóm dịch vụ liên quan thành những file riêng. Sau đó, bạn chỉ cần “nhúng” chúng vào file chính một cách chuyên nghiệp.

Cái hay nhất của include là gì?

Khác biệt lớn nhất chính là khả năng tự động xử lý đường dẫn tương đối (relative path). Khi include một file từ thư mục khác, Docker Compose tự hiểu các đường dẫn build context hay env_file phải tính từ vị trí của file được include đó. Bạn không còn phải sửa đường dẫn thủ công mỗi khi di chuyển file.

Ví dụ thực tế: Cấu trúc dự án Microservices

Giả sử mình tổ chức dự án gồm phần Hạ tầng (DB, Redis) và phần Ứng dụng (Web, Worker) như sau:

.
├── docker-compose.yml
├── infrastructure/
│   └── db-stack.yml
└── services/
    └── app-stack.yml

Nội dung file infrastructure/db-stack.yml rất tinh gọn:

services:
  postgres:
    image: postgres:15-alpine
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

Và đây là cách file docker-compose.yml ở gốc dự án triệu gọi mọi thứ:

name: my-ecommerce-platform

include:
  - infrastructure/db-stack.yml
  - services/app-stack.yml

services:
  gateway:
    image: nginx:alpine
    ports:
      - "80:80"
    depends_on:
      - web

Giờ đây, chỉ cần một lệnh docker compose up -d duy nhất, Docker sẽ tự gộp tất cả module thành một stack hoàn chỉnh.

4 lý do bạn nên dùng include ngay hôm nay

  1. Phân quyền rõ ràng: Team DevOps quản lý folder infrastructure, team Dev tập trung vào services. Không ai dẫm chân lên ai khi code.
  2. Tái sử dụng cực nhanh: Bạn có thể tạo một bộ cấu hình DB chuẩn (Gold Standard) và include vào 10 dự án khác nhau trong 30 giây.
  3. Debug siêu tốc: Lỗi ở đâu, bạn chỉ cần mở đúng file module đó. File chính lúc này chỉ đóng vai trò như một “bản đồ” tổng thể.
  4. Quản lý Network thông minh: Thuộc tính name ở file gốc đảm bảo mọi container từ các module khác nhau đều tự động nằm chung một mạng mặc định.

Kinh nghiệm thực tế từ xương máu

Qua quá trình triển khai, mình rút ra vài lưu ý nhỏ để bạn tránh mắc lỗi:

  • Luôn đặt tên project bằng name: ở file gốc để dễ quản lý container bằng lệnh docker ps.
  • Tận dụng file .env riêng cho từng module để tránh biến môi trường bị chồng chéo.
  • Đừng chia quá nhỏ. Hãy gom nhóm theo logic nghiệp vụ (ví dụ: nhóm Payment, nhóm Ship) thay vì tách mỗi service một file.

Chuyển sang include giúp mình tiết kiệm ít nhất 30% thời gian mỗi khi cần scale dự án. Nếu bạn đang mệt mỏi với đống YAML hỗn độn, hãy áp dụng ngay. Đây không chỉ là kỹ thuật, mà là cách chúng ta quản lý hệ thống thông minh hơn.

Share: