Vận hành hệ thống giám sát toàn diện: Kết hợp Prometheus, Grafana và Loki trong Docker
Trong môi trường DevOps hiện đại, việc chỉ đơn thuần theo dõi các chỉ số CPU hay bộ nhớ thông qua các công cụ truyền thống như Zabbix hay Nagios thường không còn đủ để đáp ứng nhu cầu vận hành các hệ thống Container hóa phức tạp. Một kiến trúc giám sát hoàn chỉnh cần phải giải quyết ba vấn đề cốt lõi: Metrics (đo lường hiệu năng), Logs (nhật ký sự kiện) và Alerts (báo động). Bài viết này sẽ hướng dẫn chi tiết cách xây dựng một cụm giám sát tích hợp giữa Prometheus để thu thập số liệu, Loki để quản lý nhật ký (log), và Grafana để trực quan hóa dữ liệu, tất cả được triển khai trên Docker với cấu hình tối ưu cho sản xuất.
Kiến trúc hệ thống giám sát dựa trên Log và Metrics
Khi nói về giám sát, sự kết hợp giữa Prometheus và Loki được coi là chuẩn mực trong cộng đồng Cloud Native. Prometheus hoạt động như một công cụ thu thập và lưu trữ các số liệu (time-series data) dưới dạng metric, giúp ta nhìn thấy xu hướng tài nguyên và hiệu năng hệ thống. Tuy nhiên, Prometheus không được thiết kế để lưu trữ log văn bản chi tiết do chi phí lưu trữ quá cao. Đây là lúc Loki xuất hiện, hoạt động như một giải pháp thay thế cho Elasticsearch nhưng với cấu trúc nhẹ hơn, tập trung vào việc nhóm log theo các nhãn (labels) giống như cách Prometheus làm với metric.
Mô hình này cho phép chúng ta tương quan (correlate) giữa một sự cố về hiệu năng (ví dụ: CPU cao bất thường trong Prometheus) với nguyên nhân cụ thể (ví dụ: một lỗi NullPointerException trong log Java được lưu ở Loki) ngay trên cùng một bảng điều khiển của Grafana. Việc tích hợp này giúp giảm thời gian trung bình để khắc phục sự cố (MTTR) đáng kể, cho phép kỹ sư hệ thống chuyển từ việc "chữa cháy" sang phân tích nguyên nhân gốc rễ một cách khoa học.
Triển khai cơ sở hạ tầng với Docker Compose
Để bắt đầu, chúng ta sẽ sử dụng Docker Compose để khởi tạo một cụm bao gồm Prometheus, Grafana, Loki, Promtail (client để đọc log), và Alertmanager. Thay vì cấu hình từng dịch vụ thủ công, một file docker-compose.yml duy nhất sẽ đảm bảo tính nhất quán và khả năng tái tạo. Chúng ta cần thiết lập mạng nội bộ (internal network) để các dịch vụ có thể giao tiếp với nhau mà không cần phơi bày ra ngoài, đồng thời cấu hình lưu trữ (volumes) để dữ liệu không bị mất khi container restart.
Dưới đây là ví dụ về file cấu hình docker-compose.yml được tối ưu hóa. Lưu ý rằng chúng ta sẽ sử dụng phiên bản container ổn định và cấu hình mapping cổng (port mapping) phù hợp. Dịch vụ Promtail sẽ chịu trách nhiệm đọc file log từ host hoặc các container khác và đẩy vào Loki, trong khi Prometheus sẽ scrape các endpoint metrics từ các dịch vụ đó.
version: "3.8"
services:
loki:
image: grafana/loki:2.9.4
ports:
- "3100:3100"
volumes:
- ./loki:/loki
command: -config.file=/loki/config.yml
restart: always
promtail:
image: grafana/promtail:2.9.4
volumes:
- /var/log:/var/log
- ./promtail:/etc/promtail
command: -config.file=/etc/promtail/config.yml
depends_on:
- loki
restart: always
prometheus:
image: prom/prometheus:v2.50.0
ports:
- "9090:9090"
volumes:
- ./prometheus:/etc/prometheus
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.enable-lifecycle'
depends_on:
- loki
restart: always
grafana:
image: grafana/grafana:10.2.3
ports:
- "3000:3000"
volumes:
- grafana-storage:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
depends_on:
- prometheus
- loki
restart: always
volumes:
prometheus-data:
grafana-storage:
networks:
monitoring-net:
driver: bridge
Đoạn mã trên thiết lập 4 dịch vụ chính. Dịch vụ Loki chạy trên cổng 3100, trong khi Prometheus chạy trên 9090. Grafana truy cập trên cổng 3000, đây là cổng mặc định và rất quen thuộc. Điểm quan trọng là các volume prometheus-data và grafana-storage được khai báo để đảm bảo dữ liệu được lưu tồn tại trên disk của host, tránh việc dữ liệu bị xóa sạch mỗi khi container bị xóa. Dịch vụ Promtail được mount thư mục /var/log của host vào, giúp nó có thể quét và gửi toàn bộ log hệ thống lên Loki.
Cấu hình chi tiết Prometheus để thu thập Log và Metrics
Sau khi có file docker-compose, bước tiếp theo quan trọng không kém là cấu hình file prometheus.yml. Mặc dù Prometheus chủ yếu dành cho metrics, nhưng trong kiến trúc hiện đại, nó cũng đóng vai trò quan trọng trong việc scrape log từ các ứng dụng thông qua thư viện logfmt hoặc JSON, tuy nhiên ở đây chúng ta sẽ để Promtail lo phần log. Nhiệm vụ của Prometheus là scrape các exporter và các ứng dụng tự động phát ra metric.
Chúng ta cần định nghĩa các scrape_configs để Prometheus biết cần thu thập dữ liệu từ đâu. Cấu hình tiêu chuẩn thường bao gồm việc tự scrape chính nó (prometheus), scrape các service discovery, và quan trọng là cấu hình để nhận dữ liệu từ Loki nếu cần thiết, hoặc đơn giản là tập trung vào metrics. Dưới đây là một cấu hình prometheus.yml cơ bản nhưng đầy đủ tính năng, bao gồm cả việc cấu hình remote_write nếu cần đẩy dữ liệu đi, và định nghĩa các job để scrape các container trong cùng mạng docker.
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
rule_files:
- "alerts/*.yml"
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'loki'
static_configs:
- targets: ['loki:3100']
- job_name: 'grafana'
static_configs:
- targets: ['grafana:3000']
Trong cấu hình trên, ta thấy tham số scrape_interval được đặt là 15 giây, đây là cân bằng tốt giữa độ trễ dữ liệu và tải lên hệ thống. Phần alerting trỏ về dịch vụ Alertmanager (tùy chọn, chưa được triển khai trong docker-compose ở trên nhưng cấu hình sẵn sàng). Các job như node và loki sẽ chủ động đi fetch dữ liệu từ các dịch vụ tương ứng trong mạng nội bộ. Việc đặt localhost:9090 trong job 'prometheus' là cần thiết để Prometheus tự giám sát trạng thái của chính nó.
Cấu hình Promtail để định dạng và gửi Log vào Loki
Khác với Prometheus tập trung vào số liệu, Promtail là một agent nhẹ có nhiệm vụ đọc file log, phân tích định dạng (parse), thêm các nhãn (labels) và gửi đến Loki. Để hệ thống giám sát thực sự hữu ích, các log không nên là những dòng văn bản thô. Chúng cần được gắn thẻ (tag) để dễ dàng tìm kiếm. Ví dụ, chúng ta muốn phân biệt log của ứng dụng Java với log của Nginx, hoặc log ở môi trường Production với Test.
File cấu hình promtail/config.yml đóng vai trò then chốt ở đây. Chúng ta sẽ cấu hình Promtail để đọc các file log từ /var/log và các container, đồng thời sử dụng regex để trích xuất các trường quan trọng như thời gian (timestamp), mức độ nghiêm trọng (level), và thông báo (message). Sau đó, chúng ta gán các labels động dựa trên nội dung của log để Loki có thể index chúng hiệu quả.
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: [__meta_docker_container_name]
target_label: container
regex: '(.*)'
- source_labels: [__meta_docker_container_log_stream]
target_label: stream
regex: '(.*)'
- source_labels: [__meta_docker_container_label_com_docker_compose_project]
target_label: project
regex: '(.*)'
- job_name: varlogs
static_configs:
- targets:
- /var/log
labels:
job: varlogs
__path__: /var/log/*/*.log
relabel_configs:
- source_labels: [__path__]
target_label: log_file
regex: (.*)
- source_labels: [__path__]
target_label: host
regex: '/var/log/(.*)/.*'
- source_labels: [__path__]
target_label: service
regex: '/var/log/([^/]+)/.*'
Đoạn cấu hình trên sử dụng tính năng docker_sd_configs của Promtail, cho phép nó tự động phát hiện các container Docker mới khởi động và bắt đầu log chúng mà không cần cấu hình thủ công. Các rule relabel_configs giúp trích xuất thông tin từ metadata của container Docker (như tên container, stream log stdout/stderr) để gắn vào label container và stream. Điều này cực kỳ quan trọng vì Loki sử dụng các label này để phân nhóm và index log, giúp việc truy vấn sau này cực nhanh. Job thứ hai varlogs thì quét thủ công các file log trên host theo mẫu path, phù hợp cho các ứng dụng chạy ngoài container.
Liên kết và Trực quan hóa trên Grafana
Khi Prometheus và Loki đã chạy ổn định, bước cuối cùng là cấu hình Grafana để trở thành bảng điều khiển tổng đài. Grafana không chỉ hiển thị biểu đồ mà còn cho phép chúng ta tích hợp cả nguồn dữ liệu Metrics và Logs vào một dashboard duy nhất. Bước đầu tiên là truy cập vào địa chỉ http://localhost:3000 (với username admin và password admin như đã định trong biến môi trường). Sau khi đăng nhập, chúng ta cần vào menu Configuration -> Data Sources để thêm nguồn dữ liệu.
Chúng ta sẽ thêm một nguồn dữ liệu mới loại Prometheus với URL là http://prometheus:9090 và một nguồn loại Loki với URL http://loki:3100. Lưu ý rằng bên trong container Grafana, tên service trong docker-compose sẽ được dùng làm tên host thay vì địa chỉ IP. Sau khi lưu, bạn có thể thấy biểu tượng "Health" màu xanh lá cây báo hiệu kết nối thành công. Tại thời điểm này, bạn có thể lưu các Dashboard mẫu (Import) từ thư viện cộng đồng của Grafana bằng cách nhập ID của dashboard (ví dụ: ID 3262 cho Node Exporter full, ID 4388 cho Prometheus, ID 4576 cho Loki) để nhanh chóng có được các biểu đồ có sẵn.
Tuy nhiên, giá trị thực sự nằm ở việc tạo dashboard tự tạo để tương quan dữ liệu. Ví dụ, bạn có thể tạo một bảng điều khiển cho một microservice cụ thể. Trên cùng một dashboard, bạn vẽ một biểu đồ đường (Time series) từ Prometheus hiển thị http_requests_total và latency. Ngay bên dưới biểu đồ đó, bạn chèn một bảng hiển thị log (Logs panel) từ Loki với query lọc {container="my-service-name"}. Khi bạn click vào một peak (đỉnh) trên biểu đồ metrics, Grafana có khả năng tự động filter log trong bảng bên dưới để chỉ hiển thị các log trong khoảng thời gian đó. Tính năng Linked Panels này giúp kỹ sư ngay lập tức thấy được liệu một đợt tăng đột biến về lỗi (500 error) trong metrics có tương ứng với các dòng log ERROR hay Exception nào không, từ đó rút ngắn thời gian phân tích hàng giờ xuống còn vài phút.
Kết luận và lời khuyên vận hành
Việc thiết lập một hệ thống giám sát tích hợp Prometheus, Loki và Grafana không chỉ là cài đặt các công cụ riêng lẻ mà là xây dựng một văn hóa quan sát (Observability) cho tổ chức. Với kiến trúc này, bạn đã có được khả năng nhìn thấy toàn cảnh về sức khỏe hệ thống từ góc độ hiệu năng và nhật ký sự kiện. Điều quan trọng cần nhớ là quy mô lưu trữ: Prometheus có thể tiêu tốn nhiều RAM và disk cho các metric, trong khi Loki cần dung lượng disk lớn cho log. Do đó, trong môi trường production thực tế, bạn nên cân nhắc cấu hình Retention Policy (chính sách giữ dữ liệu) trong cả Prometheus và Loki để tự động xóa các dữ liệu cũ không còn cần thiết, hoặc mở rộng sang các giải pháp lưu trữ phân tán như Thanos (cho Prometheus) hoặc Cassandra/S3 (cho Loki).
Hơn nữa, đừng quên thiết lập các cảnh báo (Alerting) thực sự hữu ích. Thay vì báo động khi CPU > 80%, hãy thiết lập alert dựa trên tỷ lệ lỗi (error rate) hoặc độ trễ (latency) vượt quá ngưỡng SLA của bạn. Kết hợp Alertmanager vào kiến trúc để gửi thông báo qua Email, Slack, hoặc PagerDuty sẽ đảm bảo rằng bạn luôn được thông báo khi có sự cố nghiêm trọng xảy ra. Bằng cách áp dụng các cấu hình chi tiết được nêu ở trên, bạn đang đặt nền móng vững chắc cho một hệ thống DevOps bền vững, linh hoạt và dễ dàng bảo trì trong tương lai.