prometheus+alertmanager

流程梳理:

Prometheus 一条告警的触发流程、等待时间

报警处理流程如下:
1. Prometheus Server监控目标主机上暴露的http接口(这里假设接口A),通过上述Promethes配置的'scrape_interval'定义的时间间隔,定期采集目标主机上监控数据。

2. 当接口A不可用的时候,Server端会持续的尝试从接口中取数据,直到"scrape_timeout"时间后停止尝试。这时候把接口的状态变为“DOWN”。

3. Prometheus同时根据配置的"evaluation_interval"的时间间隔,定期(默认1min)的对Alert Rule进行评估;当到达评估周期的时候,发现接口A为DOWN,即UP=0为真,激活Alert,进入“PENDING”状态,并记录当前active的时间;

4. 当下一个alert rule的评估周期到来的时候,发现UP=0继续为真,然后判断警报Active的时间是否已经超出rule里的‘for’ 持续时间,如果未超出,则进入下一个评估周期;如果时间超出,则alert的状态变为“FIRING”;同时调用Alertmanager接口,发送相关报警数据。

5. AlertManager收到报警数据后,会将警报信息进行分组,然后根据alertmanager配置的“group_wait”时间先进行等待。等wait时间过后再发送报警信息。

6. 属于同一个Alert Group的警报,在等待的过程中可能进入新的alert,如果之前的报警已经成功发出,那么间隔“group_interval”的时间间隔后再重新发送报警信息。比如配置的是邮件报警,那么同属一个group的报警信息会汇总在一个邮件里进行发送。

7. 如果Alert Group里的警报一直没发生变化并且已经成功发送,等待‘repeat_interval’时间间隔之后再重复发送相同的报警邮件;如果之前的警报没有成功发送,则相当于触发第6条条件,则需要等待group_interval时间间隔后重复发送。

同时最后至于警报信息具体发给谁,满足什么样的条件下指定警报接收人,设置不同报警发送频率,这里有alertmanager的route路由规则进行配置。

prometheus --->触发阈值-->判断是否超出持续时间-->Alertmanager-->> 分组/抑制/静默--->> 媒体类型--->>邮件/微信/钉钉

prometheus安装

##install
wget https://github.com/prometheus/prometheus/releases/download/v2.12.0/prometheus-2.12.0.linux-amd64.tar.gz
tar zvxf prometheus-2.12.0.linux-amd64.tar.gz  -C /usr/local/ 
ln -s /usr/local/prometheus-2.12.0.linux-amd64  /usr/local/prometheus



##配置为系统服务
cat > /usr/lib/systemd/system/prometheus.service     << EOF
[Unit]
Description=Prometheus: the monitoring system
Documentation=http://prometheus.io/docs/

[Service]
ExecStart=/usr/local/prometheus/prometheus  --config.file=/usr/local/prometheus/prometheus.yml
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF
systemctl  daemon-reload
systemctl enable prometheus
ss -lntup  | grep 9090 	

##配置文件示例
 # my global config
  global:
    scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
    evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
   

  # 指定报警的地址Alertmanager 
  alerting:
    alertmanagers:
    - static_configs:
      - targets:
          - 127.0.0.1:9093

  #加载检测规则,并根据全局设置的计算间隔时间进行计算值
  rule_files:
    - "rules/*.yml"


  scrape_configs:
    - job_name: 'promethues-node'
      static_configs:
      - targets:
          - 192.168.1.140:9100
          ....
          
          
          
##检查配置文件
prometheus-2.14/bin/promtool check config /opt/prometheus-2.14/config/prometheus.yml
# 重新加载配置文件
[root@es01 config]# curl -X POST httP://10.0.20.11:9090/-/reload

export

检测系统信息,磁盘,cpu负载,内存等

wget https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz
tar xf node_exporter-0.18.1.linux-amd64.tar.gz  -C /usr/local/
ln -s /usr/local/node_exporter-0.18.1.linux-amd64  /usr/local/node_exporter

##设置为系统服务

cat >  /usr/lib/systemd/system/node_exporter.service  <<  EOF
[Unit]
Description=Prometheus node exporter
After=local-fs.target network-online.target network.target
Wants=local-fs.target network-online.target network.target

[Service]
Restart=on-failure
ExecStart="/usr/local/node_exporter/node_exporter"

[Install]
WantedBy=multi-user.target
EOF


systemctl status node_exporter.service          
ss -lntup | grep 9100

alertmanager

install

wget https://github.com/prometheus/alertmanager/releases/download/v0.19.0/alertmanager-0.19.0.linux-amd64.tar.gz
tar xf alertmanager-0.19.0.linux-amd64.tar.gz -C /usr/local
ls  -sv  /usr/local/alertmanager-0.19.0.linux-amd64  /usr/local/alertmanager


##设置为系统服务
 cat  /usr/lib/systemd/system/alertmanager.service
[Unit]
Description=alertmanager.
Documentation=https://prometheus.io/
After=network.target

[Service]
Restart=on-failure
ExecStart=/usr/local/alertmanager/alertmanager --config.file=/usr/local/alertmanager/alertmanager.yml 
# --web.external-url=http://xxx:xxx/

[Install]
WantedBy=multi-user.target


## 其他操作
###################################
[Unit]
Description=Alertmanager
After=network.target

[Service]
Type=simple
User=prometheus
ExecStart=/srv/alertmanager/alertmanager \
   --config.file=/srv/alertmanager/alertmanager.yml \ 
   --storage.path=/srv/alertmanager/data
Restart=on-failure

[Install]
WantedBy=multi-user.target
############################################



##alertmanager.yml  报警配置文件
cat  alertmanager.yml
global:
  resolve_timeout: 5m

route:
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h
  receiver: 'web.hook'
receivers:
- name: 'web.hook'
  webhook_configs:
  - send_resolved: true
    url: 'http://localhost:8060'
    # 此url是Python版本发送地址
    #url: 'http://localhost:8060/dingtalk/webhook/send'
    # 此url 是go版本发送地址

报警触发的时间节点,结合文首理解:

1、等待时间1

查看配置文件:vim prometheus.yml

global:
  # 数据采集间隔
  scrape_interval:     15s 
  # 评估告警周期
  evaluation_interval: 15s 
 # 数据采集超时时间默认10s
 # scrape_timeout

2、等待时间2

配置文件:vim alertmanager.yml

# route标记:告警如何发送分配
route:
  # group_by:采用哪个标签作为分组的依据
  group_by: ['alertname']
  # group_wait:分组等待的时间
  group_wait: 10s
  # group_interval:上下两组发送告警的间隔时间
  group_interval: 10s
  # repeat_interval:重复发送告警时间。默认1h
  repeat_interval: 1m
  # receiver 定义谁来通知报警
  receiver: 'mail'

其中rules.yaml中设定了for 的时间,这里是检测评估的表达式成立的持续时间,

route的完整定义

[ receiver: <string> ]
[ group_by: '[' <labelname>, ... ']' ]
[ continue: <boolean> | default = false ]

match:
  [ <labelname>: <labelvalue>, ... ]

match_re:
  [ <labelname>: <regex>, ... ]

[ group_wait: <duration> | default = 30s ]
[ group_interval: <duration> | default = 5m ]
[ repeat_interval: <duration> | default = 4h ]

routes:
  [ - <route> ... ]

每一个告警都会从配置文件中顶级route进入路由树,顶级route必须匹配所有告警(即不能有任何的匹配设置:match、match_re,每一个route都可以定义自己的receiver以及匹配规则。默认情况下,告警进入顶级route后会遍历所有的子节点,直到找到最深的匹配route,并将告警发送到route定义的receiver中。如何route中设置continue为false,那么告警在匹配到第一个子节点之后就直接停止。如何当前告警匹配不到任何的子节点,那该告警将会基于当前路由节点的receiver配置方式进行处理。

### match与match_re

告警的匹配有两种方式可以选择。一种方式基于字符串验证,通过设置**match**规则判断当前告警中是否存在标签labelname并且其值等于labelvalue。第二种方式则基于正则表达式,通过设置**match_re**验证当前告警标签的值是否满足正则表达式的内容。

### repeat_interval

如果警报已经成功发送通知, 如果想设置发送告警通知之前要等待时间,则可以通过**repeat_interval**参数进行设置。

### group_by

Alertmanager可以对告警通知进行分组,将多条告警合合并为一个通知。可以使用**group_by**来定义分组规则。基于告警中包含的标签,如果满足**group_by**中定义标签名称,那么这些告警将会合并为一个通知发送给接收器。

### group_wait

有的时候为了能够一次性收集和发送更多的相关信息时,可以通过**group_wait**参数设置等待时间,如果在等待时间内当前group接收到了新的告警,这些告警将会合并为一个通知向receiver发送。

### group_interval

用于定义相同的Gourp之间发送告警通知的时间间隔。

### 抑制机制(inhibit_rules)

Alertmanager的抑制机制可以避免当某种问题告警产生之后用户接收到大量由此问题导致的一系列的其它告警通知。

alertmanager数据格

alertmanager 发送给webhook 的数据格式

prometheus alertmanager配置redis告警 prometheus告警恢复_配置文件

{
  "version": "3",   
  "status": "<resolved|firing>",
  "receiver": <string>,
  "groupLabels": <object>,
  "commonLabels": <object>,
  "commonAnnotations": <object>,
  "externalURL": <string>,  // backling to the Alertmanager.
  "alerts": [
    {
      "labels": <object>,
      "annotations": <object>,
      "startsAt": "<rfc3339>",
      "endsAt": "<rfc3339>"
    }
  ]
}

一个钉钉webhook,通过钉钉报警,由于POST数据需要有要求;数据格式有点糙

from flask import Flask
from flask import request
import json

app = Flask(__name__)

@app.route('/',methods=['POST'])
def send():
    if request.method == 'POST':
        post_data = request.get_data()
        alert_data(post_data)
    return
def alert_data(data):
    from urllib2 import Request,urlopen
    url = 'https://oapi.dingtalk.com/robot/send?access_token=xxxx'
    send_data = '{"msgtype": "text","text": {"content": %s}}' %(data)
    request = Request(url, send_data)
    request.add_header('Content-Type','application/json')
    return urlopen(request).read()
if __name__ == '__main__':
    app.run(host='0.0.0.0')

钉钉报警的markdown 格式


import os
import json
import requests
import arrow

from flask import Flask
from flask import request

app = Flask(__name__)


@app.route('/', methods=['POST', 'GET'])
def send():
    if request.method == 'POST':
        post_data = request.get_data()
        send_alert(bytes2json(post_data))
        return 'success'
    else:
        return 'weclome to use prometheus alertmanager dingtalk webhook server!'


def bytes2json(data_bytes):
    data = data_bytes.decode('utf8').replace("'", '"')
    return json.loads(data)


def send_alert(data):
    token = os.getenv('ROBOT_TOKEN')
    if not token:
        print('you must set ROBOT_TOKEN env')
        return
    url = 'https://oapi.dingtalk.com/robot/send?access_token=%s' % token
    for output in data['alerts'][:]:
        try:
            pod_name = output['labels']['pod']
        except KeyError:
            try:
                pod_name = output['labels']['pod_name']
            except KeyError:
                pod_name = 'null'
                
        try:
            namespace = output['labels']['namespace']
        except KeyError:
            namespace = 'null'

        try:
            message = output['annotations']['message']
        except KeyError:
            try:
                message = output['annotations']['description']
            except KeyError:
                message = 'null'

        send_data = {
            "msgtype": "markdown",
            "markdown": {
                "title": "prometheus_alert",
                "text": "## 告警程序: prometheus_alert \n" +
                        "**告警级别**: %s \n\n" % output['labels']['severity'] +
                        "**告警类型**: %s \n\n" % output['labels']['alertname'] +
                        "**故障pod**: %s \n\n" % pod_name +
                        "**故障namespace**: %s \n\n" % namespace +
                        "**告警详情**: %s \n\n" % message +
                        "**告警状态**: %s \n\n" % output['status'] +
                        "**触发时间**: %s \n\n" % arrow.get(output['startsAt']).to('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss ZZ') +
                        "**触发结束时间**: %s \n" % arrow.get(output['endsAt']).to('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss ZZ')
            }
        }
        req = requests.post(url, json=send_data)
        result = req.json()
        if result['errcode'] != 0:
            print('notify dingtalk error: %s' % result['errcode'])


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

报警规则汇总



一些规则,只是记录规则,看他的写作方式,然后在prometheus控制台中验证

##############################################################################################
#                                 disk                                                       #
    - expr: node_filesystem_size_bytes{job="node-exporter" ,fstype=~"ext4|xfs"}
      record: node_exporter:disk:usage:total
      labels: 
        desc: "节点的磁盘总量"
        unit: byte
        job: "node-exporter"

    - expr: node_filesystem_avail_bytes{job="node-exporter",fstype=~"ext4|xfs"}
      record: node_exporter:disk:usage:free
      labels: 
        desc: "节点的磁盘剩余空间"
        unit: byte
        job: "node-exporter"

    - expr: node_filesystem_size_bytes{job="node-exporter",fstype=~"ext4|xfs"} - node_filesystem_avail_bytes{job="node-exporter",fstype=~"ext4|xfs"}
      record: node_exporter:disk:usage:used
      labels: 
        desc: "节点的磁盘使用的空间"
        unit: byte
        job: "node-exporter"

    - expr:  (1 - node_filesystem_avail_bytes{job="node-exporter",fstype=~"ext4|xfs"} / node_filesystem_size_bytes{job="node-exporter",fstype=~"ext4|xfs"}) * 100 
      record: node_exporter:disk:used:percent    
      labels: 
        desc: "节点的磁盘的使用百分比"
        unit: "%"
        job: "node-exporter"
        
        
        
        
 ## 
  node_filesystem_avail_bytes{mountpoint="/home",fstype=~"ext4|xfs"}   /1000/1000/1000      #磁盘剩余量




### 报警规则
groups:
    - name: 主机状态-监控告警
      rules:
      - alert: 主机状态
        expr: up == 0
        for: 1m
        labels:
          status: 非常严重
        annotations:
          summary: "{{$labels.instance}}:服务器宕机"
          description: "{{$labels.instance}}:服务器延时超过1分钟"
      
      - alert: CPU使用情况
        expr: 100-(avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by(instance)* 100) > 80
        for: 1m
        labels:
          status: 一般告警
        annotations:
          summary: "{{$labels.mountpoint}} CPU使用率过高!"
          description: "{{$labels.mountpoint }} CPU使用大于80%(目前使用:{{$value}}%)"
  
      - alert: home目录可用量
        expr:  node_filesystem_avail_bytes{mountpoint="/home",fstype=~"ext4|xfs"}  /1073741824  < 4
        for: 1m
        labels:
          status: 严重告警
        annotations:
          value: "{{ $value }}"
          instance: "{{ $labels.instance }}"
          mountpoint: "{{$labels.mountpoint}}"
          summary: "实例在home挂载点磁盘可用量小于4G!, 当前可用: {{ $value }}G"
   
      - alert: root目录可用量
        expr:  node_filesystem_avail_bytes{mountpoint="/",fstype=~"ext4|xfs"}  /1073741824  < 4
        for: 1m
        labels:
          status: 严重告警
        annotations:
          value: "{{ $value }}"
          instance: "{{ $labels.instance }}"
          mountpoint: "{{$labels.mountpoint}}"
          summary: "实例在root挂载点磁盘可用量小于4G!, 当前可用: {{ $value }}G"

比较好