应用监控

应用监控

如果我们要在我们的生产环境真正使用Prometheus,往往需要关注各种各样的指标,譬如服务器的CPU负载、内存占用量、IO开销、入网和出网流量等等。正如上面所说,Prometheus是使用Pull的方式来获取指标数据的,要让Prometheus从目标处获得数据,首先必须在目标上安装指标收集的程序,并暴露出HTTP接口供Prometheus查询,这个指标收集程序被称为Exporter,不同的指标需要不同的Exporter来收集,目前已经有大量的Exporter可供使用,几乎囊括了我们常用的各种系统和软件,官网列出了一份 常用Exporter的清单,各个Exporter都遵循一份端口约定,避免端口冲突,即从9100开始依次递增,这里是 完整的Exporter端口列表。另外值得注意的是,有些软件和系统无需安装Exporter,这是因为他们本身就提供了暴露Prometheus格式的指标数据的功能,比如Kubernetes、Grafana、Etcd、Ceph等。

主机节点监控

首先我们来收集服务器的指标,这需要安装 node_exporter,这个exporter用于收集 *NIX 内核的系统,如果你的服务器是Windows,可以使用 WMI exporter

Prometheus server一样,node_exporter也是开箱即用的;node_exporter启动之后,我们访问下/metrics接口看看是否能正常获取服务器指标:

$ curl http://localhost:9100/metrics

然后可以在Prometheus中添加抓取配置:

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["192.168.0.107:9090"]
  - job_name: "server"
    static_configs:
      - targets: ["192.168.0.107:9100"]

一般情况下,node_exporter都是直接运行在要收集指标的服务器上的,官方不推荐用Docker来运行node_exporter。如果逼不得已一定要运行在Docker里,要特别注意,这是因为Docker的文件系统和网络都有自己的namespace,收集的数据并不是宿主机真实的指标。可以使用一些变通的方法,比如运行Docker时加上下面这样的参数:

$ docker run -d \
  --net="host" \
  --pid="host" \
  -v "/:/host:ro,rslave" \
  quay.io/prometheus/node-exporter \
  --path.rootfs /host

Python应用监控

这里我们以简单的Python应用为例,介绍如何在应用层面接入到Prometheus监控中:

import http.server
from prometheus_client import start_http_server

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")

if __name__ == "__main__":
    start_http_server(8000)
    server = http.server.HTTPServer(('localhost', 8001), MyHandler)
    server.serve_forever()

start_http_server(8000)会启动一个监听8000HTTP服务器,我们访问 http://localhost:8000/ 即可以获取到当前的Metrics

# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 372.0
python_gc_objects_collected_total{generation="1"} 0.0
python_gc_objects_collected_total{generation="2"} 0.0
# HELP python_gc_objects_uncollectable_total Uncollectable object found during GC
# TYPE python_gc_objects_uncollectable_total counter
python_gc_objects_uncollectable_total{generation="0"} 0.0
python_gc_objects_uncollectable_total{generation="1"} 0.0
python_gc_objects_uncollectable_total{generation="2"} 0.0
# HELP python_gc_collections_total Number of times this generation was collected
# TYPE python_gc_collections_total counter
python_gc_collections_total{generation="0"} 34.0
python_gc_collections_total{generation="1"} 3.0
python_gc_collections_total{generation="2"} 0.0
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="8",patchlevel="0",version="3.8.0"} 1.0

然后我们在Prometheus配置文件中添加如下的抓取配置:

global:
  scrape_interval: 10s
scrape_configs:
  - job_name: example
    static_configs:
      - targets:
          - localhost:8000

Prometheus中输入python_info PromQL表达式,即可以看到如下的结果:

Evaluating the expression python_info produces one result

Counter

Counter是您可能最常在仪器中使用的度量标准类型,Counter跟踪事件的数量或大小,它们主要用于跟踪执行特定代码路径的频率。

from prometheus_client import Counter

REQUESTS = Counter('hello_worlds_total',
        'Hello Worlds requested.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        REQUESTS.inc()
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")

这里我们可以使用 rate(hello_worlds_total[1m]) 这样的表达式来查看接口请求的变化率:

接口请求变化率

其他的常用场景譬如统计错误率:

import random
from prometheus_client import Counter

REQUESTS = Counter('hello_worlds_total',
        'Hello Worlds requested.')
EXCEPTIONS = Counter('hello_world_exceptions_total',
        'Exceptions serving Hello World.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        REQUESTS.inc()
        with EXCEPTIONS.count_exceptions():
          if random.random() < 0.2:
            raise Exception
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")

然后使用 rate(hello_world_exceptions_total[1m]) / rate(hello_worlds_total[1m]) 这样的表达式来计算错误率。在Python中,我们还可以使用count_exceptions注解:

EXCEPTIONS = Counter('hello_world_exceptions_total',
        'Exceptions serving Hello World.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    @EXCEPTIONS.count_exceptions()
    def do_GET(self):
      # ...

Prometheus使用64位浮点数作为值,因此您不仅限于将计数器递增1。实际上,您可以将计数器增加任何非负数。这使您可以跟踪处理的记录数,提供的字节数或以欧元为单位的销售额:

import random
from prometheus_client import Counter

REQUESTS = Counter('hello_worlds_total',
        'Hello Worlds requested.')
SALES = Counter('hello_world_sales_euro_total',
        'Euros made serving Hello World.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        REQUESTS.inc()
        euros = random.random()
        SALES.inc(euros)
        self.send_response(200)
        self.end_headers()
        self.wfile.write("Hello World for {} euros.".format(euros).encode())

Gauge

Gauge是某些当前状态的快照,对于计数器来说,增长速度是您所关心的,对于仪表而言,它是仪表的实际值。因此,值可以同时上升和下降。Gauges包含了inc, dec以及set三个方法:

import time
from prometheus_client import Gauge

INPROGRESS = Gauge('hello_worlds_inprogress',
        'Number of Hello Worlds in progress.')
LAST = Gauge('hello_world_last_time_seconds',
        'The last time a Hello World was served.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        INPROGRESS.inc()
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")
        LAST.set(time.time())
        INPROGRESS.dec()

这些指标可以直接在表达式浏览器中使用,而无需任何其他功能。例如,hello_world_last_time_seconds可用于确定何时提供最后一个Hello World。这种度量标准的主要用例是检测自处理请求以来是否时间过长。PromQL表达式 time() -hello_world_last_time_seconds 将告诉您自上次请求以来有多少秒。

from prometheus_client import Gauge

INPROGRESS = Gauge('hello_worlds_inprogress',
        'Number of Hello Worlds in progress.')
LAST = Gauge('hello_world_last_time_seconds',
        'The last time a Hello World was served.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    @INPROGRESS.track_inprogress()
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")
        LAST.set_to_current_time()

Summary

当您试图了解系统性能时,了解您的应用程序响应请求所花的时间或后端的延迟是至关重要的指标。其他仪器系统提供某种形式的Timer度量标准,但是Prometheus更一般地看待事物。正如计数器可以增加一个非1的值一样,您可能希望跟踪有关事件的信息,而不是其延迟。例如,除了后端延迟之外,您可能还希望跟踪您返回的响应的大小。最常用的方法就是observe

import time
from prometheus_client import Summary

LATENCY = Summary('hello_world_latency_seconds',
        'Time for a request Hello World.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        start = time.time()
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")
        LATENCY.observe(time.time() - start)

如果查看/metrics,则会看到hello_world_latency_seconds度量标准具有两个时间序列:hello_world_latency_seconds_counthello_world_latency_seconds_sumhello_world_latency_seconds_count是已进行的观察调用的数量,因此表达式浏览器中的 rate(hello_world_latency_seconds_count[1m]) 将返回Hello World请求的每秒速率。hello_world_latency_seconds_sum是传递给观察值的总和,因此 rate(hello_world_latency_seconds_sum[1m]) 是每秒响应请求所花费的时间。如果将这两个表达式相除,您将获得最后一分钟的平均延迟。平均延迟的完整表达式为:

rate(hello_world_latency_seconds_sum[1m]) / rate(hello_world_latency_seconds_count[1m])

Histogram

Summary将提供平均延迟,但是如果要分位数呢?分位数告诉您,一定比例的事件的大小小于给定值。例如,0.95分位数为300毫秒,这意味着95% 的请求花费的时间少于300毫秒。在推理实际的最终用户体验时,分位数很有用。如果用户的浏览器向您的应用程序发出20个并发请求,则确定用户可见延迟的时间是最慢的。在这种情况下,第95个百分位数捕获了该延迟。

from prometheus_client import Histogram

LATENCY = Histogram('hello_world_latency_seconds',
        'Time for a request Hello World.')

class MyHandler(http.server.BaseHTTPRequestHandler):
    @LATENCY.time()
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Hello World")

这将产生一组名称为hello_world_latency_seconds_bucket的时间序列,这是一组计数器。直方图具有一组存储桶,例如1 ms10 ms25 ms,用于跟踪落入每个存储桶的事件数。histogram_quantile PromQL函数可以从存储桶中计算分位数。例如,0.95分位数(第95个百分位数)为:

histogram_quantile(0.95, rate(hello_world_latency_seconds_bucket[1m]))

Buckets

默认存储桶的延迟范围为1ms10s,这旨在捕获Web应用程序的典型延迟范围。但是,您也可以在定义指标时覆盖它们并提供自己的存储桶。如果默认值不适合您的用例,或者为服务级别协议(SLA)中提到的等待时间分位数添加显式存储桶,则可以执行此操作。为了帮助您检测typos,必须对提供的存储桶进行排序:

LATENCY = Histogram('hello_world_latency_seconds',
        'Time for a request Hello World.',
        buckets=[0.0001, 0.0002, 0.0005, 0.001, 0.01, 0.1])

如果您想要线性或指数存储桶,则可以使用Python列表推导。与列表解析不等效的语言的客户端库可能包括以下工具功能:

buckets=[0.1 * x for x in range(1, 10)]    # Linear
buckets=[0.1 * 2**x for x in range(1, 10)] # Exponential
上一页