Prometheus 监控实战 06:Flink 异常检测与告警规则配置


前面已经把 Prometheus、Flink 指标和 Grafana 看板都串起来了。到这里,监控链路已经能看到指标,但还缺少最关键的一步:指标异常时不能只靠人工查看,而应该由 Prometheus 主动计算规则并触发告警。

Flink 作业运行时,比较常见的问题有几类:

  • JobManager 或 TaskManager 进程挂掉
  • TaskManager 重启或反复抖动
  • TaskManager 从 JobManager 中注销,导致可用 Slot 下降
  • 预期长期运行的作业不再运行

这些问题如果只靠人工看 Grafana,很容易错过。更合适的做法是把这些判断写成 Prometheus 告警规则,让 Prometheus 定期计算规则,并在异常时进入 pending 或 firing 状态。

这一篇先配置 Prometheus 告警规则,并通过停止 Flink TaskManager 的方式验证规则是否真实触发。

1. 环境说明

当前仍然使用前几篇文章中的同一台服务器,Prometheus 和 Flink 已经配置完成:

hostname
date '+%Y-%m-%d %H:%M:%S'
/opt/module/prometheus-3.2.1/promtool --version
curl -s http://localhost:8081/overview

输出如下:

lavm-bzoq5mwl1h
2025-05-29 20:07:22

promtool, version 3.2.1

{"taskmanagers":1,"slots-total":1,"slots-available":1,"jobs-running":0,"jobs-finished":0,"jobs-cancelled":0,"jobs-failed":0,"flink-version":"1.20.1","flink-commit":"cb1e7b5"}

当前实验环境没有提交长期运行的 Flink 作业,所以后面的 FlinkNoRunningJobs 会触发,这是符合预期的。

当前 Prometheus 已经采集 Flink 的两个 Reporter 端口:

job target 说明
flink_jobmanager localhost:9249 JobManager 指标
flink_taskmanager localhost:9250 TaskManager 指标

这里的 localhost 只适用于 Prometheus 和 Flink 部署在同一台机器上的实验环境;如果 Prometheus 单独部署,需要改成 Flink 所在机器的内网 IP 或 DNS。9249/9250 是 Flink Reporter 暴露指标的 HTTP 端口,生产环境不建议直接公网开放,应通过内网、安全组、防火墙或服务发现方式采集。

先确认采集状态:

curl -G -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=up{job="flink_taskmanager"}'

TaskManager 正常时返回。下面只截取 result 数组中的关键部分,不是完整 JSON 返回:

{
  "metric": {
    "__name__": "up",
    "instance": "localhost:9250",
    "job": "flink_taskmanager"
  },
  "value": [
    1748520442.282,
    "1"
  ]
}

up=1 说明 Prometheus 最近一次 scrape 可以正常拉取 TaskManager 指标,但不代表 TaskManager 业务状态完全正常,也不代表作业一定正常运行。后面的告警规则就是围绕这些指标来判断异常。

Prometheus 告警状态可以简单分成三种:

状态 含义
inactive 表达式不成立
pending 表达式成立,但还没有持续超过 for
firing 表达式成立,并且持续超过 for

for 的作用就是过滤短暂抖动。需要注意,PromQL 告警表达式返回空结果时不会触发告警;只有返回的时间序列满足条件时,才会进入 pending 或 firing。如果一个表达式返回多个 instance,每个 instance 都会生成一条独立告警。

2. 增加 rule_files 配置

Prometheus 的告警规则一般单独放到规则文件里,然后在 prometheus.yml 中通过 rule_files 引入。

当前配置文件路径是:

/opt/module/prometheus-3.2.1/prometheus.yml

修改前先备份原配置:

cp /opt/module/prometheus-3.2.1/prometheus.yml \
   /opt/module/prometheus-3.2.1/prometheus.yml.bak

只需要在原配置中新增 rule_files,不要删除已有 scrape_configs。下面是当前实验环境的完整示例配置,请根据自己已有配置合并,不要盲目覆盖:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "rules/*.yml"

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "node_exporter"
    static_configs:
      - targets: ["localhost:9100"]

  - job_name: "pushgateway"
    honor_labels: true
    static_configs:
      - targets: ["localhost:9091"]

  - job_name: "flink_jobmanager"
    static_configs:
      - targets: ["localhost:9249"]

  - job_name: "flink_taskmanager"
    static_configs:
      - targets: ["localhost:9250"]

这里有两个时间参数要注意:

参数 说明
scrape_interval Prometheus 抓取指标的间隔
evaluation_interval Prometheus 计算规则的间隔

如果 evaluation_interval 是 15s,规则表达式每 15 秒计算一次。规则里再配合 for: 30s、for: 1m 这样的持续时间,可以避免短暂网络抖动直接触发告警。

rules/.yml 是相对路径,相对于 prometheus.yml 所在目录解析。本文的配置文件在 /opt/module/prometheus-3.2.1/ 下,所以实际会匹配 /opt/module/prometheus-3.2.1/rules/.yml。规则文件后缀需要和 rule_files 中的匹配模式一致。

for: 30s 不一定精确等于 30 秒触发,实际会受到 evaluation_interval 影响,通常需要经过多次规则计算后才会从 pending 进入 firing。如果 evaluation_interval 比 scrape_interval 短,规则可能多次基于同一次采样值计算;如果比 scrape_interval 长,告警发现会更慢。

创建规则目录:

mkdir -p /opt/module/prometheus-3.2.1/rules

创建规则文件:

vim /opt/module/prometheus-3.2.1/rules/flink-alerts.yml

当前文件目录结构如下:

/opt/module/prometheus-3.2.1/
  prometheus.yml
  rules/
    flink-alerts.yml

内容如下:

groups:
  - name: flink.rules
    rules:
      - alert: FlinkJobManagerDown
        expr: up{job="flink_jobmanager"} == 0
        for: 30s
        labels:
          severity: critical
          service: flink
        annotations:
          summary: "Flink JobManager metrics endpoint is down"
          description: "Prometheus cannot scrape Flink JobManager metrics. cluster={{ $labels.cluster }}, job={{ $labels.job }}, instance={{ $labels.instance }}."

      - alert: FlinkTaskManagerDown
        expr: up{job="flink_taskmanager"} == 0
        for: 30s
        labels:
          severity: critical
          service: flink
        annotations:
          summary: "Flink TaskManager metrics endpoint is down"
          description: "Prometheus cannot scrape Flink TaskManager metrics. cluster={{ $labels.cluster }}, job={{ $labels.job }}, instance={{ $labels.instance }}."

      - alert: FlinkTaskManagerEndpointFlapping
        expr: changes(up{job="flink_taskmanager"}[10m]) > 1
        labels:
          severity: warning
          service: flink
        annotations:
          summary: "Flink TaskManager metrics endpoint is flapping"
          description: "Flink TaskManager scrape state changed more than once in 10 minutes. cluster={{ $labels.cluster }}, job={{ $labels.job }}, instance={{ $labels.instance }}, value={{ $value }}."

      - alert: FlinkTaskManagerMissing
        expr: flink_jobmanager_numRegisteredTaskManagers < 1
        for: 1m
        labels:
          severity: warning
          service: flink
        annotations:
          summary: "Flink registered TaskManager count is too low"
          description: "Current registered TaskManager count is {{ $value }}. cluster={{ $labels.cluster }}, job={{ $labels.job }}, instance={{ $labels.instance }}."

      - alert: FlinkNoRunningJobs
        expr: flink_jobmanager_numRunningJobs < 1
        for: 2m
        labels:
          severity: warning
          service: flink
        annotations:
          summary: "No running Flink jobs"
          description: "No running Flink jobs were observed in the current cluster. cluster={{ $labels.cluster }}, job={{ $labels.job }}, instance={{ $labels.instance }}."

这几个规则分别对应不同层面的异常。

告警 表达式 说明
FlinkJobManagerDown up{job=”flink_jobmanager”} == 0 JobManager Reporter 不可采集
FlinkTaskManagerDown up{job=”flink_taskmanager”} == 0 TaskManager Reporter 不可采集
FlinkTaskManagerEndpointFlapping changes(up{job=”flink_taskmanager”}[10m]) > 1 TaskManager Reporter 采集状态在 10 分钟内发生多次变化
FlinkTaskManagerMissing flink_jobmanager_numRegisteredTaskManagers < 1 JobManager 视角下没有可用 TaskManager
FlinkNoRunningJobs flink_jobmanager_numRunningJobs < 1 当前没有运行中的作业

这里要特别注意 FlinkNoRunningJobs。这个规则只适合有长期运行作业的集群,比如常驻的流式作业。如果当前 Flink 集群只是临时提交批任务,或者本来就允许没有运行作业,就不应该直接使用这条规则。多集群环境中应该加 cluster、env、job_type 等标签过滤,例如:

flink_jobmanager_numRunningJobs{job_type="streaming"} < 1

FlinkTaskManagerMissing 也只适合当前实验环境这种“期望至少 1 个 TaskManager”的场景。生产环境应该根据集群规模设置阈值,比如 < 3,或者结合 cluster 标签分别设置不同阈值。它依赖 JobManager Reporter 正常暴露指标,因此应和 FlinkJobManagerDown 一起看。

FlinkJobManagerDown 和 FlinkTaskManagerDown 只能检测已经存在于 Prometheus target 列表中的 Reporter 不可抓取。如果 scrape_configs 没有配置该 target,或者 job 标签写错,up == 0 不会触发。这类规则不能替代 target 缺失检查。可以用 absent(up{job=”flink_taskmanager”}) 发现完全没有对应序列的情况,但动态环境下容易误报,需要谨慎使用。

FlinkTaskManagerEndpointFlapping 看的是一段时间内 up 状态是否反复变化,它更偏向检测采集抖动或端点不稳定,不一定能精确代表 TaskManager 进程重启。网络抖动、Reporter 卡顿、Prometheus 采集失败也可能造成 up 变化。不配置 for 时,表达式一旦为 true 就会直接 firing。

生产环境建议在 scrape_configs 中给 Flink targets 增加 cluster/env 标签,并在告警 labels 或 annotations 中带上这些标签,方便定位是哪个集群触发。severity 也要根据集群规模和业务重要性调整,本文只是实验示例。labels 会决定告警身份和 Alertmanager 分组方式,生产环境中 alertname、cluster、instance、severity 等标签设计要稳定;annotations 主要用于展示描述信息,不参与告警身份判断,不建议把频繁变化的值放到 labels 中。

4. 校验规则文件

Prometheus 自带 promtool,可以先检查规则文件语法:

/opt/module/prometheus-3.2.1/promtool check rules \
  /opt/module/prometheus-3.2.1/rules/flink-alerts.yml

输出如下:

Checking /opt/module/prometheus-3.2.1/rules/flink-alerts.yml
  SUCCESS: 5 rules found

每次修改规则后都应该先 check rules,再 check config,确认规则文件语法正确,并且已经被 prometheus.yml 正确引用。再检查整体配置:

/opt/module/prometheus-3.2.1/promtool check config \
  /opt/module/prometheus-3.2.1/prometheus.yml

输出:

Checking /opt/module/prometheus-3.2.1/prometheus.yml
  SUCCESS: 1 rule files found
  SUCCESS: /opt/module/prometheus-3.2.1/prometheus.yml is valid prometheus config file syntax

规则文件修改后,Prometheus 不会自动加载,需要 reload 或重启。Prometheus 如果启动时加了 –web.enable-lifecycle,可以执行:

curl -X POST http://localhost:9090/-/reload

本文前面没有开启 lifecycle,所以这里使用重启方式。实验环境可以直接重启;生产环境建议使用 systemd 管理 Prometheus,并考虑 reload 或滚动方式,避免影响监控采集。

cd /opt/module/prometheus-3.2.1

pkill -f "/opt/module/prometheus-3.2.1/prometheus"

nohup ./prometheus \
  --config.file=/opt/module/prometheus-3.2.1/prometheus.yml \
  --storage.tsdb.path=/opt/module/prometheus-3.2.1/data \
  --web.listen-address=0.0.0.0:9090 \
  > prometheus.log 2>&1 &

Prometheus 默认没有登录认证,0.0.0.0:9090 不建议直接暴露到公网。生产环境建议绑定内网地址,或通过反向代理、VPN、网关认证访问。启动后确认服务已经 ready:

curl -s http://localhost:9090/-/ready
tail -n 50 /opt/module/prometheus-3.2.1/prometheus.log

打开 Prometheus Web UI 中的 Rules / Rule health 页面,可以看到 flink.rules 规则组已经加载:

规则状态为 OK,说明 Prometheus 能正常解析并计算这些表达式。

如果某条告警不触发,可以先把 expr 复制到 Prometheus Graph 查询页,确认表达式是否有返回值。返回空结果和返回 0 是两回事:空结果不会触发告警。

5. 模拟 TaskManager 异常

为了验证告警是否真的能触发,这里手动停止 TaskManager:

cd /opt/module/flink-1.20.1
jps | grep TaskManagerRunner
bin/taskmanager.sh stop

本文只是在 Standalone 实验环境中模拟异常;生产环境或 YARN/Kubernetes 模式不要随意手动停止 TaskManager,应使用测试环境验证告警。

停止前,TaskManager 采集状态为 1:

curl -G -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=up{job="flink_taskmanager"}'

返回。下面仍然只展示 Prometheus API result 中的关键部分:

{
  "metric": {
    "__name__": "up",
    "instance": "localhost:9250",
    "job": "flink_taskmanager"
  },
  "value": [
    1748520442.282,
    "1"
  ]
}

停止 TaskManager 后,等待两个采集周期再查询。当前 scrape_interval 是 15s,两个采集周期大约是 30 秒;告警进入 firing 还会受到 evaluation_interval 和 for 影响:

curl -G -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=up{job="flink_taskmanager"}'

返回:

{
  "metric": {
    "__name__": "up",
    "instance": "localhost:9250",
    "job": "flink_taskmanager"
  },
  "value": [
    1748520519.934,
    "0"
  ]
}

此时 up=0,说明 Prometheus 已经无法采集 TaskManager 的 /metrics 端口。

继续查看当前告警:

curl -s http://localhost:9090/api/v1/alerts | jq

如果系统没有 jq,可以去掉 | jq。下面只截取 alerts 数组中的关键字段。实际从异常到 firing 的时间取决于 scrape_interval、evaluation_interval 和 for,FlinkTaskManagerMissing 可能比 FlinkTaskManagerDown 更晚进入 firing:

{
  "labels": {
    "alertname": "FlinkTaskManagerDown",
    "instance": "localhost:9250",
    "job": "flink_taskmanager",
    "service": "flink",
    "severity": "critical"
  },
  "state": "firing",
  "value": "0e+00"
}
{
  "labels": {
    "alertname": "FlinkTaskManagerMissing",
    "instance": "localhost:9249",
    "job": "flink_jobmanager",
    "service": "flink",
    "severity": "warning"
  },
  "state": "firing",
  "value": "0e+00"
}

Prometheus 页面里也能看到对应告警:

截图里还能看到 FlinkNoRunningJobs,这是因为当前实验环境没有提交长期运行的 Flink 作业,flink_jobmanager_numRunningJobs 本来就是 0。为了避免误以为配置错误,实验验证前也可以先临时注释掉 FlinkNoRunningJobs,或者把它作为可选规则。如果生产集群里允许某些时间段没有运行作业,这条规则就应该去掉,或者增加 cluster、job_type 这类标签后只对流式作业集群启用。

这里有一个细节:FlinkTaskManagerDown 是 Prometheus 从采集侧看到的异常,表示 localhost:9250 不可采集,可能是进程挂了、端口不通、网络问题或 Reporter 异常;FlinkTaskManagerMissing 是 JobManager 指标里看到的异常,表示当前注册的 TaskManager 数量为 0,偏 Flink 集群内部状态。两个规则看的是同一类问题,但角度不同。

如果 FlinkTaskManagerDown 触发但 FlinkTaskManagerMissing 没触发,可能是 Reporter 端口或网络采集问题;如果两个都触发,更可能是 TaskManager 真的退出或无法注册到 JobManager。实际排查时,这两个告警如果同时出现,基本可以优先检查 TaskManager 进程、端口、日志和机器状态。

6. 恢复 TaskManager

验证完成后,重新启动 TaskManager:

cd /opt/module/flink-1.20.1
bin/taskmanager.sh start

再次查看 Flink Overview:

curl -s http://localhost:8081/overview

返回:

{
  "taskmanagers": 1,
  "slots-total": 1,
  "slots-available": 1,
  "jobs-running": 0,
  "jobs-finished": 0,
  "jobs-cancelled": 0,
  "jobs-failed": 0,
  "flink-version": "1.20.1",
  "flink-commit": "cb1e7b5"
}

再确认 Prometheus 采集和告警状态:

curl -G -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=up{job="flink_taskmanager"}'

curl -s http://localhost:9090/api/v1/alerts | jq

TaskManager 恢复后,仍需等待 scrape_interval + evaluation_interval,告警不会立即消失。FlinkTaskManagerDown 和 FlinkTaskManagerMissing 会在后续规则计算中恢复为 inactive。Prometheus 页面中的 Alerts 状态会自动更新;如果已经接入 Alertmanager,通知恢复还取决于 Alertmanager 的 resolve 逻辑和通知配置。

7. 关于告警发送

这一篇只配置了 Prometheus 规则,所以告警可以在 Prometheus 页面上看到 pending 和 firing 状态。

如果要真正发到外部系统,还需要接 Alertmanager。Prometheus 负责判断规则是否触发,Alertmanager 负责后续的分组、静默、抑制和通知发送。

一个最小的 Prometheus 告警发送配置大致如下:

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - "localhost:9093"

这段配置需要放在 prometheus.yml 顶层,不能放在 scrape_configs 下面。它只是 Prometheus 发送告警到 Alertmanager 的配置,前提是 Alertmanager 已经单独安装并监听在 9093。

也就是说,Prometheus 告警链路可以拆成两段:

阶段 组件 作用
规则判断 Prometheus 定期计算 PromQL,产生 pending/firing
告警发送 Alertmanager 接收告警,分组、静默、抑制、发送通知

Prometheus 只负责产生告警状态;Alertmanager 不重新计算 PromQL,只负责接收、分组、静默、抑制和发送通知。如果当前只是先验证规则是否正确,可以先不接 Alertmanager;如果已经要用于生产,就应该把 Alertmanager 和通知通道一起补上,同时配置告警分组、重复发送间隔、静默规则、抑制规则和通知接收人,避免告警风暴。

8. 规则设计建议

Flink 告警规则可以按排查链路逐层设计:

  1. 先从 target 是否 UP 开始,确认 Prometheus 能不能抓到 Reporter。
  2. 再看 JobManager 视角下的 TaskManager 数量,确认 Flink 集群内部状态。
  3. 再看作业数量、Checkpoint、失败重启、延迟、吞吐、反压、GC 和内存等业务指标。
  4. 每条规则都要明确适用场景和误报条件,尤其是空闲集群、批任务集群和动态扩缩容环境。

本文只覆盖基础链路告警,还没有覆盖作业失败、Checkpoint 失败、反压、延迟、吞吐、GC、内存等更深入的 Flink 作业级告警。规则多起来后,也可以按类型拆成多个文件,例如 flink-target-alerts.yml、flink-job-alerts.yml、flink-resource-alerts.yml,便于维护。

9. 小结

这一篇把 Flink 的几个基础异常写成了 Prometheus 告警规则,并通过停止 TaskManager 验证了告警能够真实触发。

到这里,Prometheus 监控 Flink 的基础链路已经完整了:Prometheus 采集指标,PromQL 查询指标,Grafana 展示看板,Prometheus 规则负责发现异常。后续如果要继续完善,就可以把规则拆得更细,例如按集群、作业、队列、环境打标签,再接入 Alertmanager 做通知分发。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录