Christos Markou

如何使用 Elastic 可观测性启用 Kubernetes 告警

在 Kubernetes 世界中,不同角色需要不同类型的洞察。在本文中,我们将重点介绍告警,并概述 Elastic 可观测性中的告警如何帮助用户快速识别 Kubernetes 问题。

阅读需要 24 分钟
How to enable Kubernetes alerting with Elastic Observability

在 Kubernetes 世界中,不同角色需要不同类型的洞察。开发人员对细粒度指标和调试信息感兴趣。SRE 则有兴趣一次性查看所有内容,以便在发生问题时快速收到通知,并找出根本原因。在本文中,我们将重点介绍告警,并概述 Elastic 可观测性中的告警如何帮助用户快速识别 Kubernetes 问题。

为什么我们需要告警?

日志、指标和追踪只是构建完整Kubernetes 集群监控解决方案的基础。它们的主要目标是为基础设施提供调试信息和历史证据。

虽然通过 Kibana 进行开箱即用的仪表板、基础设施拓扑和日志浏览已经非常方便执行临时分析,但添加通知和对基础设施的积极监控允许用户尽早处理检测到的问题,甚至主动采取行动来防止他们的 Kubernetes 环境面临更严重的问题。

如何实现这一点?

通过在他们的基础设施之上构建告警,用户可以利用数据并有效地将其与特定通知相关联,从而创建各种可能性来动态监控和观察他们的 Kubernetes 集群。

在这篇博文中,我们将探讨用户如何利用 Elasticsearch 的搜索能力来定义告警规则,以便在发生特定条件时收到通知。

SLI、告警和 SLO:为什么它们对 SRE 很重要?

对于站点可靠性工程师 (SRE) 而言,事件响应时间与日常工作的成功密切相关。监控、告警和操作将有助于发现、解决或预防系统中的问题。

  • SLA(服务级别协议)是您与用户创建的协议,用于指定他们可以期望的服务级别。
  • SLO(服务级别目标)是 SLA 中关于特定指标(如正常运行时间或响应时间)的协议。
  • SLI(服务级别指标)衡量是否符合 SLO。

SRE 的日常任务和项目由 SLO 驱动。通过确保在短期内捍卫 SLO 并使其在中长期内得以维持,我们奠定了稳定工作基础设施的基础。

有了上述内容,识别 SLO 的高级类别对于组织 SRE 的工作至关重要。然后在每个 SLO 类别中,SRE 都需要相应的 SLI,这些 SLI 可以涵盖他们正在观察的系统的大部分重要案例。因此,我们需要哪些 SLI 的决定需要对底层系统基础设施的额外了解。

一种广泛用于对 SLI 和 SLO 进行分类的方法是四个黄金信号方法。定义的类别是延迟、流量、错误和饱和度。

一种更具体的方法是 Tom Wilkie 开发的RED 方法,他曾是 Google 的 SRE,并使用了四个黄金信号。RED 方法删除了饱和度类别,因为此类别主要用于更高级的案例 - 而且人们更容易记住三个一组的事物。

关注 Kubernetes 基础设施操作员,我们将考虑以下几组基础设施 SLI/SLO

  • 第 1 组:控制平面的延迟(apiserver,
  • 第 2 组:节点/Pod 的资源利用率(消耗多少 CPU、内存等)
  • 第 3 组:错误(日志或事件中的错误或来自组件、网络等的错误计数)

为 Kubernetes 集群创建告警

现在我们已经完整概述了基于 SLI/SLO 定义告警的目标,我们将深入研究如何定义正确的告警。可以使用 Kibana 构建告警。

请参阅 Elastic 文档

在本博文中,我们将根据 Watcher 的功能提供的复杂 Elasticsearch 查询来定义更复杂的告警。阅读有关 Watcher 的更多信息以及如何在本文中的示例之外正确使用它。

延迟告警

对于这种告警,我们要为 Kubernetes 控制平面定义基本 SLO,这将确保基本的控制平面组件可以毫无问题地为最终用户提供服务。例如,在针对 Kubernetes API Server 的查询中面临高延迟足以表明需要采取行动。

资源饱和度

下一组告警将是资源利用率。节点的 CPU 利用率或节点状况的变化对于集群至关重要,以确保平稳地为配置为运行最终用户将与之交互的应用程序的工作负载提供服务。

错误检测

最后但并非最不重要的一点是,我们将根据特定错误定义告警,例如网络错误率或 Pod 的故障(例如 OOMKilled 情况)。对于 SRE 团队来说,这是一个非常有用的指标,可以检测基础设施层面的问题,或者只是能够通知开发团队有问题的负载。我们稍后将研究的一个示例是,应用程序作为 Pod 运行,并且因为它达到了内存限制而不断重启。在这种情况下,需要通知此应用程序的所有者采取适当的措施。

从 Kubernetes 数据到 Elasticsearch 查询

在制定了关于我们要实施的告警的可靠计划后,现在是时候探索我们从 Kubernetes 集群收集并存储在 Elasticsearch 中的数据了。为此,我们将查阅使用 Elastic Agent Kubernetes 集成摄取的可用数据字段列表(可以在此处找到完整的字段列表)。使用这些字段,我们可以创建各种告警,例如

  • 节点 CPU 利用率
  • 节点内存利用率
  • 带宽利用率
  • Pod 重启
  • Pod CPU/内存利用率

CPU 利用率告警

我们的第一个示例将使用 CPU 利用率字段来计算节点的 CPU 利用率并创建告警。对于此告警,我们利用指标

kubernetes.node.cpu.usage.nanocores
kubernetes.node.cpu.capacity.cores.

以下计算 (nodeUsage / 1000000000 ) /nodeCap 按节点名称分组将为我们提供集群节点的 CPU 利用率。

可以使用以下 API 调用 Elasticsearch 创建实现此查询的 Watcher 定义

curl -X PUT "https://elastic:changeme@localhost:9200/_watcher/watch/Node-CPU-Usage?pretty" -k -H 'Content-Type: application/json' -d'
{
  "trigger": {
    "schedule": {
      "interval": "10m"
    }
  },
  "input": {
    "search": {
      "request": {
        "body": {
          "size": 0,
          "query": {
            "bool": {
              "must": [
                {
                  "range": {
                    "@timestamp": {
                      "gte": "now-10m",
                      "lte": "now",
                      "format": "strict_date_optional_time"
                    }
                  }
                },
                {
                  "bool": {
                    "must": [
                      {
                        "query_string": {
                          "query": "data_stream.dataset: kubernetes.node OR data_stream.dataset: kubernetes.state_node",
                          "analyze_wildcard": true
                        }
                      }
                    ],
                    "filter": [],
                    "should": [],
                    "must_not": []
                  }
                }
              ],
              "filter": [],
              "should": [],
              "must_not": []
            }
          },
          "aggs": {
            "nodes": {
              "terms": {
                "field": "kubernetes.node.name",
                "size": "10000",
                "order": {
                  "_key": "asc"
                }
              },
              "aggs": {
                "nodeUsage": {
                  "max": {
                    "field": "kubernetes.node.cpu.usage.nanocores"
                  }
                },
                "nodeCap": {
                  "max": {
                    "field": "kubernetes.node.cpu.capacity.cores"
                  }
                },
                "nodeCPUUsagePCT": {
                  "bucket_script": {
                    "buckets_path": {
                      "nodeUsage": "nodeUsage",
                      "nodeCap": "nodeCap"
                    },
                    "script": {
                      "source": "( params.nodeUsage / 1000000000 ) / params.nodeCap",
                      "lang": "painless",
                      "params": {
                        "_interval": 10000
                      }
                    },
                    "gap_policy": "skip"
                  }
                }
              }
            }
          }
        },
        "indices": [
          "metrics-kubernetes*"
        ]
      }
    }
  },
  "condition": {
    "array_compare": {
      "ctx.payload.aggregations.nodes.buckets": {
        "path": "nodeCPUUsagePCT.value",
        "gte": {
          "value": 80
        }
      }
    }
  },
  "actions": {
    "log_hits": {
      "foreach": "ctx.payload.aggregations.nodes.buckets",
      "max_iterations": 500,
      "logging": {
        "text": "Kubernetes node found with high CPU usage: {{ctx.payload.key}} -> {{ctx.payload.nodeCPUUsagePCT.value}}"
      }
    }
  },
  "metadata": {
    "xpack": {
      "type": "json"
    },
    "name": "Node CPU Usage"
  }
}

OOMKilled Pod 检测和告警

我们将探讨的另一个 Watcher 是检测由于 OOMKilled 错误而重启的 Pod 的 Watcher。此错误在 Kubernetes 工作负载中非常常见,并且可以尽早检测到此错误,以通知拥有此工作负载的团队,以便他们可以调查可能导致内存泄漏的问题,或者只是考虑增加工作负载本身所需的资源。

此信息可以从如下查询中检索

kubernetes.container.status.last_terminated_reason: OOMKilled

以下是如何使用 API 调用创建相应的 Watcher

curl -X PUT "https://elastic:changeme@localhost:9200/_watcher/watch/Pod-Terminated-OOMKilled?pretty" -k -H 'Content-Type: application/json' -d'
{
  "trigger": {
    "schedule": {
      "interval": "1m"
    }
  },
  "input": {
    "search": {
      "request": {
        "search_type": "query_then_fetch",
        "indices": [
          "*"
        ],
        "rest_total_hits_as_int": true,
        "body": {
          "size": 0,
          "query": {
            "bool": {
              "must": [
                {
                  "range": {
                    "@timestamp": {
                      "gte": "now-1m",
                      "lte": "now",
                      "format": "strict_date_optional_time"
                    }
                  }
                },
                {
                  "bool": {
                    "must": [
                      {
                        "query_string": {
                          "query": "data_stream.dataset: kubernetes.state_container",
                          "analyze_wildcard": true
                        }
                      },
                      {
                        "exists": {
                          "field": "kubernetes.container.status.last_terminated_reason"
                        }
                      },
                      {
                        "query_string": {
                          "query": "kubernetes.container.status.last_terminated_reason: OOMKilled",
                          "analyze_wildcard": true
                        }
                      }
                    ],
                    "filter": [],
                    "should": [],
                    "must_not": []
                  }
                }
              ],
              "filter": [],
              "should": [],
              "must_not": []
            }
          },
          "aggs": {
            "pods": {
              "terms": {
                "field": "kubernetes.pod.name",
                "order": {
                  "_key": "asc"
                }
              }
            }
          }
        }
      }
    }
  },
  "condition": {
    "array_compare": {
      "ctx.payload.aggregations.pods.buckets": {
        "path": "doc_count",
        "gte": {
          "value": 1,
          "quantifier": "some"
        }
      }
    }
  },
  "actions": {
    "ping_slack": {
      "foreach": "ctx.payload.aggregations.pods.buckets",
      "max_iterations": 500,
      "webhook": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/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",
        "body": "{\"channel\": \"#k8s-alerts\", \"username\": \"k8s-cluster-alerting\", \"text\": \"Pod {{ctx.payload.key}} was terminated with status OOMKilled.\"}"
      }
    }
  },
  "metadata": {
    "xpack": {
      "type": "json"
    },
    "name": "Pod Terminated OOMKilled"
  }
}

从 Kubernetes 数据到告警摘要

到目前为止,我们已经了解了如何从普通的 Kubernetes 字段开始,在 ES 查询中使用它们,并在其基础上构建 Watcher 和警报。

可以按照我们在此处提供的示例,探索更多可能的数据组合并构建查询和警报。 完整的警报列表以及安装它们的基本脚本方式均可获取。

当然,这些示例都带有简单的操作定义,仅将消息记录到 Elasticsearch 日志中。 但是,可以使用更高级且有用的输出,例如 Slack 的 Webhook。

"actions": {
    "ping_slack": {
      "foreach": "ctx.payload.aggregations.pods.buckets",
      "max_iterations": 500,
      "webhook": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/T04SW3JHXasdfasdfasdfasdfasdf",
        "body": "{\"channel\": \"#k8s-alerts\", \"username\": \"k8s-cluster-alerting\", \"text\": \"Pod {{ctx.payload.key}} was terminated with status OOMKilled.\"}"
      }
    }
  }

结果将是如下所示的 Slack 消息

下一步

在我们的下一步中,我们希望将这些警报纳入我们的 Kubernetes 集成中,这意味着当用户安装或启用 Kubernetes 集成时,将安装预定义的警报。 同时,我们计划将其中一些实现为 Kibana 的原生 SLI,从而为我们的用户提供通过友好的用户界面在 SLI 之上快速定义 SLO 的选项。 如果您有兴趣了解更多相关信息,请关注公共 GitHub 问题以获取更多信息,并随时提供您的反馈。

对于那些渴望今天就开始使用 Kubernetes 警报的人,以下是您需要执行的操作

  1. 确保您已启动并运行 Elastic 集群。 部署集群的最快方法是启动 Elasticsearch 服务的免费试用版
  2. 按照相应的文档在 Kubernetes 集群上安装最新的 Elastic Agent。
  3. 安装我们提供的警报,可以在 https://github.com/elastic/integrations/tree/main/packages/kubernetes/docshttps://github.com/elastic/k8s-integration-infra/tree/main/scripts/alerting 找到。

当然,如果您有任何问题,请记住我们始终乐于在 Discuss 论坛上提供帮助。

分享这篇文章