为了解决 Prometheus 大内存问题,我竟然强行将 Prometheus Operator 给肢解了

标签: prometheus 内存 问题 | 发表时间:2020-08-13 01:42 | 作者:玻璃樽
出处:http://weekly.dockone.io

Promtheus 本身只支持单机部署,没有自带支持集群部署,也不支持高可用以及水平扩容,它的存储空间受限于本地磁盘的容量。同时随着数据采集量的增加,单台 Prometheus 实例能够处理的时间序列数会达到瓶颈,这时 CPU 和内存都会升高,一般内存先达到瓶颈,主要原因有:
  • Prometheus 的内存消耗主要是因为每隔 2 小时做一个 Block 数据落盘,落盘之前所有数据都在内存里面,因此和采集量有关。
  • 加载历史数据时,是从磁盘到内存的,查询范围越大,内存越大。这里面有一定的优化空间。
  • 一些不合理的查询条件也会加大内存,如 Group 或大范围 Rate。


这个时候要么加内存,要么通过集群分片来减少每个实例需要采集的指标。本文就来讨论通过 Prometheus Operator 部署的 Prometheus 如何根据服务维度来拆分实例。

根据服务维度拆分 Prometheus

Prometheus 主张根据功能或服务维度进行拆分,即如果要采集的服务比较多,一个 Prometheus 实例就配置成仅采集和存储某一个或某一部分服务的指标,这样根据要采集的服务将 Prometheus 拆分成多个实例分别去采集,也能一定程度上达到水平扩容的目的。

在 Kubernetes 集群中,我们可以根据 namespace 来拆分 Prometheus 实例,例如将所有 Kubernetes 集群组件相关的监控发送到一个 Prometheus 实例,将其他所有监控发送到另一个 Prometheus 实例。
Prometheus Operator 通过 CRD 资源名 Prometheus 来控制 Prometheus 实例的部署,其中可以通过在配置项 serviceMonitorNamespaceSelector 和 podMonitorNamespaceSelector 中指定标签来限定抓取 target 的 namespace。例如,将 namespace kube-system 打上标签 monitoring-role=system,将其他的 namespace 打上标签 monitoring-role=others。

告警规则拆分

将 Prometheus 拆分成多个实例之后,就不能再使用默认的告警规则了,因为默认的告警规则是针对所有 target 的监控指标的,每一个 Prometheus 实例都无法获取所有 target 的监控指标,势必会一直报警。为了解决这个问题,需要对告警规则进行拆分,使其与每个 Prometheus 实例的服务维度一一对应,按照上文的拆分逻辑,这里只需要拆分成两个告警规则,打上不同的标签,然后在 CRD 资源 Prometheus 中通过配置项 ruleSelector 指定规则标签来选择相应的告警规则。

集中数据存储

解决了告警问题之后,还有一个问题,现在监控数据比较分散,使用 Grafana 查询监控数据时我们也需要添加许多数据源,而且不同数据源之间的数据还不能聚合查询,监控页面也看不到全局的视图,造成查询混乱的局面。

为了解决这个问题,我们可以让 Prometheus 不负责存储数据,只将采集到的样本数据通过 Remote Write 的方式写入远程存储的 Adapter,然后将 Grafana 的数据源设为远程存储的地址,就可以在 Grafana 中查看全局视图了。这里选择 VictoriaMetrics 来作为远程存储。VictoriaMetrics 是一个高性能,低成本,可扩展的时序数据库,可以用来做 Prometheus 的长期存储,分为单机版本和集群版本,均已开源。如果数据写入速率低于每秒一百万个数据点,官方建议使用单节点版本而不是集群版本。本文作为演示,仅使用单机版本,架构如图:

实践

确定好了方案之后,下面来进行动手实践。

部署 VictoriaMetrics

首先部署一个单实例的 VictoriaMetrics,完整的 yaml 如下:
kind: PersistentVolumeClaim  
apiVersion: v1
metadata:
name: victoriametrics
namespace: kube-system
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
  storage: 100Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app: victoriametrics
name: victoriametrics
namespace: kube-system
spec:
serviceName: pvictoriametrics
selector:
matchLabels:
  app: victoriametrics
replicas: 1
template:
metadata:
  labels:
    app: victoriametrics
spec:
  nodeSelector:
    blog: "true"
  containers:    
  - args:
    - --storageDataPath=/storage
    - --httpListenAddr=:8428
    - --retentionPeriod=1
    image: victoriametrics/victoria-metrics
    imagePullPolicy: IfNotPresent
    name: victoriametrics
    ports:
    - containerPort: 8428
      protocol: TCP
    readinessProbe:
      httpGet:
        path: /health
        port: 8428
      initialDelaySeconds: 30
      timeoutSeconds: 30
    livenessProbe:
      httpGet:
        path: /health
        port: 8428
      initialDelaySeconds: 120
      timeoutSeconds: 30
    resources:
      limits:
        cpu: 2000m
        memory: 2000Mi
      requests:
        cpu: 2000m
        memory: 2000Mi
    volumeMounts:
    - mountPath: /storage
      name: storage-volume
  restartPolicy: Always
  priorityClassName: system-cluster-critical
  volumes:
  - name: storage-volume
    persistentVolumeClaim:
      claimName: victoriametrics
---
apiVersion: v1
kind: Service
metadata:
labels:
app: victoriametrics
name: victoriametrics
namespace: kube-system
spec:
ports:
- name: http
port: 8428
protocol: TCP
targetPort: 8428
selector:
app: victoriametrics
type: ClusterIP

有几个启动参数需要注意:
  • storageDataPath:数据目录的路径。VictoriaMetrics 将所有数据存储在此目录中。
  • retentionPeriod:数据的保留期限(以月为单位)。旧数据将自动删除。默认期限为1个月。
  • httpListenAddr:用于监听 HTTP 请求的 TCP 地址。默认情况下,它在所有网络接口上监听端口 8428。


给 namespace 打标签

为了限定抓取 target 的 namespace,我们需要给 namespace 打上标签,使每个 Prometheus 实例只抓取特定 namespace 的指标。根据上文的方案,需要给 kube-system 打上标签 monitoring-role=system:
$ kubectl label ns kube-system monitoring-role=system  

给其他的 namespace 打上标签 monitoring-role=others。例如:
$ kubectl label ns monitoring monitoring-role=others  
$ kubectl label ns default monitoring-role=others

拆分 PrometheusRule

告警规则需要根据监控目标拆分成两个 PrometheusRule。具体做法是将 kube-system namespace 相关的规则整合到一个 PrometheusRule 中,并修改名称和标签:
# prometheus-rules-system.yaml  
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
labels:
prometheus: system
role: alert-rules
name: prometheus-system-rules
namespace: monitoring
spec:
groups:
...
...

剩下的放到另外一个 PrometheusRule 中,并修改名称和标签:
# prometheus-rules-others.yaml  
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
labels:
prometheus: others
role: alert-rules
name: prometheus-others-rules
namespace: monitoring
spec:
groups:
...
...

然后删除默认的 PrometheusRule:
$ kubectl -n monitoring delete prometheusrule prometheus-k8s-rules  

新增两个 PrometheusRule:
$ kubectl apply -f prometheus-rules-system.yaml  
$ kubectl apply -f prometheus-rules-others.yaml

如果你实在不知道如何拆分规则,或者不想拆分,想做一个伸手党,可以看这里:


拆分 Prometheus

下一步是拆分 Prometheus 实例,根据上面的方案需要拆分成两个实例,一个用来监控 kube-system namespace,另一个用来监控其他 namespace:
# prometheus-prometheus-system.yaml  
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
labels:
prometheus: system 
name: system
namespace: monitoring
spec:
remoteWrite:
- url: http://victoriametrics.kube-system.svc.cluster.local:8428/api/v1/write
  queueConfig:
    maxSamplesPerSend: 10000
retention: 2h 
alerting:
alertmanagers:
- name: alertmanager-main
  namespace: monitoring
  port: web
image: quay.io/prometheus/prometheus:v2.17.2
nodeSelector:
beta.kubernetes.io/os: linux
podMonitorNamespaceSelector:
matchLabels:
  monitoring-role: system 
podMonitorSelector: {}
replicas: 1 
resources:
requests:
  memory: 400Mi
limits:
  memory: 2Gi
ruleSelector:
matchLabels:
  prometheus: system 
  role: alert-rules
securityContext:
fsGroup: 2000
runAsNonRoot: true
runAsUser: 1000
serviceAccountName: prometheus-k8s
serviceMonitorNamespaceSelector: 
matchLabels:
  monitoring-role: system 
serviceMonitorSelector: {}
version: v2.17.2
---
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
labels:
prometheus: others
name: others
namespace: monitoring
spec:
remoteWrite:
- url: http://victoriametrics.kube-system.svc.cluster.local:8428/api/v1/write
  queueConfig:
    maxSamplesPerSend: 10000
retention: 2h
alerting:
alertmanagers:
- name: alertmanager-main
  namespace: monitoring
  port: web
image: quay.io/prometheus/prometheus:v2.17.2
nodeSelector:
beta.kubernetes.io/os: linux
podMonitorNamespaceSelector: 
matchLabels:
  monitoring-role: others 
podMonitorSelector: {}
replicas: 1
resources:
requests:
  memory: 400Mi
limits:
  memory: 2Gi
ruleSelector:
matchLabels:
  prometheus: others 
  role: alert-rules
securityContext:
fsGroup: 2000
runAsNonRoot: true
runAsUser: 1000
serviceAccountName: prometheus-k8s
serviceMonitorNamespaceSelector:
matchLabels:
  monitoring-role: others 
serviceMonitorSelector: {}
additionalScrapeConfigs:
name: additional-scrape-configs
key: prometheus-additional.yaml
version: v2.17.2

需要注意的配置:
  • 通过 remoteWrite 指定 remote write 写入的远程存储。
  • 通过 ruleSelector 指定 PrometheusRule。
  • 限制内存使用上限为 2Gi,可根据实际情况自行调整。
  • 通过 retention 指定数据在本地磁盘的保存时间为 2 小时。因为指定了远程存储,本地不需要保存那么长时间,尽量缩短。
  • Prometheus 的自定义配置可以通过 additionalScrapeConfigs 在 others 实例中指定,当然你也可以继续拆分,放到其他实例中。


删除默认的 Prometheus 实例:
$ kubectl -n monitoring delete prometheus k8s  

创建新的 Prometheus 实例:
$ kubectl apply -f prometheus-prometheus.yaml  

查看运行状况:
$ kubectl -n monitoring get prometheus  
NAME     VERSION   REPLICAS   AGE
system   v2.17.2   1          29h
others   v2.17.2   1          29h

$ kubectl -n monitoring get sts
NAME                READY   AGE
prometheus-system   1/1     29h
prometheus-others   1/1     29h
alertmanager-main   1/1     25d

查看每个 Prometheus 实例的内存占用:
$ kubectl -n monitoring top pod -l app=prometheus  
NAME                  CPU(cores)   MEMORY(bytes)
prometheus-others-0   12m          110Mi
prometheus-system-0   121m         1182Mi

最后还要修改 Prometheus 的 Service,yaml 如下:
apiVersion: v1  
kind: Service
metadata:
labels:
prometheus: system 
name: prometheus-system
namespace: monitoring
spec:
ports:
- name: web
port: 9090
targetPort: web
selector:
app: prometheus
prometheus: system
sessionAffinity: ClientIP
---
apiVersion: v1
kind: Service
metadata:
labels:
prometheus: others
name: prometheus-others
namespace: monitoring
spec:
ports:
- name: web
port: 9090
targetPort: web
selector:
app: prometheus
prometheus: others
sessionAffinity: ClientIP

删除默认的 Service:
$ kubectl -n monitoring delete svc prometheus-k8s  

创建新的 Service:
$ kubectl apply -f prometheus-service.yaml  

修改 Grafana 数据源

Prometheus 拆分成功之后,最后还要修改 Grafana 的数据源为 VictoriaMetrics 的地址,这样就可以在 Grafana 中查看全局视图,也能聚合查询。

打开 Grafana 的设置页面,将数据源修改为 http://victoriametrics.kube-sy ... :8428

点击 Explore 菜单:

在查询框内输入 up,然后按下 Shift+Enter 键查询:

可以看到查询结果中包含了所有的 namespace。

写这篇文章的起因是我的 k3s 集群每台节点的资源很紧张,而且监控的 target 很多,导致 Prometheus 直接把节点的内存资源消耗完了,不停地 OOM。为了充分利用我的云主机,不得不另谋他路,这才有了这篇文章。

原文链接: https://mp.weixin.qq.com/s/hE9baT6vtwgBvATuU1r51w

相关 [prometheus 内存 问题] 推荐:

为了解决 Prometheus 大内存问题,我竟然强行将 Prometheus Operator 给肢解了

- - DockOne.io
Promtheus 本身只支持单机部署,没有自带支持集群部署,也不支持高可用以及水平扩容,它的存储空间受限于本地磁盘的容量. 同时随着数据采集量的增加,单台 Prometheus 实例能够处理的时间序列数会达到瓶颈,这时 CPU 和内存都会升高,一般内存先达到瓶颈,主要原因有:. Prometheus 的内存消耗主要是因为每隔 2 小时做一个 Block 数据落盘,落盘之前所有数据都在内存里面,因此和采集量有关.

Kubernetes组件问题排查思路 – 十点运维吧-Linux|Kubernetes|Docker|Prometheus|Python|Golang|云原生|SRE

- -
Kubernetes的基础组件就像一栋房子的地基,它们的重要性不言而喻. 作为Kubernetes集群的维护者,经常会遇到组件的问题,那平时是怎么去定位解决的呢. 这里简要分析一下我的排查思路. 通过集群的状态,找到故障的节点或者组件. 使用pprof分析组件的具体性能. Kubernetes的基础组件不多,而且部署也非常简单,所以在定义范围的时候还是很容易的,比如我们在使用.

Docker 监控- Prometheus VS Cloud Insight

- - SegmentFault 最新的文章
如今,越来越多的公司开始使用 Docker 了,2 / 3 的公司在尝试了 Docker 后最终使用了它. 为了能够更精确的分配每个容器能使用的资源,我们想要实时获取容器运行时使用资源的情况,怎样对 Docker 上的应用进行监控呢. Docker 的结构会不会加大监控难度. 可是在没有专业运维团队来监控 Docker 的情况下,并且还想加快 Docker 监控的日程,怎么办呢.

Prometheus 和 Grafana 监控系统指南

- - 互联网技术和架构
Prometheus 是源于 Google Borgmon 的一个开源监控系统,用 Golang 开发. Prometheus 基本原理是通过 HTTP 协议周期性抓取被监控组件的状态,这样做的好处是任意组件只要提供 HTTP 接口就可以接入监控系统,不需要任何 SDK 或者其他的集成过程. 这样做非常适合虚拟化环境比如 VM 或者 Docker.

我的 Prometheus 到底啥时候报警?

- - DockOne.io
最近又被问到了 Prometheus 为啥不报警,恰好回忆起之前经常解答相关问题,不妨写一篇文章来解决下面两个问题:. 我的 Prometheus 为啥报警. 我的 Prometheus 为啥不报警. 我们首先需要一些背景知识:Prometheus 是如何计算并产生警报的. 这条警报的 大致含义是,假如 kube-apiserver 的 P99 响应时间大于 4 秒,并持续 10 分钟以上,就产生报警.

使用Prometheus、Thanos监控Kubernetes集群

- - DockOne.io
当你阅读这篇文章的时候,我相信你一定已经说服了你的经理,或者是公司CTO,选择容器和Kubernetes作为微服务治理平台,去转型升级你们公司的软件产品. 你非常非常的happy,一切都貌似按照计划进行,你创建了你的第一个Kubernetes集群(三大主流云服务提供商,微软云Azure,亚马逊云AWS和谷歌云GCP都提供了非常方便的方式部署Kubernetes平台),你开发了你的第一个容器化应用,然后把它部署到了你的Kubernetes集群上.

详解Prometheus的四种指标

- - DockOne.io
指标是用来衡量性能、消耗、效率和许多其他软件属性随时间的变化趋势. 它们允许工程师通过警报和仪表盘来监控一系列测量值的演变(如CPU或内存使用量、请求持续时间、延迟等). 指标在IT监控领域有着悠久的历史,并被工程师广泛使用,与日志和链路追踪一起被用来检测系统是否有不符合预期的表现. 在其最基本的形式中,一个指标数据点是由以下三个部分构成:.

解决php内存泄露问题

- - 鲁塔弗的博客
这是08年写的一份文档,我当时在一家网站刚接手做技术负责人,网站每天大概有60万ip/300万pv的访问,网站产品很复杂,代码结构差,开发工程师来来去去,代码只能只读了. 突然有一天开始频繁出现php-fpm进程耗光内存和cpu占有率飙升,前端频繁出现504错误. php-fpm进程耗光内存 这个就是传说中的内存泄露,所谓内存泄露,是指进程在运行过程中,内存占用率逐步上升而不释放,导致系统可用内存越来越少的情况.

(转)ThreadLocal的内存泄漏问题

- - 编程语言 - ITeye博客
原文:http://www.godiscoder.com/?p=479. 在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题. 表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出. 开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放.

机器内存爆满问题排查

- - IT瘾-tuicool
两周前广告开屏服务突然503报警不断,先查看了各种业务监控没发现流量等有什么大变化,因为不久之前出过一次机器出问题的情况,马上去查看了机器是不是正常,果然内存几乎涨满了. 大概十来分钟内存就会达到90%多,然后进程就重启了,但是从日志来看并没有什么异常情况,好在并没有将机器拖死. 当时第一件事就是回滚代码,各种代码回滚到几天前,没什么效果.