启用了集群层级的监控以后,您可以查看 Rancher 的监控指标。您也可以部署 Prometheus 自定义监控指标适配器,然后配合储存在集群内的监控指标,使用 HPA。

部署 Prometheus 自定义监控指标适配器

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

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

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

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

    1. apiVersion: rbac.authorization.k8s.io/v1
    2. kind: RoleBinding
    3. metadata:
    4. name: custom-metrics-auth-reader
    5. namespace: kube-system
    6. roleRef:
    7. apiGroup: rbac.authorization.k8s.io
    8. kind: Role
    9. name: extension-apiserver-authentication-reader
    10. subjects:
    11. - kind: ServiceAccount
    12. name: cluster-monitoring
    13. namespace: cattle-prometheus

    另一个角色是集群角色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. metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}) by (<<.GroupBy>>)
    47. - seriesQuery: '{namespace!="",__name__!~"^container_.*"}'
    48. seriesFilters:
    49. - isNot: .*_total$
    50. resources:
    51. template: <<.Resource>>
    52. name:
    53. matches: ""
    54. as: ""
    55. metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
    56. - seriesQuery: '{namespace!="",__name__!~"^container_.*"}'
    57. seriesFilters:
    58. - isNot: .*_seconds_total
    59. resources:
    60. template: <<.Resource>>
    61. name:
    62. matches: ^(.*)_total$
    63. as: ""
    64. metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
    65. - seriesQuery: '{namespace!="",__name__!~"^container_.*"}'
    66. seriesFilters: []
    67. resources:
    68. template: <<.Resource>>
    69. name:
    70. matches: ^(.*)_seconds_total$
    71. as: ""
    72. metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
    73. resourceRules:
    74. cpu:
    75. containerQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
    76. nodeQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>, id='/'}[1m])) by (<<.GroupBy>>)
    77. resources:
    78. overrides:
    79. instance:
    80. resource: node
    81. namespace:
    82. resource: namespace
    83. pod_name:
    84. resource: pod
    85. containerLabel: container_name
    86. memory:
    87. containerQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>}) by (<<.GroupBy>>)
    88. nodeQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>,id='/'}) by (<<.GroupBy>>)
    89. resources:
    90. overrides:
    91. instance:
    92. resource: node
    93. namespace:
    94. resource: namespace
    95. pod_name:
    96. resource: pod
    97. containerLabel: container_name
    98. 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: apps/v1
    2. kind: Deployment
    3. metadata:
    4. labels:
    5. app: custom-metrics-apiserver
    6. name: custom-metrics-apiserver
    7. namespace: cattle-prometheus
    8. spec:
    9. replicas: 1
    10. selector:
    11. matchLabels:
    12. app: custom-metrics-apiserver
    13. template:
    14. metadata:
    15. labels:
    16. app: custom-metrics-apiserver
    17. name: custom-metrics-apiserver
    18. spec:
    19. serviceAccountName: cluster-monitoring
    20. containers:
    21. - name: custom-metrics-apiserver
    22. image: directxman12/k8s-prometheus-adapter-amd64:v0.5.0
    23. args:
    24. - --secure-port=6443
    25. - --tls-cert-file=/var/run/serving-cert/serving.crt
    26. - --tls-private-key-file=/var/run/serving-cert/serving.key
    27. - --logtostderr=true
    28. - --prometheus-url=http://prometheus-operated/
    29. - --metrics-relist-interval=1m
    30. - --v=10
    31. - --config=/etc/adapter/config.yaml
    32. ports:
    33. - containerPort: 6443
    34. volumeMounts:
    35. - mountPath: /var/run/serving-cert
    36. name: volume-serving-cert
    37. readOnly: true
    38. - mountPath: /etc/adapter/
    39. name: config
    40. readOnly: true
    41. - mountPath: /tmp
    42. name: tmp-vol
    43. volumes:
    44. - name: volume-serving-cert
    45. secret:
    46. secretName: cm-adapter-serving-certs
    47. - name: config
    48. configMap:
    49. name: adapter-config
    50. - name: tmp-vol
    51. emptyDir: {}

    以下是服务的部署示例。

    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. targetPort: 6443
    10. selector:
    11. 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. # use a "Pods" metric, which takes the average of the
    16. # given metric across all pods controlled by the autoscaling target
    17. - type: Pods
    18. pods:
    19. metricName: memory_usage_bytes
    20. targetAverageValue: 5000000

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

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

每一条规则都是独立执行的,所以请保证您配置的规则之间存在互斥关系,说明适配器需要执行的每一个步骤,然后将参数在 API 中暴露。

简而言之,每一条规则由以下四个部分组成:

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

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

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

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

您可以参考下方的代码示例,查看如何配置只有一条规则的配置文件:

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

服务发现(Discovery)

服务发现(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"

关联(Association)

关联负责的是设置 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)

命名(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的值。

例如:

  1. # match turn any name <name>_total to <name>_per_second
  2. # e.g. http_requests_total becomes http_requests_per_second
  3. name:
  4. matches: "^(.*)_total$"
  5. as: "${1}_per_second"

查询(Querying)

处理调用 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. # convert cumulative cAdvisor metrics into rates calculated over 2 minutes
  2. metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}[2m])) by (<<.GroupBy>>)"