Chuyển đến nội dung chính

BÀI 29: PROMETHEUS VÀ GRAFANA

Prometheus Operator và kube-prometheus-stack. ServiceMonitor, PodMonitor, PrometheusRule. Grafana dashboards cho Kubernetes cluster. AlertManager: routes, receivers (Slack, PagerDuty). Recording rules và PromQL best practices.

🔒 DevSecOps — Bài 29 BÀI 29: PROMETHEUS VÀ GRAFANA

KUBERNETES: TỪ CƠ BẢN ĐẾN NÂNG CAO

Module 7: Observability & Monitoring

xdev.asia

Prometheus và Grafana

Prometheus và Grafana là bộ đôi không thể tách rời trong hệ sinh thái Kubernetes. Prometheus chịu trách nhiệm thu thập, lưu trữ và query metrics, trong khi Grafana cung cấp lớp visualization mạnh mẽ. Với sự ra đời của Prometheus Operator, việc quản lý Prometheus trên Kubernetes trở nên declarative và tự động hóa hoàn toàn.

Prometheus Data Model

Trước khi đi vào Prometheus Operator, cần hiểu rõ data model của Prometheus để viết queries hiệu quả.

Time Series và Labels

Mọi dữ liệu trong Prometheus đều là time series — một chuỗi các giá trị (float64) theo thời gian, được định danh duy nhất bởi tên metric và một tập hợp key-value labels. Ví dụ:

http_requests_total{method="GET", status="200", job="api-server", instance="10.0.0.1:8080"}
http_requests_total{method="POST", status="500", job="api-server", instance="10.0.0.1:8080"}

Labels là công cụ chính để filter, aggregate và join data trong PromQL. Thiết kế labels tốt là quan trọng — không nên dùng high-cardinality labels (như user ID hay request ID) vì sẽ tạo ra hàng triệu time series và làm Prometheus chậm.

Metric Types

Prometheus định nghĩa bốn loại metric cơ bản:

  • Counter: giá trị chỉ tăng, không bao giờ giảm (reset về 0 khi restart). Dùng cho: total requests, total errors, bytes sent. Query thường dùng với rate() hoặc increase().
  • Gauge: giá trị có thể tăng hoặc giảm tự do. Dùng cho: current memory usage, current number of pods, queue size.
  • Histogram: đo phân phối của các observations (thường là request duration, response size). Tạo ra các time series với suffix _bucket, _sum, _count. Dùng để tính percentiles với histogram_quantile().
  • Summary: tương tự Histogram nhưng tính percentiles phía client-side. Ít linh hoạt hơn Histogram, không nên dùng cho metrics mới.

Prometheus Operator

Prometheus Operator giúp quản lý Prometheus và AlertManager trên Kubernetes theo cách declarative. Thay vì viết config files và reload Prometheus thủ công, bạn tạo Kubernetes resources và Operator sẽ tự động cập nhật cấu hình.

CRDs của Prometheus Operator

Prometheus Operator cung cấp các CRDs sau:

  • Prometheus: định nghĩa một Prometheus instance
  • AlertManager: định nghĩa AlertManager cluster
  • ServiceMonitor: định nghĩa cách scrape metrics từ Services
  • PodMonitor: định nghĩa cách scrape metrics từ Pods
  • PrometheusRule: định nghĩa alerting và recording rules
  • Probe: định nghĩa blackbox monitoring targets

Prometheus CRD

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  name: prometheus
  namespace: monitoring
spec:
  replicas: 2
  retention: 15d
  serviceAccountName: prometheus
  serviceMonitorSelector:
    matchLabels:
      team: frontend
  serviceMonitorNamespaceSelector:
    matchLabels:
      monitoring: enabled
  ruleSelector:
    matchLabels:
      prometheus: kube-prometheus
  storage:
    volumeClaimTemplate:
      spec:
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 100Gi
  resources:
    requests:
      memory: 2Gi
      cpu: 500m
    limits:
      memory: 4Gi
      cpu: 2000m

Prometheus Operator tự động discover ServiceMonitors và PodMonitors dựa trên label selectors — không cần restart hay reload Prometheus khi thêm target mới.

ServiceMonitor — Scrape Metrics Từ Services

ServiceMonitor là cách phổ biến nhất để thêm scrape targets vào Prometheus. Nó định nghĩa cách Prometheus tìm và scrape metrics từ một nhóm Services.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: api-server
  namespace: production
  labels:
    team: frontend
    app: api-server
spec:
  selector:
    matchLabels:
      app: api-server
  namespaceSelector:
    matchNames:
      - production
  endpoints:
    - port: metrics
      path: /metrics
      interval: 30s
      scrapeTimeout: 10s
      relabelings:
        - sourceLabels: [__meta_kubernetes_pod_name]
          targetLabel: pod
        - sourceLabels: [__meta_kubernetes_namespace]
          targetLabel: namespace

Service cần expose metrics port với tên đúng:

apiVersion: v1
kind: Service
metadata:
  name: api-server
  namespace: production
  labels:
    app: api-server
spec:
  ports:
    - name: http
      port: 8080
    - name: metrics      # tên port phải match với ServiceMonitor
      port: 9090
  selector:
    app: api-server

PodMonitor — Scrape Metrics Trực Tiếp Từ Pods

PodMonitor dùng khi bạn muốn scrape trực tiếp từ Pods mà không qua Service, hoặc khi mỗi Pod cần được scrape độc lập (ví dụ: mỗi Pod expose metrics khác nhau).

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: worker-pods
  namespace: production
spec:
  selector:
    matchLabels:
      app: worker
  podMetricsEndpoints:
    - port: metrics
      path: /metrics
      interval: 60s
  namespaceSelector:
    matchNames:
      - production
      - staging

PromQL — Query Language Của Prometheus

PromQL (Prometheus Query Language) là công cụ mạnh mẽ để query time series data. Dưới đây là các patterns phổ biến nhất.

Rate và Increase

Với Counter metrics, bạn luôn cần dùng rate() hoặc increase() để có giá trị có nghĩa:

# Requests per second (5 minute rate)
rate(http_requests_total[5m])

# Total requests trong 1 giờ qua
increase(http_requests_total[1h])

# Error rate percentage
rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) * 100

Histogram Percentiles

# P95 request latency
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# P99 latency theo service
histogram_quantile(0.99,
  sum by (le, service) (
    rate(http_request_duration_seconds_bucket[5m])
  )
)

Kubernetes-Specific Queries

# Pods với unavailable replicas
kube_deployment_status_replicas_unavailable > 0

# Pods đang trong CrashLoopBackOff
kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"} == 1

# CPU throttling percentage
rate(container_cpu_cfs_throttled_seconds_total[5m]) /
rate(container_cpu_cfs_periods_total[5m]) * 100 > 25

# Memory usage percentage
container_memory_working_set_bytes /
container_spec_memory_limit_bytes * 100

# Node disk pressure
kube_node_status_condition{condition="DiskPressure", status="true"} == 1

Grafana Dashboards

Grafana là visualization layer, kết nối với Prometheus (và Loki, Tempo) để tạo dashboards phong phú.

Import Dashboard Từ Grafana.com

Grafana.com có hàng nghìn community dashboards. Một số dashboard quan trọng cho Kubernetes:

  • ID 315: Kubernetes cluster monitoring (basic)
  • ID 12740: Kubernetes monitoring (advanced, requires kube-state-metrics)
  • ID 15661: Kubernetes Node Overview
  • ID 15760: Kubernetes Views — Global
  • ID 14205: Kubernetes — Pod Overview

Để import: vào Grafana UI → Dashboards → Import → nhập ID → chọn Prometheus data source.

Dashboard Variables

Variables biến dashboard thành interactive tool. Các variables phổ biến cho Kubernetes:

# Variable: cluster
Type: Query
Query: label_values(kube_node_info, cluster)

# Variable: namespace
Type: Query
Query: label_values(kube_namespace_labels{cluster="$cluster"}, namespace)

# Variable: pod
Type: Query
Query: label_values(kube_pod_info{cluster="$cluster", namespace="$namespace"}, pod)

Với variables này, user có thể chọn cluster → namespace → pod và mọi panel trong dashboard sẽ tự động filter theo lựa chọn đó.

Các Panels Quan Trọng

Dashboard Kubernetes hoàn chỉnh nên có:

  • Node Overview: CPU usage, memory usage, disk I/O, network I/O per node
  • Pod Metrics: CPU/memory request vs limit vs actual usage
  • Deployment Status: desired vs available replicas
  • Container Restarts: restart count trong 1h, 24h
  • Error Rate: HTTP 5xx rate per service
  • Latency P50/P95/P99: request duration percentiles

AlertManager

AlertManager nhận alerts từ Prometheus và xử lý chúng: routing đến đúng receiver, grouping để giảm noise, inhibition để tránh alert storm, và silencing khi maintenance.

Routing Tree

AlertManager routing được cấu hình theo cây phân cấp. Mỗi route match labels của alert và gửi đến receiver tương ứng:

apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
  name: main-routing
  namespace: monitoring
spec:
  route:
    groupBy: ['alertname', 'cluster', 'service']
    groupWait: 30s
    groupInterval: 5m
    repeatInterval: 12h
    receiver: 'default-slack'
    routes:
      - match:
          severity: critical
        receiver: 'pagerduty-critical'
        continue: false
      - match:
          severity: warning
          team: frontend
        receiver: 'slack-frontend'
      - match:
          severity: warning
        receiver: 'slack-platform'
  receivers:
    - name: 'default-slack'
      slackConfigs:
        - apiURL:
            name: slack-secret
            key: webhook-url
          channel: '#alerts'
          title: '{{ .CommonAnnotations.summary }}'
          text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
    - name: 'pagerduty-critical'
      pagerdutyConfigs:
        - routingKey:
            name: pagerduty-secret
            key: routing-key
          severity: '{{ .CommonLabels.severity }}'

Inhibition Rules

Inhibition rules ngăn alert bị gửi đi khi một alert khác đang active. Ví dụ: khi toàn bộ cluster down, không cần gửi alerts cho từng service:

inhibitRules:
  - sourceMatch:
      alertname: ClusterDown
    targetMatch:
      severity: warning
    equal: ['cluster']
  - sourceMatch:
      alertname: NodeNotReady
    targetMatch:
      alertname: KubePodNotRunning
    equal: ['node']

PrometheusRule — Alert Rules

PrometheusRule định nghĩa alerting rules và recording rules. Prometheus Operator tự động load rules này vào Prometheus.

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: kubernetes-alerts
  namespace: monitoring
  labels:
    prometheus: kube-prometheus
    role: alert-rules
spec:
  groups:
    - name: kubernetes.deployment
      rules:
        - alert: KubeDeploymentReplicasMismatch
          expr: |
            (
              kube_deployment_spec_replicas
              !=
              kube_deployment_status_replicas_available
            ) and (
              changes(kube_deployment_status_replicas_updated[10m]) == 0
            )
          for: 15m
          labels:
            severity: warning
          annotations:
            summary: "Deployment {{ $labels.namespace }}/{{ $labels.deployment }} replica mismatch"
            description: "Deployment {{ $labels.namespace }}/{{ $labels.deployment }} has not matched the expected number of replicas for over 15 minutes."

        - alert: KubePodCrashLooping
          expr: |
            increase(kube_pod_container_status_restarts_total[1h]) > 5
          for: 2m
          labels:
            severity: warning
          annotations:
            summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping"
            description: "Pod {{ $labels.namespace }}/{{ $labels.pod }} container {{ $labels.container }} has restarted {{ $value }} times in the last hour."

    - name: kubernetes.node
      rules:
        - alert: NodeMemoryPressure
          expr: kube_node_status_condition{condition="MemoryPressure", status="true"} == 1
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "Node {{ $labels.node }} is under memory pressure"

        - alert: NodeHighCPUUsage
          expr: |
            100 - (avg by (node) (
              rate(node_cpu_seconds_total{mode="idle"}[5m])
            ) * 100) > 85
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: "High CPU usage on node {{ $labels.node }}: {{ $value | printf \"%.1f\" }}%"

Recording Rules — Pre-Compute Expensive Queries

Recording rules tính toán trước các queries phức tạp và lưu kết quả thành time series mới. Điều này cải thiện đáng kể performance của dashboards và alerts sử dụng các queries nặng.

groups:
  - name: kubernetes.recording_rules
    interval: 1m
    rules:
      # Pre-compute request rate per service
      - record: job:http_requests_total:rate5m
        expr: |
          sum by (job, namespace, status) (
            rate(http_requests_total[5m])
          )

      # Pre-compute P99 latency per service
      - record: job:http_request_duration_seconds:p99
        expr: |
          histogram_quantile(0.99,
            sum by (le, job, namespace) (
              rate(http_request_duration_seconds_bucket[5m])
            )
          )

      # Pre-compute CPU usage ratio
      - record: namespace:container_cpu_usage_seconds_total:sum_rate
        expr: |
          sum by (namespace) (
            rate(container_cpu_usage_seconds_total{
              container!="",
              image!=""
            }[5m])
          )

Best practices cho recording rules:

  • Tên theo format level:metric:operations (ví dụ: job:http_requests:rate5m)
  • Chỉ tạo recording rules cho queries dùng trong nhiều places
  • Evaluation interval của recording rules phải nhỏ hơn scrape interval
  • Không tạo recording rules cho queries chỉ dùng một lần

Best Practices Cho Production

Prometheus Sizing

Prometheus memory usage tỉ lệ thuận với số lượng active time series. Ước tính: 1-2 bytes per sample, với 15-second scrape interval, 10,000 time series sẽ dùng khoảng 1GB RAM. Với cluster 100 nodes và hàng trăm services, dự kiến 500K-1M time series.

High Availability

Chạy 2 Prometheus instances với cùng cấu hình. Grafana sẽ deduplicate khi query. Với AlertManager, chạy 3 instances theo cluster mode để đảm bảo không mất alerts.

Long-Term Storage

Prometheus chỉ nên giữ data 2-4 tuần. Để long-term storage (months/years), dùng Thanos hoặc Grafana Mimir — cả hai đều hỗ trợ object storage backend (S3, GCS) với chi phí thấp hơn nhiều so với Prometheus local storage.

Tổng Kết

Prometheus Operator đã cách mạng hóa cách quản lý monitoring trên Kubernetes. Với ServiceMonitor và PodMonitor, việc thêm targets mới là hoàn toàn declarative và không cần can thiệp thủ công. PrometheusRule giúp alert rules được quản lý như code (GitOps-friendly), và AlertManager với routing tree linh hoạt đảm bảo đúng người nhận đúng alert.

Bài học tiếp theo sẽ bổ sung phần còn lại của observability stack: Loki cho logs và Tempo cho distributed tracing.