Observability 2 — Kiến trúc Prometheus

13 thg 7, 2026 3 lượt xem
#time-series
#devops
#prometheus
#scraping
#exporters

Prometheus là gì

Prometheus là hệ thống giám sát (monitoring) và cơ sở dữ liệu chuỗi thời gian (TSDB — time-series database) mã nguồn mở, ra đời tại SoundCloud năm 2012, sau đó trở thành dự án tốt nghiệp (graduated) thứ hai của CNCF (Cloud Native Computing Foundation) — chỉ sau Kubernetes. Ngày nay nó là chuẩn de facto cho mảng metrics trong hệ sinh thái cloud-native: định dạng phơi metric của nó (OpenMetrics/Prometheus exposition format) được hàng nghìn phần mềm hỗ trợ sẵn.

bài 1 ta đã thấy metrics là một trong ba trụ cột của observability. Prometheus chính là công cụ đại diện cho trụ cột metrics: nó thu thập số liệu đo dạng chuỗi thời gian, lưu trữ nén hiệu quả, và cho phép truy vấn/cảnh báo bằng ngôn ngữ PromQL (sẽ đi sâu ở bài 3).

Điểm cần nhớ ngay: Prometheus không là hệ thống lưu log hay tracing. Nó chuyên về số liệu định lượng theo thời gian — độ trễ, số request, tỷ lệ lỗi, mức dùng CPU/RAM. Nó cũng không thiết kế để làm nguồn dữ liệu chính xác 100% cho mục đích billing; đây là công cụ vận hành, ưu tiên tính sẵn sàng và tốc độ hơn là độ chính xác tuyệt đối từng bản ghi.

Kiến trúc tổng thể

Trái tim của hệ thống là Prometheus server — một binary Go duy nhất, không phụ thuộc runtime bên ngoài, làm ba việc chính:

  1. Scrape (kéo) metrics từ các target qua HTTP theo chu kỳ.
  2. Lưu trữ dữ liệu vào TSDB cục bộ trên đĩa.
  3. Đánh giá rule (recording rule + alerting rule) định kỳ, và đẩy cảnh báo sang Alertmanager.

Xung quanh server là các thành phần vệ tinh: exporters (biến hệ thống thành endpoint /metrics), service discovery (tự phát hiện target), Pushgateway (cho batch job), Alertmanager (nhóm/định tuyến/khử trùng lặp cảnh báo), và tầng truy vấn phía trên (Grafana, hoặc web UI/API tích hợp).

Mô hình PULL: vì sao Prometheus tự đi kéo

Đây là quyết định kiến trúc quan trọng nhất của Prometheus. Thay vì để mỗi ứng dụng push (đẩy) số liệu lên server giám sát, Prometheus pull — chủ động gửi HTTP GET đến đường dẫn /metrics của từng target theo chu kỳ (mặc định 15–60 giây) và đọc toàn bộ ảnh chụp metric tại thời điểm đó.

Endpoint /metrics chỉ là một trang text thuần, ví dụ minh hoạ (không phải SQL, không chạy được):

# HELP http_requests_total Tổng số HTTP request đã xử lý
# TYPE http_requests_total counter
http_requests_total{method="POST",handler="/transfer",status="200"} 18423
http_requests_total{method="POST",handler="/transfer",status="500"} 12
# HELP process_resident_memory_bytes Bộ nhớ RSS tiến trình
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 5.242e+08

Ưu điểm của pull trong bối cảnh giám sát cụm server/dịch vụ ngân hàng:

  • Phát hiện "target chết" miễn phí. Nếu scrape thất bại (timeout, connection refused), Prometheus tự sinh ra metric up == 0 cho target đó. Với mô hình push, một service ngừng đẩy có thể do nó chết hoặc do mạng — khó phân biệt.
  • Không cần cấu hình ngược ở phía app. App chỉ cần phơi /metrics; nó không cần biết địa chỉ Prometheus, không cần credential để đẩy. Việc thêm/bớt server giám sát không đụng tới app.
  • Kiểm soát tải tập trung. Server tự quyết scrape interval và số target, tránh việc hàng trăm client cùng dồn dữ liệu vào (thundering herd).
  • Dễ debug thủ công. Kỹ sư vận hành chỉ cần curl http://host:9100/metrics là thấy đúng dữ liệu Prometheus nhìn thấy.

Khi nào pull không đủ: Pushgateway

Mô hình pull giả định target sống đủ lâu để bị kéo. Điều này sai với batch job / job ngắn hạn — ví dụ tác vụ chạy cuối ngày (end-of-day, EOD) đối chiếu giao dịch, chạy 40 giây rồi tắt. Prometheus có thể chẳng kịp scrape lần nào.

Giải pháp là Pushgateway: job chủ động push metric của mình lên Pushgateway trước khi kết thúc; Pushgateway giữ giá trị đó và Prometheus pull từ Pushgateway như một target bình thường.

# Job EOD đẩy kết quả sau khi chạy xong (minh hoạ dòng lệnh, không phải SQL)
echo 'eod_reconcile_records_total 1284502
eod_reconcile_last_success_timestamp_seconds 1719734400' \
  | curl --data-binary @- http://pushgateway:9091/metrics/job/eod_reconcile

Lưu ý dùng đúng chỗ: Pushgateway chỉ dành cho job ngắn hạn không thể bị scrape. Đừng lạm dụng nó làm nơi tập trung metric cho service thường trực — vì Pushgateway không tự xóa metric cũ (metric "dính" lại đến khi bị ghi đè hoặc xóa thủ công), làm mất khả năng phát hiện target chết và biến nó thành single point of failure.

Mô hình dữ liệu: time series, labels, samples

Prometheus lưu mọi thứ dưới dạng time series (chuỗi thời gian). Mỗi chuỗi được định danh duy nhất bởi:

  • Metric name — tên metric, ví dụ http_requests_total.
  • Tập labels — các cặp key="value" mô tả chiều (dimension) của metric, ví dụ method="POST", status="500", instance="10.0.3.21:9100".

Về mặt kỹ thuật, metric name chỉ là một label đặc biệt tên __name__. Nghĩa là hai dòng sau là hai chuỗi khác nhau, dù cùng metric name:

http_requests_total{handler="/transfer", status="200"}   → chuỗi A
http_requests_total{handler="/transfer", status="500"}   → chuỗi B

Mỗi chuỗi là một dãy các sample — cặp (timestamp, value), trong đó value là số thực float64 và timestamp có độ phân giải mili-giây. Trực quan hoá dạng bảng:

Chuỗi (định danh)timestampvalue
http_requests_total{...status="200"}10:00:0018400
http_requests_total{...status="200"}10:00:1518423
http_requests_total{...status="500"}10:00:1512

Prometheus có bốn kiểu metric cơ bản (sẽ instrument chi tiết ở bài 4):

  • Counter — bộ đếm chỉ tăng (hoặc reset về 0 khi restart), ví dụ tổng số giao dịch.
  • Gauge — giá trị lên xuống tùy ý, ví dụ số kết nối DB đang mở, độ trễ hàng đợi.
  • Histogram — phân phối theo các bucket, ví dụ độ trễ request; dùng để tính quantile xấp xỉ.
  • Summary — tương tự histogram nhưng tính quantile phía client.

Cảnh báo về cardinality (lực lượng chuỗi): số chuỗi = tích số giá trị của các label. Nếu bạn đưa một label có giá trị vô hạn — như transaction_id, customer_id hay session_id — số chuỗi bùng nổ, làm cạn RAM và giết chết Prometheus. Đây là lỗi phổ biến và nguy hiểm nhất khi thiết kế metric.

TSDB cục bộ: block, WAL, retention

Prometheus tự lưu dữ liệu vào TSDB local trên đĩa (thư mục --storage.tsdb.path, mặc định data/), không cần cơ sở dữ liệu ngoài. Cách hoạt động:

  • Head block (trong RAM). Sample mới nhất được gom trong bộ nhớ, tổ chức theo cửa sổ 2 giờ.
  • WAL (Write-Ahead Log). Trước khi vào RAM, mọi sample được ghi tuần tự vào WAL trên đĩa. Nếu server crash, Prometheus replay WAL lúc khởi động để không mất dữ liệu chưa kịp lưu thành block.
  • Persistent block. Sau mỗi ~2 giờ, head block được nén (compact) và ghi thành một block bất biến trên đĩa (một thư mục chứa chunk dữ liệu + index + metadata). Về sau các block nhỏ được nén dần thành block lớn hơn để giảm số file.
  • Retention (thời gian lưu). Cấu hình qua --storage.tsdb.retention.time (mặc định 15 ngày) hoặc --storage.tsdb.retention.size. Block cũ hơn ngưỡng bị xóa.

Vì sao Prometheus thường không giữ dữ liệu dài hạn

TSDB local gắn liền với một node đơn: không có replication, không sharding tích hợp, và dung lượng bị giới hạn bởi đĩa của chính máy đó. Vì thế Prometheus được thiết kế cho cửa sổ gần (vài ngày đến vài tuần) — đủ để điều tra sự cố nóng, không phải để lưu trữ nhiều năm phục vụ báo cáo tuân thủ.

Trong ngân hàng, nhu cầu giữ metrics hàng năm (audit, phân tích xu hướng dung lượng, so sánh năm trước) là có thật. Lời giải không phải là tăng retention lên vô hạn (rủi ro, tốn kém, không HA), mà là dùng tầng long-term storage như Thanos hoặc Mimir — chúng đọc block từ Prometheus, đẩy lên object storage (S3/MinIO), khử trùng lặp giữa các bản HA, và cho phép query gộp toàn cụm. Kiến trúc production này là chủ đề của bài 8.

Service Discovery: tự phát hiện target

Trong hệ thống động — pod Kubernetes sinh/tắt liên tục, node auto-scaling — việc liệt kê tay từng địa chỉ IP là bất khả thi. Service discovery (SD) để Prometheus tự tìm ra target đang tồn tại. Các cơ chế SD phổ biến:

Cơ chếCách hoạt độngPhù hợp với
static_configsLiệt kê cứng danh sách host:port trong file cấu hìnhSố target ít, ổn định (vài server core)
file_sd_configsĐọc danh sách target từ file JSON/YAML; file này do công cụ khác sinh ra và Prometheus tự reload khi file đổiTích hợp với hệ thống quản lý cấu hình / CMDB nội bộ
kubernetes_sd_configsGọi Kubernetes API để lấy danh sách pod/service/endpoint/node theo thời gian thựcCụm K8s (xem k8s-08)
consul_sd_configsHỏi Consul catalog để lấy service đã đăng kýHạ tầng dùng Consul làm service registry

Điểm mạnh: khi một pod mới lên, SD tự thêm nó vào danh sách scrape; khi pod tắt, nó tự bị loại — không cần restart Prometheus, không cần sửa cấu hình tay.

Exporters: biến hệ thống thành /metrics

Không phải phần mềm nào cũng tự phơi /metrics theo định dạng Prometheus. Exporter là tiến trình cầu nối: nó đọc trạng thái từ một hệ thống (qua log, API, giao thức riêng) rồi dịch sang metric Prometheus và phơi ra một cổng HTTP.

Các exporter thường gặp trong hạ tầng ngân hàng:

  • node_exporter — metric hệ điều hành của server Linux: CPU, RAM, đĩa, network, load average. Chạy trên mọi node (thường cổng 9100). Đây là exporter phổ biến nhất; nó phơi các chỉ số tương ứng với những gì bạn xem bằng top/iostat (liên hệ linux-06).
  • postgres_exporter — kết nối vào PostgreSQL, chạy truy vấn hệ thống và phơi số liệu về connection, replication lag, kích thước DB, độ dài transaction.
  • Các exporter hạ tầng khác: redis_exporter, kafka_exporter, blackbox_exporter (probe HTTP/TCP/ICMP để kiểm tra tính sẵn sàng từ bên ngoài), exporter cho phần cứng qua SNMP.

Với ứng dụng do team tự viết, thay vì exporter, bạn instrument trực tiếp bằng client library (Java/Go/Python…) để app tự phơi /metrics — nội dung của bài 4.

Cấu hình prometheus.yml

Toàn bộ hành vi scrape được khai báo trong prometheus.yml. Cấu trúc cốt lõi (minh hoạ, không chạy được):

global:
  scrape_interval: 15s        # chu kỳ scrape mặc định
  evaluation_interval: 15s    # chu kỳ đánh giá rule
  external_labels:
    cluster: core-banking-hn  # label gắn vào mọi series khi gửi ra ngoài (Thanos, remote_write)

scrape_configs:
  # Job giám sát server Linux qua node_exporter
  - job_name: 'node'
    static_configs:
      - targets:
          - '10.0.3.21:9100'
          - '10.0.3.22:9100'
        labels:
          env: 'prod'
          role: 'app-server'

  # Job giám sát DB core, scrape thưa hơn
  - job_name: 'postgres'
    scrape_interval: 30s
    static_configs:
      - targets: ['10.0.5.10:9187']

  # Tự phát hiện pod trong cụm K8s
  - job_name: 'k8s-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # chỉ scrape pod có annotation prometheus.io/scrape=true
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      # dùng label app của pod làm giá trị label 'service'
      - source_labels: [__meta_kubernetes_pod_label_app]
        target_label: service

Các thành phần cần nắm:

  • job_name — nhóm logic các target cùng loại; giá trị này thành label job trên mọi series thu được.
  • targets — danh sách endpoint host:port (khi dùng static). Prometheus mặc định ghép đường dẫn /metrics và scheme http; đổi được qua metrics_pathscheme.
  • scrape_interval — có thể ghi đè per-job. DB có thể scrape 30s thay vì 15s để giảm tải.
  • relabeling — bước biến đổi label trước khi lưu, thực hiện bởi các relabel_configs. Ba action hay dùng: keep/drop (lọc target theo điều kiện), và replace (đặt/đổi giá trị một label). Trong ví dụ trên, relabeling lọc chỉ giữ pod được đánh dấu scrape và ánh xạ label Kubernetes thành label metric gọn hơn. Đây là "van điều tiết" chính để kiểm soát cardinality ngay từ nguồn.

Scrape interval và độ phân giải

Scrape interval quyết định độ phân giải (resolution) dữ liệu — khoảng cách giữa hai điểm liên tiếp trên đồ thị. Đây là đánh đổi trực tiếp:

  • Interval ngắn (ví dụ 5s): đồ thị mượt, bắt được gai (spike) ngắn, phát hiện sự cố nhanh — nhưng sinh nhiều sample hơn, tốn RAM/đĩa/CPU và tăng số chuỗi phải xử lý.
  • Interval dài (ví dụ 60s): tiết kiệm tài nguyên — nhưng một gai lỗi kéo dài 10 giây có thể lọt hoàn toàn giữa hai lần scrape, không để lại dấu vết.

Nguyên tắc thực dụng: chọn interval đủ nhỏ để bắt được sự kiện bạn quan tâm, thường 15s cho phần lớn hệ thống. Hàm tốc độ như rate() trong PromQL cần cửa sổ tính ít nhất bằng 4 lần scrape interval để chịu được việc mất một-hai lần scrape mà vẫn cho kết quả ổn định — chi tiết ở bài 3.

Use case thực tế

Bối cảnh: Team hạ tầng NCB cần giám sát 40 server ứng dụng và 6 node PostgreSQL của hệ core banding, đồng thời theo dõi các job cuối ngày (EOD).

Cách triển khai:

  1. Cài node_exporter (cổng 9100) trên cả 40 server ứng dụng và 6 node DB; cài thêm postgres_exporter (cổng 9187) cạnh mỗi node PostgreSQL.
  2. Trong prometheus.yml, tạo hai job_name: node (46 target) và postgres (6 target). Job node để scrape_interval: 15s; job postgres để 30s vì truy vấn hệ thống DB nặng hơn.
  3. Job EOD reconcile chạy lúc 22:00 mỗi ngày, kéo dài ~40 giây rồi tắt — quá ngắn để bị scrape. Nên job này push hai metric (eod_reconcile_records_total, eod_reconcile_last_success_timestamp_seconds) lên Pushgateway ngay trước khi kết thúc; Prometheus scrape Pushgateway bình thường.
  4. Kết quả cardinality: node_exporter phơi ~1.000 series/target → 46 target ≈ 46.000 series; postgres_exporter ~400 series/target → 6 target ≈ 2.400 series. Tổng ~48.000 series — một Prometheus đơn xử lý thừa sức (một node bình thường chịu được hàng triệu series).
  5. Dung lượng đĩa: với ~15 byte/sample sau nén và scrape 15s, ước tính ≈ 48.000 series × 4 sample/phút × 60 × 24 × 15 byte ≈ ~4 GB/ngày; retention 15 ngày → cần ~60 GB đĩa cho TSDB local. Muốn giữ 1 năm cho audit → đẩy sang Thanos + object storage thay vì phình retention.
  6. Phát hiện sự cố: khi một server treo, scrape thất bại → metric up{job="node",instance="..."} == 0; rule cảnh báo up == 0 for 2m firing → Prometheus gửi Alertmanager → team nhận cảnh báo, dù server đó không kịp gửi bất kỳ tín hiệu chủ động nào.

Ghi nhớ

  • Prometheus = monitoring + TSDB mã nguồn mở, chuẩn de facto của CNCF cho metrics (không phải logs/traces).
  • Kiến trúc: một server Go duy nhất làm ba việc — scrape, lưu TSDB, đánh giá rule; quanh nó là exporters, SD, Pushgateway, Alertmanager.
  • Mô hình PULL: server chủ động GET /metrics. Lợi ích lớn nhất là tự có metric up để phát hiện target chết. Pushgateway chỉ dành cho batch/job ngắn hạn.
  • Data model: mỗi time series = metric name + tập labels key="value"; dữ liệu là các sample (timestamp, value float64). Metric name thực chất là label __name__.
  • Coi chừng cardinality: đừng bao giờ đặt ID/giá trị vô hạn làm label — bùng nổ chuỗi, giết Prometheus.
  • TSDB local: WAL chống mất dữ liệu → head block trong RAM → nén thành block bất biến; retention mặc định 15 ngày. Không HA, không sharding → không giữ dài hạn → dùng Thanos/Mimir cho lưu trữ nhiều năm.
  • Service discovery (static, file_sd, kubernetes_sd, consul) giúp tự phát hiện target trong môi trường động — không cần sửa cấu hình tay khi target sinh/tắt.
  • Exporters (node_exporter, postgres_exporter…) dịch hệ thống thành endpoint /metrics; app tự viết thì instrument trực tiếp.
  • prometheus.yml: scrape_configs gồm job_name, targets, scrape_interval; relabeling là van điều tiết target và cardinality ngay từ nguồn.
  • Scrape interval = độ phân giải: đủ nhỏ để bắt sự kiện quan tâm (thường 15s); hàm rate() cần cửa sổ ≥ 4× interval.

Bài viết liên quan

Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.

13 thg 7, 2026 4

Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.

13 thg 7, 2026 3

Vì sao cần orchestration; Pod, Deployment, Service, Ingress; scaling, self-healing và cấu hình.

13 thg 7, 2026 3

Bản chất DevOps: phá bỏ rào cản Dev–Ops, vòng lặp vô tận, CALMS, và vì sao tự động hoá.

13 thg 7, 2026 3