Kubernetes HPA之custom-metrics-server

1,323 阅读2分钟

1、架构图

image.png

2、安装custom metrics adapter


$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
$ helm install my-release prometheus-community/prometheus-adapter

3、指标发现和配置展示(Metrics Discovery and Presentation Configuration)

prometheus-adapter通过一组“Discovery”规则确定公开哪些指标以及如何公开它们。每个规则都是独立执行的,并指定prometheus-adapter 在API 中公开指标所需采取的每个步骤。以下面规则为例:

rules:
# this rule matches cumulative cAdvisor metrics measured in seconds
- seriesQuery: '{__name__=~"^container_.*",container!="POD",namespace!="",pod!=""}'
  resources:
    # skip specifying generic resource<->label mappings, and just
    # attach only pod and namespace resources by mapping label names to group-resources
    overrides:
      namespace: {resource: "namespace"}
      pod: {resource: "pod"}
  # specify that the `container_` and `_seconds_total` suffixes should be removed.
  # this also introduces an implicit filter on metric family names
  name:
    # we use the value of the capture group implicitly as the API name
    # we could also explicitly write `as: "$1"`
    matches: "^container_(.*)_seconds_total$"
  # specify how to construct a query to fetch samples for a given series
  # This is a Go template where the `.Series` and `.LabelMatchers` string values
  # are available, and the delimiters are `<<` and `>>` to avoid conflicts with
  # the prometheus query language
  metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[2m])) by (<<.GroupBy>>)"

每个规则可以大致分为四个部分:

  • Discovery,它指定prometheus-adapter应如何找到此规则的所有 Prometheus 指标,对应配置文件中seriesQuery
  • Association,它指定prometheus-adapter应如何确定特定指标与哪些 Kubernetes 资源相关联,对应配置文件中resources
  • Naming,它指定prometheus-adapter应如何在custom-metrics API 中公开指标,对应配置文件中name
  • Querying,它指定对一个或多个 Kubernetes 对象的特定指标的请求应如何转换为对 Prometheus 的查询,对应配置文件中metricsQuery

每个部分的细节可参考:github.com/kubernetes-…

4、我们以获取Per-pod HTTP Requests为例

4.1 背景

假设我们有一些web服务,我们正在尝试为 Prometheus adapter编写一个配置,以便我们可以根据它每秒接收到的 HTTP 请求自动缩放它。

在开始之前,我们已经开始使用指标http_requests_total对我们的web服务器进行检测,它使用单个标签公开, method按 HTTP 动词分解请求。

我们配置了 Prometheus 来收集指标,并添加了kubernetes_namespacekubernetes_pod_name标签,分别代表命namespace 和 pod。

如果我们查询Prometheus,我们看到的系列看起来像

http_requests_total{method="GET",kubernetes_namespace="production",kubernetes_pod_name="frontend-server-abcd-0123"}
4.2 配置适配器

适配器通过以下方式考虑指标:

  1. 首先,它发现可用的指标 ( Discovery )
  2. 然后,它找出每个指标与哪些 Kubernetes 资源相关联(Association
  3. 然后,它会弄清楚应该如何将它们暴露给自定义指标 API(name
  4. 最后,它弄清楚应该如何查询 Prometheus 以获取实际数字(Querying

我们需要通知适配器它应该如何为我们的指标执行这些步骤中的每一个,http_requests_total因此我们需要添加一个新 规则。适配器中的每个规则都对这些步骤进行编码。让我们在配置中添加一个新的:

rules:
- {}

http_requests_total如果我们想自己在 Prometheus仪表板中查找所有系列,我们将编写代码以查找与命名空间和 pod 关联的http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}所有系列。http_requests_total

我们可以将其添加到我们的规则中seriesQuery,告诉适配器如何发现正确的系列:

rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'

接下来,我们需要告诉适配器如何确定哪些 Kubernetes 资源与指标相关, kubernetes_namespace代表namespace名称, kubernetes_pod_name代表pod名称。由于这些名称不太遵循一致的模式,因此我们在规则中使用字段overrides部分 :resources

rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
  resources:
    overrides:
      kubernetes_namespace: {resource: "namespace"}
      kubernetes_pod_name: {resource: "pod"}

这表示每个标签代表其对应的资源。由于资源位于“core”kubernetes API 中,因此我们不需要指定组。适配器会自动处理复数,所以我们可以指定podor pods,和使用kubectl get是一样的。资源可以是 kubernetes 集群中可用的任何资源,只要您有相应的标签即可。

如果我们的标签遵循一致的模式,比如kubernetes_<resource>,我们可以指定resources: {template: "kubernetes_<<.Resource>>"} 而不是为每个资源指定overrides。如果您想查看集群中当前可用的所有资源,可以使用该 kubectl api-resources命令(但可用资源列表可能会随着您添加或删除 CRD 或聚合的 API 服务器而改变), 现在,累积指标(如以 结尾的指标_total)对于自动缩放不是特别有用,因此我们希望将它们转换为 API 中的速率指标。我们将调用我们的指标的rate version http_requests_per_second。我们可以使用name该字段来告知适配器:

rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
  resources:
    overrides:
      kubernetes_namespace: {resource: "namespace"}
      kubernetes_pod_name: {resource: "pod"}
  name:
    matches: "^(.*)_total"
    as: "${1}_per_second"

在这里,我们已经说过,我们应该将名称匹配 <something>_total,并将其转换为<something>_per_second

最后,我们需要告诉适配器如何实际查询 Prometheus 以获得一些数字。由于我们想要一个速率,我们可能会写: sum(rate(http_requests_total{kubernetes_namespace="production",kubernetes_pod_name=~"frontend-server-abcd-0123|fronted-server-abcd-4567"}) by (kubernetes_pod_name),这将使我们得到每个pod 每秒的总请求数,通过动词求和。

我们可以在适配器中编写类似的东西,使用metricsQuery 字段:

rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
  resources:
    overrides:
      kubernetes_namespace: {resource: "namespace"}
      kubernetes_pod_name: {resource: "pod"}
  name:
    matches: "^(.*)_total"
    as: "${1}_per_second"
  metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)'

适配器将根据我们放入 API 中的内容自动填充正确的系列名称、标签匹配器和分组依据子句。由于无论如何我们只使用一个指标,我们可以 用http_requests_total替换<<.Series>>.

4.3 查询api

现在,如果我们使用此配置运行 Prometheus 适配器的实例,我们可以使用下面的命令查看指标

$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "custom.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "pods/http_requests_total",
      "singularName": "",
      "namespaced": true,
      "kind": "MetricValueList",
      "verbs": ["get"]
    },
    {
      "name": "namespaces/http_requests_total",
      "singularName": "",
      "namespaced": false,
      "kind": "MetricValueList",
      "verbs": ["get"]
    }
  ]
}

请注意,我们获得了“pods”和“namespaces”的条目——适配器暴露了我们与metric关联的每个资源的metric(并且所有命名空间资源必须与命名空间关联),并将填充<<.GroupBy>>根据我们的要求,携带了合适的label。

查看每一个pod暴露的metric

$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/http_requests_per_second
{
  "kind": "MetricValueList",
  "apiVersion": "custom.metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/http_requests_per_second",
  },
  "items": [
    {
      "describedObject": {
        "kind": "Pod",
        "name": "frontend-server-abcd-0123",
        "apiVersion": "/__internal",
      },
      "metricName": "http_requests_per_second",
      "timestamp": "2018-08-07T17:45:22Z",
      "value": "16m"
    },
    {
      "describedObject": {
        "kind": "Pod",
        "name": "frontend-server-abcd-4567",
        "apiVersion": "/__internal",
      },
      "metricName": "http_requests_per_second",
      "timestamp": "2018-08-07T17:45:22Z",
      "value": "22m"
    }
  ]
}

这表示我们的pod 每秒接收 16 和 22 毫请求(取决于 pod),即每秒 0.016 和 0.022 请求,写为小数。这就是我们对除了Prometheus scrape之外几乎没有流量的期望。

如果我们向我们的 pod 添加一些流量,我们可能会看到1or20而不是 16m,这将是1or 20requests per second。我们可能还会看到 20500m,这意味着每秒 20500 毫请求,或者以十进制形式表示每秒 20.5 个请求。