Vận hành Microservices: Khi những dòng log là chưa đủ
Hãy tưởng tượng bạn nhận một ticket báo lỗi: “User không thể thanh toán, hệ thống xoay vòng 10 giây rồi báo lỗi 500”. Trong một hệ thống Microservices chạy bằng Docker, request này có thể đi qua 5-7 dịch vụ khác nhau. Nếu chỉ dùng docker logs để mò mẫm, bạn sẽ mất hàng giờ để chắp vá các mốc thời gian. Việc tìm ra service nào đang là “nút thắt cổ chai” thực sự là một cực hình.
OpenTelemetry (OTel) sinh ra để giải quyết nỗi đau này. Nó giúp chúng ta thu thập Traces (dấu vết request) và Metrics (thông số hệ thống) một cách tập trung. Điểm mạnh nhất của OTel là khả năng Auto-instrumentation. Bạn có thể theo dõi ứng dụng Java, Python hay Node.js mà không cần sửa bất kỳ dòng code logic nào. Mọi thứ được cấu hình hoàn toàn từ bên ngoài container.
Ba cách tiếp cận Observability: Bạn đang ở đâu?
Mỗi giai đoạn phát triển dự án sẽ phù hợp với một cách triển khai khác nhau. Dưới đây là 3 phương pháp mình đã từng áp dụng:
- Manual Instrumentation (Thủ công): Bạn phải cài thư viện và viết code để tạo Span. Cách này cho độ chi tiết cao nhất nhưng lại cực kỳ tốn công khi hệ thống lên tới hàng chục service.
- Sidecar Pattern: Mỗi container ứng dụng sẽ đi kèm một container Collector phụ. Cách này phổ biến trên Kubernetes nhưng lại gây lãng phí tài nguyên RAM và CPU nếu triển khai trên Docker Compose thuần.
- Auto-Instrumentation qua Docker Env (Khuyên dùng): Bạn chỉ cần nạp Agent (file .jar hoặc package) và cấu hình qua biến môi trường. Đây là giải pháp cân bằng nhất: triển khai nhanh, không sửa code và dễ bảo trì.
Kinh nghiệm của mình cho thấy, nếu bạn muốn có kết quả ngay trong 15 phút, hãy chọn cách thứ ba.
Kiến trúc triển khai: Đừng gửi dữ liệu trực tiếp
Nhiều anh em thường cấu hình để ứng dụng đẩy dữ liệu thẳng về Jaeger hay Prometheus. Tuy nhiên, cách làm này rất thiếu linh hoạt. Thay vào đó, hãy sử dụng OTel Collector làm trạm trung chuyển theo mô hình Hub-and-Spoke.
Luồng dữ liệu chuẩn: App Container (Agent) -> OTel Collector -> Storage/Visualization (Jaeger/Grafana).
Việc dùng Collector giúp bạn đổi backend dễ dàng. Ví dụ, khi muốn chuyển từ Jaeger sang Datadog, bạn chỉ cần sửa 5 dòng config trong Collector. Toàn bộ container ứng dụng phía trước hoàn toàn không bị ảnh hưởng.
Hướng dẫn triển khai thực tế
Dưới đây là cách mình tích hợp OTel cho một ứng dụng Flask (Python) chạy trong Docker mà không cần sửa file app.py.
Bước 1: Thiết lập bộ não OTel Collector
Tạo file otel-config.yaml để định nghĩa cách nhận và đẩy dữ liệu. Đây là nơi bạn quyết định dữ liệu sẽ đi về đâu:
receivers:
otlp:
protocols:
grpc: # Cổng 4317 mặc định
http: # Cổng 4318 mặc định
exporters:
jaeger:
endpoint: "jaeger:14250"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
exporters: [jaeger]
Bước 2: Tối ưu Dockerfile với OTel Agent
Thay vì cài đặt thủ công từng thư viện, mình dùng opentelemetry-bootstrap. Lệnh này tự động nhận diện các framework như Flask, Django hay Redis để cài bản instrumentation phù hợp.
FROM python:3.9-slim
WORKDIR /app
COPY . .
RUN pip install flask opentelemetry-distro opentelemetry-exporter-otlp
# Tự động cài đặt các plugin cần thiết
RUN opentelemetry-bootstrap -a install
# Chạy ứng dụng thông qua wrapper của OTel
CMD ["opentelemetry-instrument", "python", "app.py"]
Bước 3: Kết nối bằng Docker Compose
Lưu ý nhỏ: Mình sử dụng Docker Compose V2 (lệnh docker compose không gạch ngang). Các biến môi trường sẽ đóng vai trò kết nối ứng dụng với Collector.
services:
app:
build: .
environment:
- OTEL_SERVICE_NAME=order-service
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
- OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
depends_on:
- otel-collector
otel-collector:
image: otel/opentelemetry-collector-contrib
volumes:
- ./otel-config.yaml:/etc/otel-collector-config.yaml
command: ["--config=/etc/otel-collector-config.yaml"]
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686"
Mẹo thực chiến và các lỗi gây tốn thời gian
Sau nhiều lần “ăn hành” khi triển khai thực tế, mình rút ra 3 lưu ý quan trọng:
- Vấn đề DNS trong Docker: Tuyệt đối không dùng
localhost:4317trong biến môi trường của App. Container sẽ tự tìm đến chính nó thay vì Collector. Hãy dùng tên service đã đặt trong file compose. - Kiểm soát Sampling Rate: Mặc định OTel gửi 100% dữ liệu. Với hệ thống xử lý trên 1.000 requests/giây, việc này sẽ ngốn sạch băng thông. Hãy dùng
OTEL_TRACES_SAMPLER=traceidratiovà set giá trị0.1để chỉ lấy 10% dữ liệu. - Debug Collector: Nếu Jaeger trắng trơn, hãy kiểm tra log của Collector bằng
docker logs otel-collector. Thường lỗi nằm ở việc mismatch giữa gRPC và HTTP protocol.
Lời kết
Tích hợp OpenTelemetry vào Docker là bước đi đầu tiên để làm chủ hệ thống Microservices. Phương pháp này giúp bạn có cái nhìn xuyên thấu vào từng request mà không làm bẩn mã nguồn. Khi dữ liệu đã đổ về tập trung, việc tối ưu performance sẽ trở thành một bài toán thú vị thay vì là nỗi ám ảnh mỗi khi có sự cố xảy ra.

