前言

前陣子公司團隊的 SRE 導入 Prometheus 和 Grafana 來監測我們 RD 開發的服務,因為好奇心使然,想說順帶了解一下這兩個監測服務,就整理了這篇文章來記錄一下使用的心得

Prometheus

簡介

Prometheus 是一套開源的 監測(monitor)警示(alert) 工具,負責收集和儲存監測對象的指標(metric)

metric 泛指一切需觀察的數據,例如 web server 的每秒 request 數量、DB 每秒的連線數量…等等

而這些 metric 都是隨著時間不斷更新,因此會以 Time series 的方式為數據本身壓一個 timestamp 來儲存,也可以為該 metric 建立一個 optional 的 key-value pair 的 label

Prometheus 主要的 features 有:

  • 多維的 data model,包含 time series 與 key-value pairs 的 label
  • 自帶 query language PromQL,用於下一些 metric 的 query 語句
  • 是一個基於 HTTP 協定的 pull-based model(會叫 pull-based 的原因是 Prometheus 會定期去跟監測對象索取 metrics)
  • 符合微服務架構的 Application Metrics Pattern,集中式收集與監測各服務的指標

具體流程

Prometheus 會用一個 prometheus.yml 來管理每個要監測的任務(job or target),可以自己增加要監測的對象,具體做法是這樣:

假設要被監測的對象是一個 api server,那這個 api 必須增加一個 /metrics route 來提供該服務的 metric,各個程式語言都有要被監測的 metric 重點(ex: Node.js 的 event loop, Python 的 garbage collection…),也會有對應的套件來協助完成 metric route 的建立

接下來 Prometheus 就會用 HTTP 的 GET method 來定期輪詢 api-server 的 /metrics route,並將其存進 time series database,這樣就完成一個簡易的服務監測任務

當然,不見得所有服務都要自己實作提供 metric 的方式,例如 Caddy 就有內建提供 metric 給 Prometheus,所以可見 Prometheus 和其他軟體之間的介接性是很完整的

了解整個運作流程之後,再來仔細看一下官方文件的架構圖

architecture

Prometheus 大部分元件都是以 Go 語言撰寫的,其中比較重要的是:

  • Push Gateway
    • 對於一些生命週期較短且無法被 Prometheus scrape 的 jobs 來說,可以用 pushgateway 來將 metric 傳給 Prometheus,例如:臨時的 batch jobs
    • 官方文件建議唯有特殊狀況才需考慮 pushgateway,因爲可能會有 single point of failure 等等的問題,細節見此
  • Prometheus Server
    • 以 HTTP GET method 來 pull 監測對象的 metric
    • 存進 time series database(TSDB),預設就是 local 的 disk storage,會依據每兩個小時的區間來存進 chunks 資料夾內,細節見此
  • Alert Manager
    • 負責依據設定好的 rule 來發警示
    • 可串接 Slack, Email, PagerDuty…等通訊聯絡工具
  • Prometheus Web UI
    • 內建有視覺化的 dashboard
    • 但一般來說都會用 Grafana 來作為視覺化的 dashboard

如何生成 metric?

生成、暴露、公開 metric 的動作叫做 instrument,可以透過 Prometheus 的 Client Library 或是用 Go 撰寫的 plugin: node exporter 來協助將 Linux 機器的 metric export 出來,如果是 Windows 機器則可以用 Windows Exporter,兩者主要都是用來匯出機器本身相關的資訊,包含 CPU、memory 和 disk 用量,除此之外,還有很多其他選項,例如:Message system 的 Kafka exporter, Database 的 MySQL exporter, HTTP 的 Nginx VTS exporter, logging 的 Fluentd exporter…等等,可以參考官方文件

這邊特別關注一下等等要 demo 的 node exporter,他在 DockerHub 上有 image prom/node-exporter ,Quay 上也有 prometheus / node-exporter image,所以部署上算是開箱即用(雖然官網不建議用 Docker 部署,因為 host system 會被 access)

具體流程是,node exporter 啟動後預設會監聽 HTTP 9100 port,所以這個 api endpoint 將會提供給 Prometheus 做監測 metric 的依據

Alert 警示

Prometheus 是透過 prometheus.ymlrule_files 欄位來定義一個 alert.yml,由 alert.yml 來管理發警示的 rule,值得注意的是 alert rules 本身沒有發警報的功能,是靠 AlertManager 來發送警報的,同樣地, AlertManager 也有 Docker image 可以快速部署

Grafana

簡介

Grafana 是一個資料視覺化的工具,有 Open Source 版也有 Enterprice 版,比較常聽到和 Prometheus 的 metric 合作,完成一個監視服務的 dashboard

除此之外,Grafana 背後的資料來源(data source)也可以串接各種 Time series DB(TSDB)的資料,例如:Prometheus, Graphite, OpenTSDB, InfluxDB…等等

個人感覺 Grafana 和 Prometheus 的關係就類似 Kibana 和 Elasticsearch 的關係

用 Prometheus & Grafana 監測一個 FastAPI Web Server

接下來用 FastAPI 建立一個簡易的 Web API server,監聽 HTTP 8080 port,前面有提到每個程式語言幾乎都有套件來和 Prometheus 介接傳送 metric,而 FastAPI 就有 starlette_prometheus 套件來協助,最終用 /metrics來 export metric 給 Prometheus

from starlette_prometheus import metrics, PrometheusMiddleware
from fastapi import FastAPI
import uvicorn


app = FastAPI()
app.add_middleware(PrometheusMiddleware)
app.add_route("/metrics", metrics)

@app.get("/")
def root():
    return {"message": "Hello!"}


if __name__=="__main__":
    uvicorn.run(app, port=8080, host="0.0.0.0")

接著打包 Docker image

FROM python:3.9-slim

RUN apt-get update && \
    apt-get install -y python3-pip python3-dev && \
    cd /usr/local/bin && \
    pip3 install --upgrade pip

COPY ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip3 install -r requirements.txt

COPY . /app

EXPOSE 8000
CMD [ "python", "main.py"]

準備好 api server 之後,再來是要寫 prometheus.yml,指定 Prometheus 要監聽的服務,其中被監測的對象寫在 scrape_configs 欄位裡,每一個都是一個 job,我們這邊啟動三個 job,分別是 Prometheus 本身、node exporter 來監測運行機器(docker)狀態,還有剛剛建立的 api server(值得注意的是 hostname 要是 docker-compose 的 service 名稱)

再來,AlertManager 的服務也要寫在 yml config 內,最後在 rule_files 欄位內寫 alert.yml 來指定 alert 的 rule,該例子是停止回應 5 分鐘後就發警示

prometheus.yml

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ["localhost:9090"]
  - job_name: node
    static_configs:
      - targets: ["node-exporter:9100"]
  - job_name: api-server
    static_configs:
      - targets: ["api-server:8080"]
rule_files:
  - alert.yml
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - "alertmanager:9093"

alert.yml

# Ref: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/#templating

groups:
  - name: example
    rules:
      # Alert for any instance that is unreachable for >5 minutes.
      - alert: InstanceDown
        expr: up == 0
        for: 5m
        labels:
          severity: page
        annotations:
          summary: "Instance {{ $labels.instance }} down"
          description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes."

再來用 docker-compose 一次啟動 api server, prometheus, AlertManager, node exporter, Grafana 這幾個服務,值得注意的是 Grafana 會有一些機敏資料得用環境變數帶給他

export GF_ADMIN=xxx
export GF_ADMIN_PASS=xxxx

docker-compose.yml

version: "3.8"
services:
  api-server:
    image: fastapi-server
    ports:
      - 8080:8080
  prometheus:
    image: prom/prometheus:v2.30.0
    volumes:
      - ./config:/etc/prometheus
      - ./data:/prometheus
    ports:
      - 9090:9090
  node-exporter:
    image: prom/node-exporter:v1.2.2
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.rootfs=/rootfs"
      - "--path.sysfs=/host/sys"
      - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
      - "--no-collector.arp"
      - "--no-collector.netstat"
      - "--no-collector.netdev"
      - "--no-collector.softnet"
  alertmanager:
    image: prom/alertmanager:v0.23.0
    restart: unless-stopped
    volumes:
      - ./alertmanager:/etc/alertmanager
    command:
      - "--config.file=/etc/alertmanager/config.yml"
      - "--storage.path=/alertmanager"
  grafana:
    image: grafana/grafana:8.1.5
    restart: unless-stopped
    volumes:
      - ./grafana/data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    environment:
      - GF_SECURITY_ADMIN_USER=$GF_ADMIN
      - GF_SECURITY_ADMIN_PASSWORD=$GF_ADMIN_PASS
      - GF_USERS_ALLOW_SIGN_UP=false
    ports:
      - 3000:3000

啟動之後先到 8080 port 看一下 api server 有沒有成功運行,再看一下 /metrics 路由,可以看到傳給 Prometheus 的 metrics

api-server-metrics

再到 9090 port 看 Prometheus 啟動的頁面,點開 target 看一下 http://api-server:8080 有沒有在監測的對象內

prometheus_target

最後到 3000 port 的 Grafana,用剛剛的帳密環境變數來登入,接著按下 “Add Data Sources” 按鈕來增加 Prometheus 的 TSDB,並填上 Prometheus 運行的 URL

grafana

儲存變更後,就可以回到 dashboard 來 Explore 一些 metric,以目前 Python 運行的 FastAPI 來說,就能監測一系列 Python 相關的 GC(Garbage Collection)參數

grafana2

接著,改搜尋"starlette_requests_processing_time_seconds_count" 的 metric,接著連續快速戳 api-server 幾次,就可以看到 Grafana 忠實的將 Prometheus 監測到的數據呈現出來

grafana3

簡單回顧一下流程,api server 提供 /metrics route 給 Prometheus 來 scrap metric,而 Prometheus 運行在 9090 port,所以 Grafana 再透過指定 Prometheus 的 URL 來綁定 metric 的來源,將其視覺化

結論

趁這次機會了解一下作為監測系統的 Prometheus 和 Grafana,但測試上 instrument metric 除了手動連擊 api server 以外,更好的做法應該是用類似 JMeter 這種壓測工具來大量發送請求,所以有機會再來研究一下 JMeter