Chiến lược Debug khi mất dữ liệu: Kiểm tra pipeline từ ứng dụng đến Dashboard
Khi dữ liệu biến mất trên Dashboard Grafana, nguyên nhân thường nằm ở một trong ba điểm: ứng dụng không xuất dữ liệu, collector không nhận được dữ liệu, hoặc lưu trữ (Storage) bị lỗi.
Chúng ta sẽ sử dụng chiến lược "Chia để trị" (Divide and Conquer) bằng cách kiểm tra từng đoạn của pipeline: Application -> OpenTelemetry Collector -> Backend (Prometheus/Loki) -> Grafana.
Bước 1: Xác minh dữ liệu tại điểm xuất (Application Level)
Trước khi kiểm tra hệ thống quan sát, hãy đảm bảo ứng dụng thực sự đang tạo ra metric hoặc log. Chúng ta sẽ kích hoạt một request mẫu và kiểm tra stdout hoặc file log cục bộ.
Thực hiện lệnh sau để tạo traffic giả lập và theo dõi log thời gian thực trên container ứng dụng:
kubectl exec -it -n -- tail -f /var/log/app.log | grep "request-id"
Đồng thời, kích thích ứng dụng tạo dữ liệu từ terminal ngoài:
curl -H "X-Request-ID: debug-session-01" http:///api/health
Kết quả mong đợi: Bạn phải thấy dòng log chứa "debug-session-01" xuất hiện ngay lập tức trên terminal. Nếu không thấy, lỗi nằm ở code ứng dụng hoặc cấu hình logging bên trong app.
Bước 2: Kiểm tra điểm thu thập (Collector Level)
Nếu ứng dụng đã xuất dữ liệu nhưng Collector (OpenTelemetry) không nhận, hãy kiểm tra trạng thái của Collector. Chúng ta cần xem logs của container Collector để tìm lỗi kết nối hoặc định dạng.
kubectl logs -f -n --tail=100
Để debug sâu hơn, hãy cấu hình Collector ở chế độ debug bằng cách thêm biến môi trường `OTEL_LOG_LEVEL=debug` trong file Deployment của Collector, sau đó restart pod.
Tuy nhiên, cách nhanh nhất để xác nhận Collector đã nhận được dữ liệu mà chưa gửi đi là kiểm tra trạng thái của Exporter trong file cấu hình Collector. Nếu bạn dùng gRPC, hãy kiểm tra port kết nối.
netstat -tulpn | grep 4317
Kết quả mong đợi: Trong logs của Collector, bạn sẽ thấy các dòng ghi nhận "Received span" hoặc "Received metric" kèm theo ID request, nhưng có thể có lỗi "connection refused" hoặc "unavailable" nếu Backend (Prometheus/Loki) đang tắt hoặc bị chặn firewall.
Bước 3: Kiểm tra điểm lưu trữ (Backend Level)
Nếu Collector đã nhận dữ liệu, bước tiếp theo là kiểm tra xem Prometheus hoặc Loki đã nhận và lưu chưa. Đây là điểm thường bị lỗi do cấu hình retention, storage full, hoặc rule match không đúng.
Đối với Prometheus, truy cập vào endpoint `/api/v1/targets` hoặc xem logs của Prometheus:
curl -s http://:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job=="my-app") | .health'
Đối với Loki, kiểm tra logs thô trực tiếp qua API Loki (bypass Grafana) để xem dữ liệu có tồn tại không:
curl -s 'http://:3100/loki/api/v1/query_range?query={app="my-app"}&direction=backward&limit=10&end=now&start=1h' | jq '.data.result[0].values'
Kết quả mong đợi:
- Prometheus: Trả về trạng thái "up" và số lượng series > 0.
- Loki: Trả về mảng JSON chứa các dòng log gần nhất. Nếu trả về mảng rỗng `[]` dù Collector đã nhận, lỗi nằm ở cấu hình LogQL hoặc pipeline Loki (ingestion pipeline).
Bước 4: Kiểm tra điểm hiển thị (Grafana Level)
Đảm bảo dữ liệu đã đến Backend, nhưng Grafana không hiển thị. Nguyên nhân thường là do Data Source bị disconnect hoặc Query bị sai cú pháp.
Truy cập vào Settings -> Data Sources của Grafana, chọn Prometheus hoặc Loki và nhấn "Save & Test".
Để debug query trực tiếp trong Grafana, hãy mở Dashboard, vào Edit Mode, chọn Panel, và xem tab "Query". Sử dụng tính năng "Inspect" (biểu tượng kính lúp) -> "Query Inspector" để xem query thực tế đã gửi đi là gì và response trả về ra sao.
Kết quả mong đợi: Grafana hiển thị "Data source is working". Trong tab Query Inspector, bạn thấy query đã được gửi và response có dữ liệu (không phải empty array). Nếu query trả về dữ liệu nhưng panel không vẽ biểu đồ, kiểm tra phần "Transformations" hoặc "Visualization" của panel.
Giải quyết sự cố Prometheus không phát hiện Target và Loki bị lỗi Parse Log
Hai vấn đề kinh điển trong môi trường Hybrid Cloud là Prometheus không thấy target (discovery failure) và Loki không parse được log (parsing error). Dưới đây là quy trình xử lý cụ thể.
Xử lý Prometheus không phát hiện Target
Nguyên nhân phổ biến: Service Discovery (SD) không khớp label, network policy chặn port 9090, hoặc target không expose endpoint `/metrics`.
Đầu tiên, kiểm tra xem Prometheus có thể resolve được tên service không:
dig ..svc.cluster.local
Nếu resolve được, kiểm tra xem target có expose port 9090 không và có network policy nào chặn không. Sau đó, vào file cấu hình `prometheus.yml` để debug.
Tạm thời, hãy sửa file cấu hình để thêm `static_configs` thay vì dùng `kubernetes_sd_configs` để ép Prometheus scan một pod cụ thể, xác nhận lỗi có phải do discovery hay không.
File cấu hình: `/etc/prometheus/prometheus.yml`
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'debug-static-target'
static_configs:
- targets: [':8080']
labels:
environment: 'debug'
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- action: keep
regex: true
source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
Reload cấu hình Prometheus mà không cần restart:
curl -X POST http://:9090/-/reload
Kết quả mong đợi: Vào trang UI Prometheus -> Status -> Targets. Job "debug-static-target" phải hiện trạng thái "UP". Nếu "debug-static-target" là UP nhưng "kubernetes-pods" là DOWN, thì lỗi nằm ở annotation của pod hoặc cấu hình relabel trong `kubernetes_sd_configs`.
Xử lý Loki bị lỗi Parse Log (Malformed Log)
Khi Loki báo lỗi parse, thường là do log không tuân thủ định dạng được cấu hình trong `pipeline_stages`. Điều này khiến cả một batch log bị loại bỏ.
Để xem lỗi cụ thể, kiểm tra logs của Loki (hoặc Promtail nếu dùng agent model):
kubectl logs -f -n | grep "parse error"
Giải pháp là cấu hình lại `pipeline_stages` trong file cấu hình Loki để xử lý các dòng log bất thường hoặc chuyển sang chế độ raw.
File cấu hình: `/etc/loki/loki-config.yaml` (hoặc trong `values.yaml` nếu dùng Helm)
auth_enabled: false
server:
http_listen_port: 3100
ingester:
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
chunk_idle_period: 1h
max_chunk_age: 1h
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/index
shared_store: s3
filesystem:
directory: /loki/chunks
ruler:
storage:
type: local
local:
directory: /loki/rules
rule_path: /loki/rules-temp
limits_config:
ingestion_rate_mb: 16
ingestion_burst_size_mb: 24
max_entry_size: 256KB
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
# Cấu hình Pipeline để xử lý lỗi parse
logql:
engine:
# Bật chế độ bỏ qua dòng lỗi parse thay vì reject cả batch
max_line_size: 1MB
max_line_length: 1000000
# Nếu dùng Promtail, cấu hình pipeline tại đây
# positions:
# filename: /tmp/positions.yaml
# clients:
# - url: http://loki:3100/loki/api/v1/push
# scrape_configs:
# - job_name: syslogs
# static_configs:
# - targets:
# - localhost
# labels:
# __path__: /var/log/syslog
# pipeline_stages:
# - json:
# expressions:
# level: level
# message: message
# # Cấu hình fallback nếu parse JSON thất bại
# # (Tùy thuộc vào version, cần cấu hình đúng để không drop log)
# - output:
# format: json
Kết quả mong đợi: Loki không còn báo lỗi parse liên tục. Các dòng log bị lỗi định dạng có thể sẽ bị chuyển thành dạng raw hoặc bị bỏ qua tùy cấu hình, nhưng không làm gián đoạn luồng log của các dòng khác.
Giải quyết vấn đề Trace bị gãy (Broken Context) giữa các Microservices
Vấn đề "Broken Trace" xảy ra khi context propagation (trace ID) không được truyền từ service A sang service B. Kết quả là bạn thấy nhiều trace rời rạc thay vì một dòng chảy duy nhất.
Bước 1: Xác định điểm gãy vỡ Context
Trong Grafana Tempo/Jaeger, tìm một trace bị gãy. Bạn sẽ thấy hai span có cùng `parent_span_id` nhưng thuộc hai `trace_id` khác nhau. Điều này nghĩa là service con đã tạo một trace mới thay vì tiếp tục trace cha.
Kiểm tra headers HTTP trong request giữa hai service. Trace context được truyền qua các header chuẩn W3C:
traceparent: Chứa trace ID, span ID, và flags.
tracestate: Chứa các tham số vendor-specific.
Sử dụng `tcpdump` hoặc `curl -v` trên service nguồn để xem headers có được gửi đi không:
curl -v http://:8080/api/data 2>&1 | grep -i "traceparent"
Kết quả mong đợi: Nếu không thấy header `traceparent` trong request, lỗi nằm ở phía service nguồn (client) chưa inject context.
Bước 2: Sửa lỗi tại Service Downstream (Receiver)
Nếu header đã được gửi nhưng service đích không nhận, có thể do library OpenTelemetry chưa được cấu hình đúng để extract context, hoặc middleware (Load Balancer/Proxy) đã strip đi headers.
Đảm bảo trong code của service đích (ví dụ: Node.js, Go, Java), bạn đã khởi tạo SDK với `Propagator` chuẩn W3C.
Ví dụ cấu hình cho Node.js (OpenTelemetry):
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { W3CTraceContextPropagator } = require('@opentelemetry/core');
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
textMapPropagator: new W3CTraceContextPropagator(), // BẮT BUỘC
});
sdk.start();
Kết quả mong đợi: Sau khi deploy lại, trace ID trong log của service đích phải trùng với trace ID của service nguồn.
Bước 3: Xử lý trường hợp có Proxy/Load Balancer trung gian
Trong môi trường Hybrid Cloud, thường có Nginx, HAProxy, hoặc Cloud Load Balancer nằm giữa. Các proxy này mặc định có thể xóa headers custom hoặc headers mới.
Đảm bảo proxy được cấu hình để preserve headers `traceparent` và `tracestate`.
File cấu hình Nginx: `/etc/nginx/nginx.conf`
http {
# Đảm bảo proxy giữ nguyên các header quan trọng
proxy_set_header traceparent $http_traceparent;
proxy_set_header tracestate $http_tracestate;
# Hoặc sử dụng proxy_pass với bảo toàn headers
server {
listen 80;
location / {
proxy_pass http://backend_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Không xóa headers trace
proxy_ignore_headers X-Accel-Redirect X-Content-Type-Options;
}
}
}
Reload Nginx:
nginx -t && systemctl reload nginx
Kết quả mong đợi: Khi inspect request ở service đích, bạn thấy header `traceparent` vẫn tồn tại với giá trị nguyên vẹn.
Best Practices và Checklist khi mở rộng hệ thống Observability cho quy mô lớn
Khi hệ thống mở rộng từ vài chục lên hàng nghìn service, chi phí và độ phức tạp tăng theo cấp số nhân. Dưới đây là các nguyên tắc vàng và checklist để đảm bảo hệ thống bền vững.
1. Chiến lược Sampling (Mẫu hóa dữ liệu)
Không thể lưu 100% trace trong môi trường production quy mô lớn. Hãy áp dụng sampling thông minh.
- Head-based Sampling: Quyết định tại đầu vào (Collector) dựa trên tỷ lệ % cố định (ví dụ: 1%). Nhược điểm: Có thể mất các lỗi hiếm gặp.
- Tail-based Sampling: Giữ lại trace có lỗi (status_code != 200) hoặc latency cao. Đây là best practice cho production.
Cấu hình Tail Sampling trong OpenTelemetry Collector (file: `/etc/otel/collector-config.yaml`):
extensions:
health_check:
processors:
batch:
tail_sampling:
policies:
- name: error-policy
type: status_code
status_code:
status_codes: [ERROR]
- name: latency-policy
type: latency
latency:
threshold_ms: 1000
- name: probabilistic-policy
type: probabilistic
from_attributes:
- name: http.status_code
value: 200
probabilistic:
sampling_percentage: 1
exporters:
otlp/tempo:
endpoint: tempo:4317
logging:
verbosity: detailed
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [tail_sampling, batch]
exporters: [otlp/tempo]
Kết quả mong đợi: Chỉ các trace có lỗi hoặc latency cao được lưu vào Tempo. Các trace 200 OK thông thường chỉ được lưu 1% để có cái nhìn tổng quan mà không tốn storage.
2. Quản lý chi phí Storage (Retention Policy)
Chi phí lớn nhất là lưu trữ Log và Trace. Cần thiết lập chính sách retention rõ ràng.
- Metric: Lưu dài hạn (30-90 ngày) với resolution thấp (15s-1m).
- Log: Lưu ngắn hạn (7-14 ngày) cho debug, lưu dạng aggregated (stats) cho dài hạn.
- Trace: Lưu rất ngắn hạn (3-7 ngày) trừ khi có incident.
Cấu hình Retention trong Loki (file: `/etc/loki/loki-config.yaml`):
limits_config:
# Giới hạn kích thước entry log
max_entry_size: 256KB
# Giới hạn số log mỗi ngày
max_bytes_per_timestamp: 1024
# Thời gian giữ log (Tùy chỉnh theo nhu cầu)
retention_period: 72h # 3 ngày
storage_config:
boltdb_shipper:
shared_store: s3
ring:
kvstore:
store: inmemory
active_index_directory: /loki/index
# Cấu hình xóa index cũ để tiết kiệm
cache_location: /loki/index-cache
cache_ttl: 24h
Kết quả mong đợi: Các log cũ hơn 3 ngày sẽ bị tự động xóa khỏi storage, giảm đáng kể chi phí S3/Bucket và dung lượng disk.
3. Checklist trước khi Deploy lên Production
Trước khi đưa hệ thống observability vào môi trường Hybrid Cloud thực tế, hãy kiểm tra các điểm sau:
- Security: Đã bật mTLS giữa Collector và Backend chưa? Đã cấu hình RBAC cho Grafana chưa?
- Network: Có firewall rule nào chặn port 9090, 3100, 4317 chưa? Network Policy trong K8s đã cho phép traffic từ Pod App -> Collector chưa?
- High Availability: Prometheus/Loki có được deploy dưới dạng StatefulSet với replica > 1 chưa? Có cơ chế sharding chưa?
- Alerting: Đã có alert cho chính hệ thống observability chưa (ví dụ: "Prometheus Target Down", "Loki Ingestion Lag")?
- Resource: Đã set request/limit CPU/RAM cho Collector và Backend chưa để tránh OOMKilled?
Để kiểm tra alert cho chính hệ thống observability, thêm rule vào file alerting của Prometheus (`/etc/prometheus/rules/alerts.yaml`):
groups:
- name: observability-health
rules:
- alert: PrometheusTargetMissing
expr: up == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Target {{ $labels.job }} is down"
- alert: LokiIngestionHighLatency
expr: histogram_quantile(0.99, rate(loki_ingester_ingestion_bytes_per_second[5m])) > 1000000
for: 10m
labels:
severity: warning
annotations:
summary: "Loki ingestion is slow"
Kết quả mong đợi: Hệ thống tự động cảnh báo khi chính nó gặp sự cố, giúp đội vận động phát hiện và xử lý trước khi ảnh hưởng đến việc giám sát các ứng dụng khác.
Điều hướng series:
Mục lục: Series: Series: Xây dựng hệ thống observability toàn diện (Logging, Metrics, Tracing) cho môi trường Hybrid Cloud với Prometheus, Grafana và OpenTelemetry
« Phần 8: Tối ưu hiệu năng và quản lý chi phí cho môi trường Hybrid