解决 Prometheus 监控 Kubernetes Job 误报的坑
最近有遇到 Prometheus 监控 Job 任务误报的问题,大概的意思就 CronJob 控制的 Job,前面执行失败了会触发报警,后面生成的新的 Job 可以正常执行后,但是还是会收到前面的报警。
这是因为一般在执行 Job 任务的时候我们会保留一些历史记录方便排查问题,所以如果之前有失败的 Job 了,即便稍后会变成成功的,那么之前的 Job 也会继续存在,而大部分直接使用 kube-prometheus 安装部署的话使用的默认报警规则是kube_job_status_failed > 0,这显然是不准确的,只有我们去手动删除之前这个失败的 Job 任务才可以消除误报,当然这种方式是可以解决问题的,但是不够自动化,一开始没有想得很深入,想去自动化删除失败的 Job 来解决,但是这也会给运维人员带来问题,就是不方便回头去排查问题。下面我们来重新整理下思路解决下这个问题。
CronJob 
   会在计划的每个执行时间创建一个 Job 对象,可以通过  
  .spec.successfulJobsHistoryLimit 
   和  
  .spec.failedJobsHistoryLimit 
   属性来保留多少已完成和失败的 Job,默认分别为3和1,比如下面声明一个  
  CronJob 
   的资源对象: 
 apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date;
          restartPolicy: OnFailure
NAME               COMPLETIONS   DURATION   AGE
hello-4111706356   0/1           2m         10d
hello-4111706356   1/1           5s         5s
kube-state-metrics 
   这个服务,它通过监听 Kubernetes APIServer 并生成有关对象状态的指标,它并不关注单个 Kubernetes 组件的健康状况,而是关注内部各种对象的健康状况,例如 Deployment、Node、Job、Pod 等资源对象的状态。这里我们将要使用到以下几个指标: 
 - kube_job_owner:用来查找 Job 和触发它的 CronJob 之间的关系
- kube_job_status_start_time:获取 Job 被触发的时间
- kube_job_status_failed:获取执行失败的任务
- kube_cronjob_spec_suspend:过滤掉挂起的作业
hello 
   任务生成的标签: 
 kube_job_owner{job_name="hello-1604875860", namespace="myNamespace", owner_is_controller="true", owner_kind="CronJob", owner_name="hello"} 1
kube_job_status_start_time{job_name="hello-1604875860", namespace="myNamespace"} 1604875874
kube_job_status_failed{job_name="hello-1604875860", namespace="myNamespace", reason="BackoffLimitExceeded"} 1
kube_cronjob_spec_suspend{cronjob="hello",job="kube-state-metrics", namespace="myNamespace"} 0
kube_job_status_failed 
   和  
  kube_job_status_start_time 
   指标中并不包含所属 CronJob 的标签,所以第一步需要加入这个标签,而  
  kube_job_owner 
   指标中的  
  owner_name 
   就是我们需要的,可以用下面的 promql 语句来进行合并: 
 max(
  kube_job_status_start_time
  * ON(job_name, namespace) GROUP_RIGHT()
  kube_job_owner{owner_name != ""}
  )
BY (job_name, owner_name, namespace)
max 
   函数是因为我们可能会因为 HA 运行多个 kube-state-metrics,所以用 max 函数来返回每个 Job 任务的一个结果即可。假设我们的 Job 历史记录包含 2 个任务(一个失败,另一个成功),结果将如下所示: 
 {job_name="hello-1623578940", namespace="myNamespace", owner_name="hello"} 1623578959
{job_name="hello-1617667200", namespace="myNamespace", owner_name="hello"} 1617667204
owner_name 
   标签聚合结果来实现这一点: 
 max(
  kube_job_status_start_time
  * ON(job_name,namespace) GROUP_RIGHT()
  kube_job_owner{owner_name!=""}
) 
BY (owner_name)
max(
 kube_job_status_start_time
 * ON(job_name,namespace) GROUP_RIGHT()
 kube_job_owner{owner_name!=""}
)
BY (job_name, owner_name, namespace)
== ON(owner_name) GROUP_LEFT()
max(
 kube_job_status_start_time
 * ON(job_name,namespace) GROUP_RIGHT()
 kube_job_owner{owner_name!=""}
)
BY (owner_name)
{job_name="hello-1623578940", namespace="myNamespace", owner_name="hello"} 1623578959
label_replace(
  label_replace(
    max(
      kube_job_status_start_time
      * ON(job_name,namespace) GROUP_RIGHT()
      kube_job_owner{owner_name!=""}
    )
    BY (job_name, owner_name, namespace)
    == ON(owner_name) GROUP_LEFT()
    max(
      kube_job_status_start_time
      * ON(job_name,namespace) GROUP_RIGHT()
      kube_job_owner{owner_name!=""}
    )
    BY (owner_name),
  "job", "$1", "job_name", "(.+)"),
"cronjob", "$1", "owner_name", "(.+)")
{job="hello-1623578940", cronjob="hello", job_name="hello-1623578940", namespace="myNamespace", owner_name="hello"} 1623578959
- record: job:kube_job_status_start_time:max
  expr: |
    label_replace(
      label_replace(
        max(
          kube_job_status_start_time
          * ON(job_name,namespace) GROUP_RIGHT()
          kube_job_owner{owner_name!=""}
        )
        BY (job_name, owner_name, namespace)
        == ON(owner_name) GROUP_LEFT()
        max(
          kube_job_status_start_time
          * ON(job_name,namespace) GROUP_RIGHT()
          kube_job_owner{owner_name!=""}
        )
        BY (owner_name),
      "job", "$1", "job_name", "(.+)"),
    "cronjob", "$1", "owner_name", "(.+)")
kube_job_status_failed 
   指标就可以了: 
 - record: job:kube_job_status_failed:sum
  expr: |
    clamp_max(job:kube_job_status_start_time:max, 1)
      * ON(job) GROUP_LEFT()
      label_replace(
        (kube_job_status_failed > 0),
        "job", "$1", "job_name", "(.+)"
      )
clamp_max 
   函数将  
  job:kube_job_status_start_time:max 
   的结果转换为一组上限为 1 的时间序列,使用它来通过乘法过滤失败的作业,得到包含一组最近失败的 Job 任务,这里我们也添加到名为  
  kube_job_status_failed:sum 
   的记录规则中。 
 - alert: CronJobStatusFailed
  expr: |
    job:kube_job_status_failed:sum
    * ON(cronjob, namespace) GROUP_LEFT()
    (kube_cronjob_spec_suspend == 0)
来源: K8s技术圈
推荐阅读
