Skip to content

Prometheus StatsD Exporter 部署

接收應用程式以 StatsD 協定推送的指標,並將其轉換為 Prometheus 格式的橋接元件;支援獨立部署與 Sidecar 兩種模式。

概述

StatsD Exporter 是 Prometheus 生態中一個特殊的 Exporter:它不是主動連線到目標系統抓取指標,而是扮演一個 StatsD Server,等待應用程式主動將 metrics 以 UDP/TCP 推送過來。這種推送(push)模式適合無法暴露 HTTP /metrics 端點的應用程式,例如傳統的 Gunicorn Web Server 透過 statsd 函式庫自動送出請求統計。

有兩種部署方式:

  • 獨立部署(Helm Chart):一個共用的 StatsD Exporter 服務,多個應用程式的指標都推送到同一個端點
  • Sidecar 部署(推薦):每個應用 Pod 帶一個自己的 StatsD Exporter,隨主服務一同重啟,指標隔離性更好

核心內容

Metric Mapping 機制

StatsD 原始 metric 名稱通常是點號分隔的路徑(如 myapp.gunicorn.request.status.200),Prometheus 期望的格式是帶 Label 的結構化名稱(如 gunicorn_response_code{app="myapp", status="200"})。

Mapping 設定透過 glob 模式(* 萬用字元)捕捉路徑中的動態部分,再以 $1$2 引用轉換為 Label:

yaml
mappings:
  - match: "*.gunicorn.request.status.*"
    name: "gunicorn_response_code"
    labels:
      app: "$1"    # 捕捉第一個 *
      status: "$2" # 捕捉最後一個 *

獨立 vs. Sidecar 選擇

面向獨立部署Sidecar 部署
管理複雜度低(一個服務)高(每個 Pod 一份)
指標隔離性低(共用 Port)高(應用私有)
服務生命週期獨立隨主服務重啟
Prometheus 抓取ClusterIP ServicePod 同一個 Service
推薦場景舊系統整合新服務開發

Sidecar 模式的網路特性

Sidecar Container 與主容器共用 Pod 網路命名空間,因此應用程式的 StatsD 連線目標是 127.0.0.1:9125(localhost),不需要服務發現或跨 Pod 通訊。

Lifecycle API(資料殘留問題)

StatsD Exporter 在資料停止推送後仍會保留先前的 metric 值(不像 Prometheus 的 target 可以標記為 down)。啟用 --web.enable-lifecycle 後,可呼叫以下端點來重設 Exporter 狀態:

端點功能
/-/reload重新載入 Mapping 設定
/-/quit停止 Exporter(觸發 Pod 重啟)

關鍵要點

  • StatsD Exporter 是被動接收推送的 Server,而非主動抓取的 Client
  • Mapping 設定決定 Prometheus 指標的命名與 Label 結構,需與應用程式的 metric 格式對應
  • Sidecar 模式下,應用程式連線 127.0.0.1:9125;Service 額外開放 9102 供 Prometheus 抓取
  • Helm Chart 的 NodePort 無法透過 Chart 指定埠號,需要另外建立 Service YAML
  • 資料殘留問題:資料停止後 Exporter 仍保留舊值,可用 Lifecycle API 清除

實際應用

Gunicorn Web Server 啟動時配置 statsd_host="127.0.0.1:9125",透過 gunicorn_statsd 等函式庫自動將請求統計(狀態碼、回應時間、Worker 數量)推送到 StatsD Exporter Sidecar,再由 Prometheus 定期從 Pod 的 9102 端口抓取轉換後的指標,最終在 Grafana 上呈現 API 效能儀表板。

部署設定參考

以下為實際部署時使用的完整設定,供日後查詢與複製使用。

環境參數

項目獨立部署Sidecar 部署
接收埠(UDP/TCP)91259125
Prometheus 抓取埠91029102
NodePort(接收)31728
Namespacemonitoring與主服務相同

獨立部署(Helm Chart)

statsd-values.yaml

yaml
extraArgs:
  - --web.enable-lifecycle

statsd:
  udpPort: 9125
  tcpPort: 9125
  mappingConfig: |-
    mappings:
      - match: "*.gunicorn.request.status.*"
        help: "gunicorn response code"
        name: "gunicorn_response_code"
        labels:
          app: "$1"
          status: "$2"
      - match: "*.gunicorn.workers"
        name: "gunicorn_workers"
        labels:
          app: "$1"
      - match: "*.gunicorn.requests"
        name: "gunicorn_requests"
        labels:
          app: "$1"
      - match: "*.gunicorn.request.duration"
        name: "gunicorn_request_duration"
        labels:
          app: "$1"
      - match: "*.gunicorn.request.duration_count"
        name: "gunicorn_request_duration_count"
        labels:
          app: "$1"
      - match: "*.gunicorn.request.duration_sum"
        name: "gunicorn_request_duration_sum"
        labels:
          app: "$1"

service:
  type: NodePort
  port: 9102

安裝指令

bash
helm install statsd-exporter \
  -f statsd-values.yaml \
  --namespace monitoring \
  --insecure-skip-tls-verify \
  prometheus-community/prometheus-statsd-exporter

自訂 NodePort Service(固定埠號)

yaml
apiVersion: v1
kind: Service
metadata:
  name: statsd-nodeport-service
  namespace: monitoring
spec:
  type: NodePort
  ports:
    - name: statsd-tcp
      nodePort: 31728
      port: 9125
      protocol: TCP
      targetPort: statsd-tcp
    - name: statsd-udp
      nodePort: 31728
      port: 9125
      protocol: UDP
      targetPort: statsd-udp
  selector:
    app.kubernetes.io/instance: statsd-exporter
    app.kubernetes.io/name: prometheus-statsd-exporter

Sidecar 部署

建立 Mapping ConfigMap

statsd_mapping.yml:

yaml
mappings:
  - match: "*.gunicorn.request.status.*"
    help: "gunicorn response code"
    name: "gunicorn_response_code"
    labels:
      app: "$1"
      status: "$2"
  - match: "*.gunicorn.workers"
    name: "gunicorn_workers"
    labels:
      app: "$1"
  - match: "*.gunicorn.requests"
    name: "gunicorn_requests"
    labels:
      app: "$1"
  - match: "*.gunicorn.request.duration"
    name: "gunicorn_request_duration"
    labels:
      app: "$1"
  - match: "*.gunicorn.request.duration_count"
    name: "gunicorn_request_duration_count"
    labels:
      app: "$1"
  - match: "*.gunicorn.request.duration_sum"
    name: "gunicorn_request_duration_sum"
    labels:
      app: "$1"
bash
kubectl create configmap buyer-helper-server-dev-statsd-mapping \
  --from-file=buyer_server/statsd_mapping.yml \
  -n default \
  -o yaml --dry-run=client | kubectl apply -f -

Deployment 中加入 Sidecar Container

yaml
containers:
  # ... 主服務容器 ...

  - name: statsd-exporter
    image: prom/statsd-exporter
    ports:
      - containerPort: 9102
    args:
      - "--statsd.mapping-config=/tmp/statsd_mapping.yml"
    volumeMounts:
      - name: statsd-mapping-config-volume
        mountPath: /tmp
volumes:
  - name: statsd-mapping-config-volume
    configMap:
      name: buyer-helper-server-dev-statsd-mapping

Service 設定(加入 Exporter 埠)

yaml
ports:
  - name: http
    port: 9194
    nodePort: 30002
    targetPort: 9194
  - name: exporter
    port: 9102
    targetPort: 9102

Gunicorn 連線設定(Python)

python
statsd_host = "127.0.0.1:9125"

相關概念

來源