下文使用Prometheus 自定义监控指标适配器 v0.5.0。这是一个示例。只有集群所有者才可以执行以下步骤。

  1. 获取集群监控使用的 service account,它应该已经配置了这个 workload ID:。如果您没有自定义任何选项,service account 的名字应该是cluster-monitoring

  2. 授予 service account 需要的两个权限。

    一个角色是kube-system中的extension-apiserver-authentication-reader,您需要在kube-system创建一个Rolebinding。这个权限的作用是从kube-system的 config map 获取 api 集合器配置。

    另一个角色是集群角色system:auth-delegator,您需要创建一个ClusterRoleBinding。这个权限的作用是允许代理身份认证和鉴权,以实现统一的身份认证和鉴权。

    1. apiVersion: rbac.authorization.k8s.io/v1
    2. kind: ClusterRoleBinding
    3. metadata:
    4. name: custom-metrics:system:auth-delegator
    5. roleRef:
    6. apiGroup: rbac.authorization.k8s.io
    7. kind: ClusterRole
    8. name: system:auth-delegator
    9. subjects:
    10. - kind: ServiceAccount
    11. name: cluster-monitoring
    12. namespace: cattle-prometheus
  3. 创建自定义参数适配器的配置文件,以下代码是一个配置文件的示例。下一节会详细讲述如何完成该配置文件。

    1. apiVersion: v1
    2. kind: ConfigMap
    3. metadata:
    4. name: adapter-config
    5. namespace: cattle-prometheus
    6. data:
    7. config.yaml: |
    8. rules:
    9. - seriesQuery: '{__name__=~"^container_.*",container_name!="POD",namespace!="",pod_name!=""}'
    10. seriesFilters: []
    11. resources:
    12. overrides:
    13. namespace:
    14. resource: namespace
    15. pod_name:
    16. resource: pod
    17. name:
    18. matches: ^container_(.*)_seconds_total$
    19. as: ""
    20. metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}[1m])) by (<<.GroupBy>>)
    21. - seriesQuery: '{__name__=~"^container_.*",container_name!="POD",namespace!="",pod_name!=""}'
    22. seriesFilters:
    23. - isNot: ^container_.*_seconds_total$
    24. resources:
    25. overrides:
    26. namespace:
    27. resource: namespace
    28. pod_name:
    29. resource: pod
    30. name:
    31. matches: ^container_(.*)_total$
    32. as: ""
    33. metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}[1m])) by (<<.GroupBy>>)
    34. - seriesQuery: '{__name__=~"^container_.*",container_name!="POD",namespace!="",pod_name!=""}'
    35. seriesFilters:
    36. - isNot: ^container_.*_total$
    37. resources:
    38. overrides:
    39. namespace:
    40. resource: namespace
    41. pod_name:
    42. resource: pod
    43. name:
    44. matches: ^container_(.*)$
    45. as: ""
    46. - seriesQuery: '{namespace!="",__name__!~"^container_.*"}'
    47. seriesFilters:
    48. - isNot: .*_total$
    49. resources:
    50. template: <<.Resource>>
    51. name:
    52. matches: ""
    53. as: ""
    54. metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
    55. - seriesQuery: '{namespace!="",__name__!~"^container_.*"}'
    56. seriesFilters:
    57. - isNot: .*_seconds_total
    58. resources:
    59. template: <<.Resource>>
    60. matches: ^(.*)_total$
    61. as: ""
    62. metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
    63. - seriesQuery: '{namespace!="",__name__!~"^container_.*"}'
    64. seriesFilters: []
    65. resources:
    66. template: <<.Resource>>
    67. name:
    68. matches: ^(.*)_seconds_total$
    69. as: ""
    70. metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
    71. resourceRules:
    72. cpu:
    73. containerQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
    74. nodeQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>, id='/'}[1m])) by (<<.GroupBy>>)
    75. resources:
    76. overrides:
    77. instance:
    78. resource: node
    79. namespace:
    80. resource: namespace
    81. pod_name:
    82. resource: pod
    83. containerLabel: container_name
    84. memory:
    85. containerQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>}) by (<<.GroupBy>>)
    86. nodeQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>,id='/'}) by (<<.GroupBy>>)
    87. resources:
    88. overrides:
    89. instance:
    90. resource: node
    91. namespace:
    92. resource: namespace
    93. pod_name:
    94. resource: pod
    95. containerLabel: container_name
    96. window: 1m
  4. 为您的 api server 创建 HTTPS TLS 证书。

    1. openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out serving.crt -keyout serving.key -subj "/C=CN/CN=custom-metrics-apiserver.cattle-prometheus.svc.cluster.local"
    2. # And you will find serving.crt and serving.key in your path. And then you are going to create a secret in cattle-prometheus namespace.
    3. kubectl create secret generic -n cattle-prometheus cm-adapter-serving-certs --from-file=serving.key=./serving.key --from-file=serving.crt=./serving.crt
  5. 然后您可以创建 Prometheus 自定义监控指标适配器。部署前需要以导入 YAML 的方式创建一个服务。请在cattle-prometheus这个命名空间内创建以下资源。

    以下是 Prometheus 自定义监控指标适配器的部署示例。

    以下是服务的部署示例。

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: custom-metrics-apiserver
    5. namespace: cattle-prometheus
    6. spec:
    7. ports:
    8. - port: 443
    9. selector:
    10. app: custom-metrics-apiserver
  6. 为您的自定义参数 server 创建 API service。

    1. apiVersion: apiregistration.k8s.io/v1beta1
    2. kind: APIService
    3. metadata:
    4. name: v1beta1.custom.metrics.k8s.io
    5. spec:
    6. service:
    7. name: custom-metrics-apiserver
    8. namespace: cattle-prometheus
    9. group: custom.metrics.k8s.io
    10. version: v1beta1
    11. insecureSkipTLSVerify: true
    12. groupPriorityMinimum: 100
    13. versionPriority: 100
  7. 在命令行界面输入kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1命令,校验自定义参数 server 是否已经配置成功。如果从 api 返回了参数,则表示配置成功。

  8. 现在您可以使用自定义参数创建 HPA。首先您需要在命名空间中创建一个 NGINX 部署,以下是 HPA 的示例代码。

    1. kind: HorizontalPodAutoscaler
    2. apiVersion: autoscaling/v2beta1
    3. metadata:
    4. name: nginx
    5. spec:
    6. scaleTargetRef:
    7. # point the HPA at the nginx deployment you just created
    8. apiVersion: apps/v1
    9. kind: Deployment
    10. name: nginx
    11. # autoscale between 1 and 10 replicas
    12. minReplicas: 1
    13. maxReplicas: 10
    14. metrics:
    15. # given metric across all pods controlled by the autoscaling target
    16. - type: Pods
    17. pods:
    18. metricName: memory_usage_bytes
    19. targetAverageValue: 5000000

    然后,您的 NGINX 规模会变大,表示使用自定义参数创建的 HPA 开始运行。

配置 Prometheus 自定义监控指标适配器

  • 服务发现(Discovery):告诉适配器如何找到这条规则涉及的所有参数。

  • 关联(Association):说明了参数与 Kubernetes 资源之间的关系,如参数 A 代表的是某个 Kubernetes 资源。

  • 名称(Naming):说明了适配器如何在自定义参数 API 中将特定的参数暴露出去。

  • 查询(Querying):说明了如何将查询 Kubernetes 参数的转换为 Prometheus 的查询语句。

您可以查看更加具体的配置文件sample-config.yaml获取详细的配置样例,也可参考下方的代码示例,查看如何配置只有一条规则的配置文件:

服务发现(Discovery)指定需要处理的 Prometheus 的参数,通过seriesQuery挑选需要处理的参数集合,通过seriesFilters精确过滤参数。seriesQuery用于挑选需要处理的参数集合。Prometheus 参数适配器会使用该机器的标签信息,后续还会用到 “metric-name-label-names”。

seriesFilters用于精确过滤参数。在大部分情况下,过滤参数只需要用到seriesQuery。但是当两条规则的关系不为互斥时,需要同时使用seriesFiltersseriesQuery,以达到精确过滤的目的。首先通过seriesQuery查询,然后通过seriesFilters过滤返回信息。

seriesFilters提供了以下两个过滤方式:

  • is: <regex>,返回名称和<regex>中的字符串匹配的 series。

  • isNot: <regex>,返回名称和<regex>中的字符串不匹配的 series。

例如:

  1. # match all cAdvisor metrics that aren't measured in seconds
  2. seriesQuery: '{__name__=~"^container_.*_total",container_name!="POD",namespace!="",pod_name!=""}'
  3. seriesFilters:
  4. isNot: "^container_.*_seconds_total"

关联负责的是设置 metric 与 kubernetes resources 的映射关系,resources控制这个过程。

有两种关联 Kubernetes 资源和参数的方式。在这两种方式中,标签(label)的值都会变成某个对象。

一种方式是基于标签名称指定匹配某些样式的名称。这可以通过template实现。样式通过 GO 模板指定,GroupResource表示组合资源。系统会自动辨别是哪个组,所以您可能不会用到Group,例如:

  1. # any label `kube_<group>_<resource>` becomes <group>.<resource> in Kubernetes
  2. resources:
  3. template: "kube_<<.Group>>_<<.Resource>>"

另一种方式是使用overrides指定匹配某些样式的名称,一个overrides可以指定一种 Prometheus label 和 Kubernetes 资源的映射关系,例如:

  1. # the microservice label corresponds to the apps.deployment resource
  2. resource:
  3. overrides:
  4. microservice: { group: "apps", resource: "deployment" }

只要您有对应的标签,resource 可以指代 Kubernetes 集群中的任意资源。

命名(Naming)用于将 prometheus metrics 名称转化为 custom metrics API 所使用的 metrics 名称,但不会改变其本身的 metric 名称,name控制这个过程。

命名是通过指定参数名称的模板,从 Prometheus name 提取 API name,然后将 name 转换为您指定的 name。

该名称的模板通过 matches指定,是一个正则表达式。如果没有指定特定的值,默认值为.*

名称的转换通过 as 指定。您可以使用在matches提到任何捕获组(capture group)。如果matches没有捕获组,as的默认值为$0。如果matches有 1 个捕获组,as的默认值为$1。如果matches有 2 个或更多的捕获组,您必须指定as的值。

例如:

处理调用 custom metrics API 获取到的 metrics 的 value,该值最终提供给 HPA 进行扩缩容,通过 metricsQuery指定。

metricsQuery是一个 GO 模板,它会转化为一个 Prometheus 查询语句,使用 custom metrics API 调用的某个特定 call。一个给定的 call 会被分解为 metric name、组资源和一个或多个组资源内的对象。这些参数会被转换成以下格式:

  • Series: metric name,参数名称
  • LabelMatchers: 以逗号分割的 objects,当前表示特定 group-resource 加上命名空间的 label(如果该 group-resource 是 namespaced 的)
  • GroupBy: 以逗号分割的 label 的集合,当前表示 LabelMatchers 中的 group-resource label。当前GroupBy包含LabelMatchers内的所有组资源 label。

假设我们有一个 series,http_requests_total (以http_requests_per_second 在 API 中暴露),有servicepodingressnamespaceverb这几个 label。前四个 label 有对应的 Kubernetes 资源。如果有人请求了pod1pod2的参数pods/http_request_per_second,我们会有如下的代码:

  • Series: "http_requests_total"
  • LabelMatchers: "pod=~\"pod1|pod2",namespace="somens"
  • GroupBy: pod

除了以上三个域之外,还有两个域

  • LabelValuesByNameLabelMatchers 中 label 和 value 的映射。使用|预连接(Prometheus 使用的是=~)。

  • GroupBySliceGroupBy的一个子集。

大多数情况下,您只会用到 SeriesLabelMatchers、和GroupBy,其他两个是高级选项。

这个查询语句应该为每个请求对象返回一个值。适配器会使用返回 series 的 labels 关联对应的对象。

例如:

  1. metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}[2m])) by (<<.GroupBy>>)"