Python Exporter接入Prometheus

Prometheus简介

Prometheus是什么

Prometheus是一个监控告警工具。监控可以简单理解为一个指标数据收集器,在运维体系中,常用来收集服务器节点状态,容器状态,服务指标等多种类型数据。告警可以简单理解为,Prometheus根据监控的一些数据指标,当达到某个条件的时候,生成告警信息。

Prometheus收集数据方式

通常而言,数据的收集(数据同步)方式分为两种,一种是数据节点的数据主动想数据中心发送,我们称之为推送模式。另一种方式是,数据中心定时向数据节点拉去数据,我们称之为拉取模式。

对于推送模式而言,数据中心可能会面临较大压力,如果当前的数据节点是一个分布式架构,每个节点都要向数据中心主动发送数据,随着分布式规模的扩大,数据中心存在处理不过来的情况,而处理不过来的数据就会存在丢失的风险。

对于拉取模式而言,不会出现数据处理不过来的情况,顶多是长时间没有接收到数据中心的请求,而数据本身是暂存在数据节点种的,不会存在数据丢失的风险。

Prometheus常采用的是拉取模式,意味着数据需要通过某种API的形式暴露给数据中心,以供拉取。如果是要使用推送数据方式,则使用client 对应的 push_to_gateway方法。

Prometheus指标类型

Prometheus的指标分类四种类型,计数器(Counter)、仪表盘(Gauge)、直方图(Histogram)、摘要(Summary)

计数器Counter

Counter 类型代表一种样本数据单调递增的指标,即只增不减,除非监控系统发生了重置。可用来表示服务的请求数、已完成的任务数、错误发生的次数等。(不可用于统计非单调变量,如线/进程数量之类的指标)

常见使用方法

// 将counter值加1.
Inc()
// 将指定值加到counter值上,如果指定值<0 会panic.
Add(float64)

常用PromQL举例

//通过rate()函数获取HTTP请求量的增长率
rate(http_requests_total[5m])
//查询当前系统中,访问量前10的HTTP地址
topk(10, http_requests_total)

仪表盘(Gauge)

Guage 类型代表一种样本数据可以任意变化的指标,即可增可减。guage 通常用于像温度或者内存使用率这种指标数据,也可以表示能随时增加或减少的“总数”,例如:当前并发请求的数量。

对于 Gauge 类型的监控指标,通过 PromQL 内置函数 delta() 可以获取样本在一段时间内的变化情况,例如,计算 CPU 温度在两小时内的差异:

dalta(cpu_temp_celsius{host="zeus"}[2h])

同时可以通过历史数据进行预测,例如,基于 2 小时的样本数据,来预测主机可用磁盘空间在 4 个小时之后的剩余情况

predict_linear(node_filesystem_free{job="node"}[2h], 4 * 3600) < 0

直方图(Histogram)

在大多数情况下人们都倾向于使用某些量化指标的平均值,例如 CPU 的平均使用率、页面的平均响应时间。这种方式的问题很明显,以系统 API 调用的平均响应时间为例:如果大多数 API 请求都维持在 100ms 的响应时间范围内,而个别请求的响应时间需要 5s,那么就会导致某些 WEB 页面的响应时间落到中位数的情况,而这种现象被称为 长尾问题

为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在 0~10ms 之间的请求数有多少而 10~20ms 之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。Histogram 和 Summary 都是为了能够解决这样问题的存在,通过 Histogram 和 Summary 类型的监控指标,我们可以快速了解监控样本的分布情况。

Histogram 在一段时间范围内对数据进行采样(通常是请求持续时间或响应大小等),并将其计入可配置的存储桶(bucket)中,后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图。

Histogram 类型的样本会提供三种指标(假定原始指标名称为baseIndicator):

  • 样本的值分布在 bucket 中的数量。

    命名为_bucket{le="<上边界>"},即baseIndicator_bucket{le="<上边界>"}

    例如:

    // 在总共2次请求当中。http 请求响应时间 <=0.005 秒 的请求次数为0
    baseIndicator_bucket{path="/",method="GET",code="200",le="0.005",} 0.0
     
    // 在总共2次请求当中。http 请求响应时间 <=0.01 秒 的请求次数为0
    baseIndicator_bucket{path="/",method="GET",code="200",le="0.01",} 0.0
    
  • 所有样本值的大小总和

    命名为_sum,即baseIndicator_sum

    例如:

    // 实际含义:发生的2次 http 请求总的响应时间为 13.107670803000001 秒
    baseIndicator_sum{path="/",method="GET",code="200",} 13.107670803000001
    
  • 样本总数

    命名为_count,即baseIndicator_count

    例如:

     // 实际含义:当前一共发生了 2 次 http 请求
    baseIndicator_count{path="/",method="GET",code="200",} 2.0
    
    

可以通过 histogram_quantile() 函数来计算 Histogram 类型样本的分位数。分位数可能不太好理解,我举个例子,假设你要计算样本的 9 分位数(quantile=0.9),即表示 90% 的样本的值。Histogram 还可以用来计算应用性能指标值(Apdex score)

摘要(Summary)

与 Histogram 类型类似,用于表示一段时间内的数据采样结果(通常是请求持续时间或响应大小等),但它直接存储了分位数(通过客户端计算,然后展示出来),而不是通过区间来计算。

Summary 类型的样本也会提供三种指标(假定原始指标名称为baseIndicator ):

  • 样本值的分位数分布情况,命名为 baseIndicator{quantile=“<φ>”}。

     // 含义:这 12 次 http 请求中有 50% 的请求响应时间是 3.052404983s
    baseIndicator{path="/",method="GET",code="200",quantile="0.5",} 3.052404983
     // 含义:这 12 次 http 请求中有 90% 的请求响应时间是 8.003261666s
     baseIndicator{path="/",method="GET",code="200",quantile="0.9",} 8.003261666
    
  • 所有样本值的大小总和,命名为 baseIndicator_sum。

     // 含义:这12次 http 请求的总响应时间为 51.029495508s
    baseIndicator_sum{path="/",method="GET",code="200",} 51.029495508
    
  • 样本总数,命名为 baseIndicator_count。

     // 含义:当前一共发生了 12 次 http 请求
    baseIndicator_count{path="/",method="GET",code="200",} 12.0
    

Prometheus结构

Prometheus分为Server和Exporter,Server即指Prometheus监控中心。Exporter则是向Server提供数据的节点。Exporter可以由多种不同的语言实现。

Prometheus Server 配置文件

单机Prometheus配置

global:
  scrape_interval:     15s # 默认15s向其他节点获取数据

  # 需要展现的标签
  external_labels:
    monitor: 'codelab-monitor'

# 数据抓取配置
scrape_configs:
  - job_name: 'prometheus' # 任务名称
    scrape_interval: 5s # 抓取时间,重写全局时间
    
    static_configs:
      - targets: ['localhost:9090'] # 需要抓取的节点

在k8s集群中,由于k8s节点可以直接通过selector筛选,配置形式也有所不同

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  annotations:
  labels:
    type: data
  name: app-1
  namespace: test-namespace
spec:
  endpoints:
  - interval: 30s # 拉取时间
    port: http-metrics #拉取端口
  namespaceSelector:
    matchNames:
    - test-namespace
  selector:
    matchLabels:
      app: app-1 # 自己写的服务,需要配合 k8s lable
      stage: "1" 
      type: web

Python Exporter 实现

依赖安装

安装prometheus-client

pip install prometheus-client

创建Exporter

由于Prometheus拉取数据的模型是通过服务端向Exporter请求的形式,这意味着Exporter必须是一个web服务,且需要有一个统一的api向Prometheus提供数据。

在Prometheus监控体系中,所有的Exporter都会使用一个路径为/metrics的API向Prometheus提供数据,根据之前的配置,也能理解提供数据的频率取决于Prometheus.yml文件的配置。

那么我们就必须在自己Exporter上实现/metricsAPI,但是在现实使用中,封装为Exporter的程序未必是一个web服务。所以针对于该情况,又得分一下几种方式来暴露/metricsAPI。

  1. 程序本身就不是不是一个web服务。

    from prometheus_client import start_http_server
    start_http_server(8000)
    
  2. twisted类型服务

    from prometheus_client.twisted import MetricsResource
    from twisted.web.server import Site
    from twisted.web.resource import Resource
    from twisted.internet import reactor
    
    root = Resource()
    root.putChild(b'metrics', MetricsResource())
    
    factory = Site(root)
    reactor.listenTCP(8000, factory)
    reactor.run()
    
  3. WSGI类型,针对于flask、django框架

    from wsgiref.simple_server import make_server
    httpd = make_server('', 8000, app)
    httpd.serve_forever()
    
    # 使用独立端口暴露给Prometheus,注意端口冲突
    from prometheus_client import start_wsgi_server
    start_wsgi_server(8000)
    

    如果是针对于flask应用,可以考虑直接使用prometheus-flask-exporter

  4. ASGI类型,针对Fastapi、Starlette

    虽然官方client有提供make_asgi_app方法,推荐使用starlette_exporter,或者prometheus-fastapi-instrumentator之类的库

    两种方式都是通过加入middleware的形式来注入的

    • starlette_exporter的显示注入
    from fastapi import FastAPI
    from starlette_exporter import PrometheusMiddleware, handle_metrics
    
    app = FastAPI()
    app.add_middleware(PrometheusMiddleware)
    app.add_route("/metrics", openmetrics_handler)
    
    • prometheus-fastapi-instrumentator 则是在expose方法中注入,使用方式如下
    from fastapi import FastAPI
    from prometheus_fastapi_instrumentator import Instrumentator, metrics
    
    app = FastAPI()
    instrumentator = Instrumentator(
        should_group_status_codes=True,
        should_ignore_untemplated=True,
        should_respect_env_var=False, # 为True时候,会屏蔽/metrics 路由
        should_instrument_requests_inprogress=True,
        excluded_handlers=["/metrics"],
        env_var_name="ENABLE_METRICS",
        inprogress_name="fastapi_inprogress",
        inprogress_labels=True,
    )
    
    instrumentator.add(
        metrics.latency(
            should_include_handler=True,
            should_include_method=True,
            should_include_status=True,
            metric_namespace=NAMESPACE,
            metric_subsystem=SUBSYSTEM,
        )
    )
    instrumentator.instrument(app) # middleware 由此添加
    instrumentator.expose(app, include_in_schema=False, should_gzip=True) # 添加/metrics路由
    
  5. 需要将数据计入text文件类型(针对于cronjob 类型监控)

    from prometheus_client import CollectorRegistry, Gauge, write_to_textfile
    
    registry = CollectorRegistry()
    g = Gauge('raid_status', '1 if raid array is okay', registry=registry)
    g.set(1)
    write_to_textfile('/configured/textfile/path/raid.prom', registry)
    
  6. 主动推送数据方式

    from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
    
    registry = CollectorRegistry()
    g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
    g.set_to_current_time()
    push_to_gateway('localhost:9091', job='batchA', registry=registry)
    


基于邮件发送场景的Demo

使用fastapi+prometheus_fastapi_instrumentator实现。

采用Counter数据结构,以拉取数据方式实现,考虑到同时需要统计多个客户的发送情况,labelnames使用[‘client’, ‘status’]来标记。

from typing import Callable

from fastapi import FastAPI
from prometheus_client import Counter
from prometheus_fastapi_instrumentator import Instrumentator
from prometheus_fastapi_instrumentator.metrics import Info

instrumentator = Instrumentator(
    should_group_status_codes=True,
    should_ignore_untemplated=True,
    should_respect_env_var=False,
    should_instrument_requests_inprogress=True,
    excluded_handlers=["/metrics"],
    env_var_name="ENABLE_METRICS",
    inprogress_name="fastapi_inprogress",
    inprogress_labels=True,
)

app = FastAPI()


@app.on_event("startup")
async def startup():
    instrumentator.instrument(app)
    instrumentator.expose(app, include_in_schema=False, should_gzip=True)


@app.get("/health")
async def health():
    return "OK"


@app.get("/email_count")
async def email_count(status: str, client: str):
    return True


def email_send_counter() -> Callable[[Info], None]:
    METRIC = Counter(
        "email_send_counter",
        "Total number of emails sent, labeled by result (success, failure, error)",
        labelnames=['client', 'status']
    )

    def instrumentation(info: Info) -> None:
        base_url = info.request.url
        query_params = info.request.query_params
        status = query_params.get('status')
        client_name = query_params.get('client')
        if "email_count" in base_url.path:
            METRIC.labels(status, client_name).inc()

    return instrumentation


instrumentator.add(email_send_counter())

prometheus配置文件

global:
  scrape_interval:     15s 
  evaluation_interval: 15s 
  external_labels:
    monitor: 'test-project'

rule_files:
  # - 'alert.rules'

scrape_configs:

  - job_name: 'python-test'
    scrape_interval: 5s
    static_configs:
      - targets: ['prometheus_test:8000'] # 此处填写上面服务部署位置

你可能感兴趣的:(python,prometheus,开发语言)