我们知道 Prometheus 采用的 pull 模式,但是某些网络场景下面(比如不在一个子网或者防火墙),Prometheus 无法直接拉取监控指标数据,这个时候我们可能就需要一种能够主动 push 的模式了。而 Pushgateway 就是 Prometheus 生态中来解决这个问题的一个工具。
但是 Pushgateway 也不是万能的,其本身也存在一些弊端:
- 将多个节点数据汇总到 pushgateway, 如果 pushgateway 挂了,受影响范围更大
- Prometheus 拉取状态 up 只针对 pushgateway, 无法做到对每个目标有效
由于 Pushgateway 可以持久化推送给它的所有监控数据,所以即使你的监控已经下线,Prometheus 还会拉取到旧的监控数据,需要手动清理 Pushgateway 不要的数据。
Pushgateway 的存在是为了允许临时和批处理作业向 Prometheus 暴露其指标。由于这些类型的任务可能存在的时间不够长而无法被抓取,因此他们可以将指标推送到 Pushgateway,然后 Pushgateway 将这些指标暴露给 Prometheus。有一点我们需要明白的是 Pushgateway 不是将指标主动 push 给 Prometheus,而是通过脚本将指标数据主动 push 给 Pushgateway 后,Prometheus 仍然通过 pull 模式去抓取指标。
前面我们也介绍过 node-exporter 中的 textfile 收集器也可以用来采集指标,似乎和 Pushgateway 比较相似,这二者有什么区别吗?textfile 通常是用于节点级别的指标,而 Pushgateway 是用于服务级别的指标。
安装
同样要安装 Pushgateway 也非常简单,直接从 Release 页面 下载适用于你平台的二进制版本并解压即可使用。如果你想从源码自行编译,可以在代码根目录下面直接执行 make 命令即可编译。
直接执行 Pushgateway 二进制文件即可启动了,要更改监听的地址,可以通过 --web.listen-address 标志(例如0.0.0.0:9091或:9091)指定。默认情况下 Pushgateway 不保留指标。但是 --persistence.file 标志允许我们指定一个文件,将推送的指标保存在其中,这样当 Pushgateway 重新启动后指标仍然存在。
此外当然我们也可以直接使用 Docker 镜像来进行启动:
docker run -d -p 9091:9091 prom/pushgateway
同样我们这里还是将 Pushgateway 部署在 Kubernetes 集群中,对应的资源清单文件如下所示:
# pushgateway.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pushgateway-data
namespace: kube-mon
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: local-path
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pushgateway
namespace: kube-mon
labels:
app: pushgateway
spec:
selector:
matchLabels:
app: pushgateway
template:
metadata:
labels:
app: pushgateway
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: pushgateway-data
containers:
- name: pushgateway
image: prom/pushgateway:v1.4.3
imagePullPolicy: IfNotPresent
args:
- "--persistence.file=/data"
ports:
- containerPort: 9091
name: http
volumeMounts:
- mountPath: "/data"
name: data
resources:
requests:
cpu: 100m
memory: 500Mi
limits:
cpu: 100m
memory: 500Mi
---
apiVersion: v1
kind: Service
metadata:
name: pushgateway
namespace: kube-mon
labels:
app: pushgateway
spec:
selector:
app: pushgateway
type: NodePort
ports:
- name: http
port: 9091
targetPort: http
这里我们 --persistence.file 指定了持久化的文件,然后通过一个 Service 来暴露了服务,直接应用上面的资源清单文件即可:
☸ ➜ kubectl apply -f https://p8s.io/docs/pushgateway/manifests/pushgateway.yaml
☸ ➜ kubectl get pods -n kube-mon -l app=pushgateway
NAME READY STATUS RESTARTS AGE
pushgateway-7684cbb67d-6mbjn 1/1 Running 0 99s
☸ ➜ kubectl get svc -n kube-mon -l app=pushgateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
pushgateway NodePort 10.106.136.207 <none> 9091:30893/TCP 107s
默认情况下 Pushgateway 会提供一个简单的 Web 页面,可以查看当前具有哪些指标。
基本使用
Pushgateway 的数据推送支持两种方式,Prometheus Client SDK 推送和 API 推送。
Client SDK 推送
Prometheus 本身提供了支持多种语言的 SDK,可通过 SDK 的方式,生成相关的数据,并推送到 Pushgateway,当然这种方式需要客户端代码支持,这也是官方推荐的方案。目前的 SDK 覆盖语言有官方的:
- Go
- Java or Scala
- Python
- Ruby
也有许多第三方的库,详情可参见此链接:https://prometheus.io/docs/instrumenting/clientlibs/。
这里我们以 Python 为例进行说明。
首先安装 Prometheus 的 Python SDK:
☸ ➜ pip install prometheus-client
然后创建一个名为 app.py 的文件,内容如下所示:
from prometheus_client import CollectorRegistry, Counter, Gauge, push_to_gateway
registry = CollectorRegistry()
g = Gauge('container_memory', 'Container memory data', ['node'], registry=registry)
g.labels(node='node1').inc() # +1
g.labels(node='node2').dec(10) # -10
g.labels(node='node3').set(4.2) # 4.2
c = Counter('my_requests_total', 'HTTP requests total', ['method', 'endpoint'], registry=registry)
c.labels(method='get', endpoint='/').inc()
c.labels(method='post', endpoint='/submit').inc()
push_to_gateway('192.168.0.106:30893', job='batchA', registry=registry)
首先引入了 Prometheus 的 Python SDK,然后创建了一个 CollectorRegistry 实例,分别创建了一个 Gauge 和 Counter 类型的指标,其中第一个参数为指标名称,第二个参数为指标的注释信息,第三个参数为相关的标签,然后为不同的标签值设置不同的指标值,最后通过 push_to_gateway 函数将指标数据发送到指定的 Pushgateway 服务上去。
直接执行上面的 Python 文件即可将数据推送到 Pushgateway:
☸ ➜ python app.py
API 推送
此外使用 Prometheus 文本协议,推送指标也是非常简单的,无需提供单独的 CLI。只需使用 curl 之类的命令行工具即可。
不过需要注意,在文本协议中,每一行都必须以换行符结尾('LF' 或 '\n'),以其他方式结束一行,例如使用 'CR'('\r')、'CRLF'('\r\n')或只是数据包的结尾,将导致协议错误。
推送的指标按组进行管理,由任意数量的标签组成的分组键识别,其中第一个必须是 job 标签。
比如我们现在将单个样本推送到由 {job="some_job"} 标识的组中:
☸ ➜ echo "some_metric 3.14" | curl --data-binary - http://192.168.0.106:30893/metrics/job/some_job
需要注意由于没有提供类型信息, 所以这里的 some_metric 将是 UNTYPED 无类型的。
上面的命令执行完后我们可以重新前往 Pushgateway 的 Web 页面查看。
可以看到页面中就出现了上面我们推送的 some_metric 这个指标,而且位于 job="some_job" 这个分组下面。
接下来我们再推送一些相对复杂的指标,推到 {job="some_job",instance="some_instance"} 标识的分组中去,继续之前记得将上面的分组删除,然后执行下面的命令:
☸ ➜ cat <<EOF | curl --data-binary - http://192.168.0.106:30893/metrics/job/some_job/instance/some_instance
# TYPE some_metric counter
some_metric{label="val1"} 42
# TYPE another_metric gauge
# HELP another_metric Just an example.
another_metric 2398.283
EOF
该命令会将指标推送到 {job="some_job",instance="some_instance"} 标识的分组中去,要注意指标内容中是如何提供类型信息和帮助信息的,这里我们的指标是都具有类型了。
在 Web 界面中就可以看到上面推送的两个指标了,一个是 COUNTER 类型,一个是 GAUGE 类型。
如果你想删除某个分组下面的所有指标,我们也可以通过 curl 命令来实现。比如删除由 {job="some_job",instance="some_instance"} 标识的分组中的所有指标,可以通过下面的命令来实现:
☸ ➜ curl -X DELETE http://192.168.0.106:30893/metrics/job/some_job/instance/some_instance
这里需要注意的是删除是根据分组标识进行删除的,比如我们要删除由 {job="some_job"} 标识的分组中的所有指标,那么可以用下面的命令来实现:
☸ ➜ curl -X DELETE http://192.168.0.106:30893/metrics/job/some_job
但是需要注意这里是不包括上面示例 {job="some_job",instance="some_instance"} 分组中的指标的,即使这些指标具有相同的 job 标签,这点非常重要。
如果你想删除所有组中的所有指标,可以使用下面的命令来实现:
☸ ➜ curl -X PUT http://192.168.0.106:30893/api/v1/admin/wipe
但是需要注意需要通过 Pushgateway 的命令行标志 --web.enable-admin-api 来启用管理 API。
抓取指标
现在我们需要将 Pushgateway 的指标配置到 Prometheus 中去,让 Prometheus 去主动抓取 Pushgateway 的指标数据,我们当然也可以使用服务发现的方式,这里我们单独为 Pushgateway 创建一个抓取任务,在 Prometheus 中添加如下所示的抓取配置:
scrape_configs:
- job_name: "pushgateway"
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels:
[__meta_kubernetes_namespace, __meta_kubernetes_service_name]
action: keep
regex: kube-mon;pushgateway
# 省略其他配置......
我们这里同样采用基于 endpoints 的自动发现的方式来进行配置,去匹配 kube-mon 这个命名空间下名为 pushgateway 的服务即可。正常更新上面的配置后 Prometheus 就会自动去发现 Pushgateway 这个服务了。
现在我们重新推送下面的指标:
☸ ➜ cat <<EOF | curl --data-binary - http://192.168.0.106:30893/metrics/job/some_job/instance/some_instance
# TYPE some_metric counter
some_metric{label="val1"} 42
# TYPE another_metric gauge
# HELP another_metric Just an example.
another_metric 2398.283
EOF
推送完成后我们就可以在 Prometheus 中去查询 some_metric 和 another_metric 这两个指标了。
Prometheus 会给每个抓取的指标附加一个 job 和 instance 的标签,job 标签来自 scrape 配置,我们这里抓取 Pushgateway 的 job 标签为 job="pushgateway",instance 标签的值会自动设置为抓取目标的主机和端口,所以所有从 Pushgateway 抓取的指标都会有 Pushgateway 的主机和端口作为 instance 标签,但是这可能会和你附加推送到 Pushgateway 指标上的 job 和 instance 标签冲突,这个时候 Prometheus 会将这些标签重命名为 exported_job 和 exported_instance。
但是,在抓取 Pushgateway 时,通常不希望出现这种行为。更多的时候你可能更希望保留推送到 Pushgateway 的指标的 job 和 instance 标签,这个时候我们只需要在 Pushgateway 的抓取配置中设置 honor_labels: true 即可,我们重新更新 Prometheus 的配置:
scrape_configs:
- job_name: "pushgateway"
honor_labels: true
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels:
[__meta_kubernetes_namespace, __meta_kubernetes_service_name]
action: keep
regex: kube-mon;pushgateway
# 省略其他配置......
上面抓取 Pushgateway 的任务中我们新增了一个 honor_labels: true 的配置,更新后我们重新去查询推送到 Pushgateway 中的两个指标。
可以看到 job 和 instance 这两个标签变成了我们推送到 Pushgateway 中的标签值了,这个可能更符合我们的预期。
另外需要注意的是 Pushgateway 不提供任何强一致性保证,没有高可用的方案,它可以做的最好的事情就是将每个指定时间段的指标保存到磁盘了,总之不到万不得已的情况下最好别用 Pushgateway。