项目作者: zalando-incubator

项目描述 :
General purpose metrics adapter for Kubernetes HPA metrics
高级语言: Go
项目地址: git://github.com/zalando-incubator/kube-metrics-adapter.git
创建时间: 2018-10-08T11:16:58Z
项目社区:https://github.com/zalando-incubator/kube-metrics-adapter

开源协议:MIT License

下载


kube-metrics-adapter

Build Status
Coverage Status

Kube Metrics Adapter is a general purpose metrics adapter for Kubernetes that
can collect and serve custom and external metrics for Horizontal Pod
Autoscaling.

It supports scaling based on Prometheus metrics, SQS queues and others out of the box.

It discovers Horizontal Pod Autoscaling resources and starts to collect the
requested metrics and stores them in memory. It’s implemented using the
custom-metrics-apiserver
library.

Here’s an example of a HorizontalPodAutoscaler resource configured to get
requests-per-second metrics from each pod of the deployment myapp.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  7. metric-config.pods.requests-per-second.json-path/json-key: "$.http_server.rps"
  8. metric-config.pods.requests-per-second.json-path/path: /metrics
  9. metric-config.pods.requests-per-second.json-path/port: "9090"
  10. spec:
  11. scaleTargetRef:
  12. apiVersion: apps/v1
  13. kind: Deployment
  14. name: myapp
  15. minReplicas: 1
  16. maxReplicas: 10
  17. metrics:
  18. - type: Pods
  19. pods:
  20. metric:
  21. name: requests-per-second
  22. target:
  23. averageValue: 1k
  24. type: AverageValue

The metric-config.* annotations are used by the kube-metrics-adapter to
configure a collector for getting the metrics. In the above example it
configures a json-path pod collector.

Kubernetes compatibility

Like the support
policy
offered
for Kubernetes, this project aims to support the latest three minor releases of
Kubernetes.

The default supported API is autoscaling/v2 (available since v1.23).
This API MUST be available in the cluster which is the default.

Building

This project uses Go modules as
introduced in Go 1.11 therefore you need Go >=1.11 installed in order to build.
If using Go 1.11 you also need to activate Module
support
.

Assuming Go has been setup with module support it can be built simply by running:

  1. export GO111MODULE=on # needed if the project is checked out in your $GOPATH.
  2. $ make

Install in Kubernetes

Clone this repository, and run as below:

  1. $ cd kube-metrics-adapter/docs
  2. $ kubectl apply -f .

Collectors

Collectors are different implementations for getting metrics requested by an
HPA resource. They are configured based on HPA resources and started on-demand by the
kube-metrics-adapter to only collect the metrics required for scaling the application.

The collectors are configured either simply based on the metrics defined in an
HPA resource, or via additional annotations on the HPA resource.

Pod collector

The pod collector allows collecting metrics from each pod matching the label selector defined in the HPA’s scaleTargetRef.
Currently only json-path collection is supported.

Supported HPA scaleTargetRef

The Pod Collector utilizes the scaleTargetRef specified in an HPA resource to obtain the label selector from the referenced Kubernetes object. This enables the identification and management of pods associated with that object. Currently, the supported Kubernetes objects for this operation are: Deployment, StatefulSet and Rollout.

Supported metrics

Metric Description Type K8s Versions
custom No predefined metrics. Metrics are generated from user defined queries. Pods >=1.12

Example

This is an example of using the pod collector to collect metrics from a json
metrics endpoint of each pod matched by the HPA.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  7. metric-config.pods.requests-per-second.json-path/json-key: "$.http_server.rps"
  8. metric-config.pods.requests-per-second.json-path/json-eval: "ceil($['active processes'] / $['total processes'] * 100)" # cannot use both json-eval and json-key
  9. metric-config.pods.requests-per-second.json-path/path: /metrics
  10. metric-config.pods.requests-per-second.json-path/port: "9090"
  11. metric-config.pods.requests-per-second.json-path/scheme: "https"
  12. metric-config.pods.requests-per-second.json-path/aggregator: "max"
  13. metric-config.pods.requests-per-second.json-path/interval: "60s" # optional
  14. metric-config.pods.requests-per-second.json-path/min-pod-ready-age: "30s" # optional
  15. spec:
  16. scaleTargetRef:
  17. apiVersion: apps/v1
  18. kind: Deployment
  19. name: myapp
  20. minReplicas: 1
  21. maxReplicas: 10
  22. metrics:
  23. - type: Pods
  24. pods:
  25. metric:
  26. name: requests-per-second
  27. target:
  28. averageValue: 1k
  29. type: AverageValue

The pod collector is configured through the annotations which specify the
collector name json-path and a set of configuration options for the
collector. json-key defines the json-path query for extracting the right
metric. This assumes the pod is exposing metrics in JSON format. For the above
example the following JSON data would be expected:

  1. {
  2. "http_server": {
  3. "rps": 0.5
  4. }
  5. }

The json-path query support depends on the
github.com/spyzhov/ajson library.
See the README for possible queries. It’s expected that the metric you query
returns something that can be turned into a float64.

The json-eval configuration option allows for more complex calculations to be
performed on the extracted metric. The json-eval expression is evaluated using
ajson’s script engine.

The other configuration options path, port and scheme specify where the metrics
endpoint is exposed on the pod. The path and port options do not have default values
so they must be defined. The scheme is optional and defaults to http.

The aggregator configuration option specifies the aggregation function used to aggregate
values of JSONPath expressions that evaluate to arrays/slices of numbers.
It’s optional but when the expression evaluates to an array/slice, it’s absence will
produce an error. The supported aggregation functions are avg, max, min and sum.

The raw-query configuration option specifies the query params to send along to the endpoint:

  1. metric-config.pods.requests-per-second.json-path/path: /metrics
  2. metric-config.pods.requests-per-second.json-path/port: "9090"
  3. metric-config.pods.requests-per-second.json-path/raw-query: "foo=bar&baz=bop"

will create a URL like this:

  1. http://<podIP>:9090/metrics?foo=bar&baz=bop

There are also configuration options for custom (connect and request) timeouts when querying pods for metrics:

  1. metric-config.pods.requests-per-second.json-path/request-timeout: 2s
  2. metric-config.pods.requests-per-second.json-path/connect-timeout: 500ms

The default for both of the above values is 15 seconds.

The min-pod-ready-age configuration option instructs the service to start collecting metrics from the pods only if they are “older” (time elapsed after pod reached “Ready” state) than the specified amount of time.
This is handy when pods need to warm up before HPAs will start tracking their metrics.

The default value is 0 seconds.

Prometheus collector

The Prometheus collector is a generic collector which can map Prometheus
queries to metrics that can be used for scaling. This approach is different
from how it’s done in the
k8s-prometheus-adapter
where all available Prometheus metrics are collected
and transformed into metrics which the HPA can scale on, and there is no
possibility to do custom queries.
With the approach implemented here, users can define custom queries and only metrics
returned from those queries will be available, reducing the total number of
metrics stored.

One downside of this approach is that bad performing queries can slow down/kill
Prometheus, so it can be dangerous to allow in a multi tenant cluster. It’s
also not possible to restrict the available metrics using something like RBAC
since any user would be able to create the metrics based on a custom query.

I still believe custom queries are more useful, but it’s good to be aware of
the trade-offs between the two approaches.

Supported metrics

Metric Description Type Kind K8s Versions
prometheus-query Generic metric which requires a user defined query. External >=1.12
custom No predefined metrics. Metrics are generated from user defined queries. Object any >=1.12

Example: External Metric

This is an example of an HPA configured to get metrics based on a Prometheus
query. The query is defined in the annotation
metric-config.external.processed-events-per-second.prometheus/query
where processed-events-per-second is the query name which will be associated
with the result of the query.
This allows having multiple prometheus queries associated with a single HPA.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # This annotation is optional.
  7. # If specified, then this prometheus server is used,
  8. # instead of the prometheus server specified as the CLI argument `--prometheus-server`.
  9. metric-config.external.processed-events-per-second.prometheus/prometheus-server: http://prometheus.my-namespace.svc
  10. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  11. metric-config.external.processed-events-per-second.prometheus/query: |
  12. scalar(sum(rate(event-service_events_count{application="event-service",processed="true"}[1m])))
  13. metric-config.external.processed-events-per-second.prometheus/interval: "60s" # optional
  14. spec:
  15. scaleTargetRef:
  16. apiVersion: apps/v1
  17. kind: Deployment
  18. name: custom-metrics-consumer
  19. minReplicas: 1
  20. maxReplicas: 10
  21. metrics:
  22. - type: External
  23. external:
  24. metric:
  25. name: processed-events-per-second
  26. selector:
  27. matchLabels:
  28. type: prometheus
  29. target:
  30. type: AverageValue
  31. averageValue: "10"

Example: Object Metric [DEPRECATED]

Note: Prometheus Object metrics are deprecated and will most likely be
removed in the future. Use the Prometheus External metrics instead as described
above.

This is an example of an HPA configured to get metrics based on a Prometheus
query. The query is defined in the annotation
metric-config.object.processed-events-per-second.prometheus/query where
processed-events-per-second is the metric name which will be associated with
the result of the query.

It also specifies an annotation
metric-config.object.processed-events-per-second.prometheus/per-replica which
instructs the collector to treat the results as an average over all pods
targeted by the HPA. This makes it possible to mimic the behavior of
targetAverageValue which is not implemented for metric type Object as of
Kubernetes v1.10. (It will most likely come in v1.12).

  1. apiVersion: autoscaling/v2beta1
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  7. metric-config.object.processed-events-per-second.prometheus/query: |
  8. scalar(sum(rate(event-service_events_count{application="event-service",processed="true"}[1m])))
  9. metric-config.object.processed-events-per-second.prometheus/per-replica: "true"
  10. spec:
  11. scaleTargetRef:
  12. apiVersion: apps/v1
  13. kind: Deployment
  14. name: custom-metrics-consumer
  15. minReplicas: 1
  16. maxReplicas: 10
  17. metrics:
  18. - type: Object
  19. object:
  20. metricName: processed-events-per-second
  21. target:
  22. apiVersion: v1
  23. kind: Pod
  24. name: dummy-pod
  25. targetValue: 10 # this will be treated as targetAverageValue

Note: The HPA object requires an Object to be specified. However when a Prometheus metric is used there is no need
for this object. But to satisfy the schema we specify a dummy pod called dummy-pod.

Skipper collector

The skipper collector is a simple wrapper around the Prometheus collector to
make it easy to define an HPA for scaling based on Ingress or
RouteGroup metrics when
skipper is used as the ingress
implementation in your cluster. It assumes you are collecting Prometheus
metrics from skipper and it provides the correct Prometheus queries out of the
box so users don’t have to define those manually.

Supported metrics

Metric Description Type Kind K8s Versions
requests-per-second Scale based on requests per second for a certain ingress or routegroup. Object Ingress, RouteGroup >=1.19

Example

Ingress

This is an example of an HPA that will scale based on requests-per-second for
an ingress called myapp.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. spec:
  6. scaleTargetRef:
  7. apiVersion: apps/v1
  8. kind: Deployment
  9. name: myapp
  10. minReplicas: 1
  11. maxReplicas: 10
  12. metrics:
  13. - type: Object
  14. object:
  15. describedObject:
  16. apiVersion: networking.k8s.io/v1
  17. kind: Ingress
  18. name: myapp
  19. metric:
  20. name: requests-per-second
  21. selector:
  22. matchLabels:
  23. backend: backend1 # optional backend
  24. target:
  25. averageValue: "10"
  26. type: AverageValue

RouteGroup

This is an example of an HPA that will scale based on requests-per-second for
a routegroup called myapp.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. spec:
  6. scaleTargetRef:
  7. apiVersion: apps/v1
  8. kind: Deployment
  9. name: myapp
  10. minReplicas: 1
  11. maxReplicas: 10
  12. metrics:
  13. - type: Object
  14. object:
  15. describedObject:
  16. apiVersion: zalando.org/v1
  17. kind: RouteGroup
  18. name: myapp
  19. metric:
  20. name: requests-per-second
  21. selector:
  22. matchLabels:
  23. backend: backend1 # optional backend
  24. target:
  25. averageValue: "10"
  26. type: AverageValue

Metric weighting based on backend

Skipper supports sending traffic to different backends based on annotations
present on the Ingress object, or weights on the RouteGroup backends. By
default the number of replicas will be calculated based on the full traffic
served by that ingress/routegroup. If however only the traffic being routed to
a specific backend should be used then the backend name can be specified via
the backend label under matchLabels for the metric. The ingress annotation
where the backend weights can be obtained can be specified through the flag
--skipper-backends-annotation.

External RPS collector

The External RPS collector, like Skipper collector, is a simple wrapper around the Prometheus collector to
make it easy to define an HPA for scaling based on the RPS measured for a given hostname. When
skipper is used as the ingress
implementation in your cluster everything should work automatically, in case another reverse proxy is used as ingress, like Nginx for example, its necessary to configure which prometheus metric should be used through --external-rps-metric-name <metric-name> flag. Assuming skipper-ingress is being used or the appropriate metric name is passed using the flag mentioned previously this collector provides the correct Prometheus queries out of the
box so users don’t have to define those manually.

Supported metrics

Metric Description Type Kind K8s Versions
requests-per-second Scale based on requests per second for a certain hostname. External >=1.12

Example: External Metric

This is an example of an HPA that will scale based on requests-per-second for the RPS measured in the hostnames called: www.example1.com and www.example2.com; and weighted by 42%.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. metric-config.external.example-rps.requests-per-second/hostnames: www.example1.com,www.example2.com
  7. metric-config.external.example-rps.requests-per-second/weight: "42"
  8. spec:
  9. scaleTargetRef:
  10. apiVersion: apps/v1
  11. kind: Deployment
  12. name: custom-metrics-consumer
  13. minReplicas: 1
  14. maxReplicas: 10
  15. metrics:
  16. - type: External
  17. external:
  18. metric:
  19. name: example-rps
  20. selector:
  21. matchLabels:
  22. type: requests-per-second
  23. target:
  24. type: AverageValue
  25. averageValue: "42"

Multiple hostnames per metric

This metric supports a relation of n:1 between hostnames and metrics. The way it works is the measured RPS is the sum of the RPS rate of each of the specified hostnames. This value is further modified by the weight parameter explained below.

Metric weighting based on backend

There are ingress-controllers, like skipper-ingress, that supports sending traffic to different backends based on some kind of configuration, in case of skipper annotations
present on the Ingress object, or weights on the RouteGroup backends. By
default the number of replicas will be calculated based on the full traffic
served by these components. If however only the traffic being routed to
a specific hostname should be used then the weight for the configured hostname(s) might be specified via the weight annotation metric-config.external.<metric-name>.request-per-second/weight for the metric being configured.

InfluxDB collector

The InfluxDB collector maps Flux queries to metrics that can be used for scaling.

Note that the collector targets an InfluxDB v2 instance, that’s why
we only support Flux instead of InfluxQL.

Supported metrics

Metric Description Type Kind K8s Versions
flux-query Generic metric which requires a user defined query. External >=1.10

Example: External Metric

This is an example of an HPA configured to get metrics based on a Flux query.
The query is defined in the annotation
metric-config.external.<metricName>.influxdb/query where <metricName> is
the query name which will be associated with the result of the query. This
allows having multiple flux queries associated with a single HPA.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # These annotations are optional.
  7. # If specified, then they are used for setting up the InfluxDB client properly,
  8. # instead of using the ones specified via CLI. Respectively:
  9. # - --influxdb-address
  10. # - --influxdb-token
  11. # - --influxdb-org
  12. metric-config.external.queue-depth.influxdb/address: "http://influxdbv2.my-namespace.svc"
  13. metric-config.external.queue-depth.influxdb/token: "secret-token"
  14. # This could be either the organization name or the ID.
  15. metric-config.external.queue-depth.influxdb/org: "deadbeef"
  16. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  17. # <configKey> == query-name
  18. metric-config.external.queue-depth.influxdb/query: |
  19. from(bucket: "apps")
  20. |> range(start: -30s)
  21. |> filter(fn: (r) => r._measurement == "queue_depth")
  22. |> group()
  23. |> max()
  24. // Rename "_value" to "metricvalue" for letting the metrics server properly unmarshal the result.
  25. |> rename(columns: {_value: "metricvalue"})
  26. |> keep(columns: ["metricvalue"])
  27. metric-config.external.queue-depth.influxdb/interval: "60s" # optional
  28. spec:
  29. scaleTargetRef:
  30. apiVersion: apps/v1
  31. kind: Deployment
  32. name: queryd-v1
  33. minReplicas: 1
  34. maxReplicas: 4
  35. metrics:
  36. - type: External
  37. external:
  38. metric:
  39. name: queue-depth
  40. selector:
  41. matchLabels:
  42. type: influxdb
  43. target:
  44. type: Value
  45. value: "1"

AWS collector

The AWS collector allows scaling based on external metrics exposed by AWS
services e.g. SQS queue lengths.

AWS IAM role

To integrate with AWS, the controller needs to run on nodes with
access to AWS API. Additionally the controller have to have a role
with the following policy to get all required data from AWS:

  1. PolicyDocument:
  2. Statement:
  3. - Action: 'sqs:GetQueueUrl'
  4. Effect: Allow
  5. Resource: '*'
  6. - Action: 'sqs:GetQueueAttributes'
  7. Effect: Allow
  8. Resource: '*'
  9. - Action: 'sqs:ListQueues'
  10. Effect: Allow
  11. Resource: '*'
  12. - Action: 'sqs:ListQueueTags'
  13. Effect: Allow
  14. Resource: '*'
  15. Version: 2012-10-17

Supported metrics

Metric Description Type K8s Versions
sqs-queue-length Scale based on SQS queue length External >=1.12

Example

This is an example of an HPA that will scale based on the length of an SQS
queue.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. spec:
  6. scaleTargetRef:
  7. apiVersion: apps/v1
  8. kind: Deployment
  9. name: custom-metrics-consumer
  10. minReplicas: 1
  11. maxReplicas: 10
  12. metrics:
  13. - type: External
  14. external:
  15. metric:
  16. name: my-sqs
  17. selector:
  18. matchLabels:
  19. type: sqs-queue-length
  20. queue-name: foobar
  21. region: eu-central-1
  22. target:
  23. averageValue: "30"
  24. type: AverageValue

The matchLabels are used by kube-metrics-adapter to configure a collector
that will get the queue length for an SQS queue named foobar in region
eu-central-1.

The AWS account of the queue currently depends on how kube-metrics-adapter is
configured to get AWS credentials. The normal assumption is that you run the
adapter in a cluster running in the AWS account where the queue is defined.
Please open an issue if you would like support for other use cases.

ZMON collector

The ZMON collector allows scaling based on external metrics exposed by
ZMON checks.

Supported metrics

Metric Description Type K8s Versions
zmon-check Scale based on any ZMON check results External >=1.12

Example

This is an example of an HPA that will scale based on the specified value
exposed by a ZMON check with id 1234.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  7. metric-config.external.my-zmon-check.zmon/key: "custom.*"
  8. metric-config.external.my-zmon-check.zmon/tag-application: "my-custom-app-*"
  9. metric-config.external.my-zmon-check.zmon/interval: "60s" # optional
  10. spec:
  11. scaleTargetRef:
  12. apiVersion: apps/v1
  13. kind: Deployment
  14. name: custom-metrics-consumer
  15. minReplicas: 1
  16. maxReplicas: 10
  17. metrics:
  18. - type: External
  19. external:
  20. metric:
  21. name: my-zmon-check
  22. selector:
  23. matchLabels:
  24. type: zmon
  25. check-id: "1234" # the ZMON check to query for metrics
  26. key: "custom.value"
  27. tag-application: my-custom-app
  28. aggregators: avg # comma separated list of aggregation functions, default: last
  29. duration: 5m # default: 10m
  30. target:
  31. averageValue: "30"
  32. type: AverageValue

The check-id specifies the ZMON check to query for the metrics. key
specifies the JSON key in the check output to extract the metric value from.
E.g. if you have a check which returns the following data:

  1. {
  2. "custom": {
  3. "value": 1.0
  4. },
  5. "other": {
  6. "value": 3.0
  7. }
  8. }

Then the value 1.0 would be returned when the key is defined as custom.value.

The tag-<name> labels defines the tags used for the kariosDB query. In a
normal ZMON setup the following tags will be available:

  • application
  • alias (name of Kubernetes cluster)
  • entity - full ZMON entity ID.

aggregators defines the aggregation functions applied to the metrics query.
For instance if you define the entity filter
type=kube_pod,application=my-custom-app you might get three entities back and
then you might want to get an average over the metrics for those three
entities. This would be possible by using the avg aggregator. The default
aggregator is last which returns only the latest metric point from the
query. The supported aggregation functions are avg, count,
last, max, min, sum, diff. See the KariosDB docs for
details.

The duration defines the duration used for the timeseries query. E.g. if you
specify a duration of 5m then the query will return metric points for the
last 5 minutes and apply the specified aggregation with the same duration .e.g
max(5m).

The annotations metric-config.external.my-zmon-check.zmon/key and
metric-config.external.my-zmon-check.zmon/tag-<name> can be optionally used if
you need to define a key or other tag with a “star” query syntax like
values.*. This hack is in place because it’s not allowed to use * in the
metric label definitions. If both annotations and corresponding label is
defined, then the annotation takes precedence.

Nakadi collector

The Nakadi collector allows scaling based on Nakadi
Subscription API stats metrics consumer_lag_seconds or unconsumed_events.

Supported metrics

Metric Type Description Type K8s Versions
unconsumed-events Scale based on number of unconsumed events for a Nakadi subscription External >=1.24
consumer-lag-seconds Scale based on number of max consumer lag seconds for a Nakadi subscription External >=1.24
  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  7. metric-config.external.my-nakadi-consumer.nakadi/interval: "60s" # optional
  8. spec:
  9. scaleTargetRef:
  10. apiVersion: apps/v1
  11. kind: Deployment
  12. name: custom-metrics-consumer
  13. minReplicas: 0
  14. maxReplicas: 8 # should match number of partitions for the event type
  15. metrics:
  16. - type: External
  17. external:
  18. metric:
  19. name: my-nakadi-consumer
  20. selector:
  21. matchLabels:
  22. type: nakadi
  23. subscription-id: "708095f6-cece-4d02-840e-ee488d710b29"
  24. metric-type: "consumer-lag-seconds|unconsumed-events"
  25. target:
  26. # value is compatible with the consumer-lag-seconds metric type.
  27. # It describes the amount of consumer lag in seconds before scaling
  28. # additionally up.
  29. # if an event-type has multiple partitions the value of
  30. # consumer-lag-seconds is the max of all the partitions.
  31. value: "600" # 10m
  32. type: Value
  33. # averageValue is compatible with unconsumed-events metric type.
  34. # This means for every 30 unconsumed events a pod is added.
  35. # unconsumed-events is the sum of of unconsumed_events over all
  36. # partitions.
  37. averageValue: "30"
  38. type: AverageValue

The subscription-id is the Subscription ID of the relevant consumer. The
metric-type indicates whether to scale on consumer-lag-seconds or
unconsumed-events as outlined below.

unconsumed-events - is the total number of unconsumed events over all
partitions. When using this metric-type you should also use the target
averageValue which indicates the number of events which can be handled per
pod. To best estimate the number of events per pods, you need to understand the
average time for processing an event as well as the rate of events.

Example: You have an event type producing 100 events per second between 00:00
and 08:00. Between 08:01 to 23:59 it produces 400 events per second.
Let’s assume that on average a single pod can consume 100 events per second,
then we can define 100 as averageValue and the HPA would scale to 1 between
00:00 and 08:00, and scale to 4 between 08:01 and 23:59. If there for some
reason is a short spike of 800 events per second, then it would scale to 8 pods
to process those events until the rate goes down again.

consumer-lag-seconds - describes the age of the oldest unconsumed event for
a subscription. If the event type has multiple partitions the lag is defined as
the max age over all partitions. When using this metric-type you should use
the target value to indicate the max lag (in seconds) before the HPA should
scale.

Example: You have a subscription with a defined SLO of “99.99 of events are
consumed within 30 min.”. In this case you can define a target value of e.g.
20 min. (1200s) (to include a safety buffer) such that the HPA only scales up
from 1 to 2 if the target of 20 min. is breached and it needs to work faster
with more consumers.
For this case you should also account for the average time for processing an
event when defining the target.

Alternative to defining subscription-id you can also filter based on
owning_application, event-types and consumer-group:

  1. metrics:
  2. - type: External
  3. external:
  4. metric:
  5. name: my-nakadi-consumer
  6. selector:
  7. matchLabels:
  8. type: nakadi
  9. owning-application: "example-app"
  10. # comma separated list of event types
  11. event-types: "example-event-type,example-event-type2"
  12. consumer-group: "abcd1234"
  13. metric-type: "consumer-lag-seconds|unconsumed-events"

This is useful in dynamic environments where the subscription ID might not be
known before deployment time (e.g. because it’s created by the same deployment).

HTTP Collector

The http collector allows collecting metrics from an external endpoint specified in the HPA.
Currently only json-path collection is supported.

Supported metrics

Metric Description Type K8s Versions
custom No predefined metrics. Metrics are generated from user defined queries. Pods >=1.12

Example

This is an example of using the HTTP collector to collect metrics from a json
metrics endpoint specified in the annotations.

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: myapp-hpa
  5. annotations:
  6. # metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
  7. metric-config.external.unique-metric-name.json-path/json-key: "$.some-metric.value"
  8. metric-config.external.unique-metric-name.json-path/json-eval: ceil($['active processes'] / $['total processes'] * 100) # cannot use both json-eval and json-key
  9. metric-config.external.unique-metric-name.json-path/endpoint: "http://metric-source.app-namespace:8080/metrics"
  10. metric-config.external.unique-metric-name.json-path/aggregator: "max"
  11. metric-config.external.unique-metric-name.json-path/interval: "60s" # optional
  12. spec:
  13. scaleTargetRef:
  14. apiVersion: apps/v1
  15. kind: Deployment
  16. name: myapp
  17. minReplicas: 1
  18. maxReplicas: 10
  19. metrics:
  20. - type: External
  21. external:
  22. metric:
  23. name: unique-metric-name
  24. selector:
  25. matchLabels:
  26. type: json-path
  27. target:
  28. averageValue: 1
  29. type: AverageValue

The HTTP collector similar to the Pod Metrics collector. The following
configuration values are supported:

  • json-key to specify the JSON path of the metric to be queried
  • json-eval to specify an evaluate string to evaluate on the script engine,
    cannot be used in conjunction with json-key
  • endpoint the fully formed path to query for the metric. In the above example a Kubernetes Service
    in the namespace app-namespace is called.
  • aggregator is only required if the metric is an array of values and specifies how the values
    are aggregated. Currently this option can support the values: sum, max, min, avg.

Scrape Interval

It’s possible to configure the scrape interval for each of the metric types via
an annotation:

  1. metric-config.<metricType>.<metricName>.<collectorType>/interval: "30s"

The default is 60s but can be reduced to let the adapter collect metrics more
often.

ScalingSchedule Collectors

The ScalingSchedule and ClusterScalingSchedule collectors allow
collecting time-based metrics from the respective CRD objects specified
in the HPA.

These collectors are disabled by default, you have to start the server
with the --scaling-schedule flag to enable it. Remember to deploy the CRDs
ScalingSchedule and ClusterScalingSchedule and allow the service
account used by the server to read, watch and list them.

Supported metrics

Metric Description Type K8s Versions
ObjectName The metric is calculated and stored for each ScalingSchedule and ClusterScalingSchedule referenced in the HPAs ScalingSchedule and ClusterScalingSchedule >=1.16

Ramp-up and ramp-down feature

To avoid abrupt scaling due to time based metrics,the SchalingSchedule
collector has a feature of ramp-up and ramp-down the metric over a
specific period of time. The duration of the scaling window can be
configured individually in the [Cluster]ScalingSchedule object, via
the option scalingWindowDurationMinutes or globally for all scheduled
events, and defaults to a globally configured value if not specified.
The default for the latter is set to 10 minutes, but can be changed
using the --scaling-schedule-default-scaling-window flag.

This spreads the scale events around, creating less load on the other
components, and helping the rest of the metrics (like the CPU ones) to
adjust as well.

The HPA algorithm does not make changes if the metric
change is less than the specified by the
horizontal-pod-autoscaler-tolerance flag:

We’ll skip scaling if the ratio is sufficiently close to 1.0 (within a
globally-configurable tolerance, from the
--horizontal-pod-autoscaler-tolerance flag, which defaults to 0.1.

With that in mind, the ramp-up and ramp-down feature divides the scaling
over the specified period of time in buckets, trying to achieve changes
bigger than the configured tolerance. The number of buckets defaults to
10 and can be configured by the --scaling-schedule-ramp-steps flag.

Important: note that the ramp-up and ramp-down feature can lead to
deployments achieving less than the specified number of pods, due to the
HPA 10% change rule and the ceiling function applied to the desired
number of the pods (check the algorithm details). It
varies with the configured metric for ScalingSchedule events, the
number of pods and the configured horizontal-pod-autoscaler-tolerance
flag of your kubernetes installation. This gist contains the code to
simulate the situations a deployment with different number of pods, with
a metric of 10000 can face with 10 buckets (max of 90% of the metric
returned) and 5 buckets (max of 80% of the metric returned). The ramp-up
and ramp-down feature can be disabled by setting
--scaling-schedule-default-scaling-window to 0 and abrupt scalings can
be handled via scaling policies.

Example

This is an example of using the ScalingSchedule collectors to collect
metrics from a deployed kind of the CRD. First, the schedule object:

  1. apiVersion: zalando.org/v1
  2. kind: ClusterScalingSchedule
  3. metadata:
  4. name: "scheduling-event"
  5. spec:
  6. schedules:
  7. - type: OneTime
  8. date: "2021-10-02T08:08:08+02:00"
  9. durationMinutes: 30
  10. value: 100
  11. - type: Repeating
  12. durationMinutes: 10
  13. value: 120
  14. period:
  15. startTime: "15:45"
  16. timezone: "Europe/Berlin"
  17. days:
  18. - Mon
  19. - Wed
  20. - Fri

This resource defines a scheduling event named scheduling-event with
two schedules of the kind ClusterScalingSchedule.

ClusterScalingSchedule objects aren’t namespaced, what means it can be
referenced by any HPA in any namespace in the cluster. ScalingSchedule
have the exact same fields and behavior, but can be referenced just by
HPAs in the same namespace. The schedules can have the type Repeating
or OneTime.

This example configuration will generate the following result: at
2021-10-02T08:08:08+02:00 for 30 minutes a metric with the value of
100 will be returned. Every Monday, Wednesday and Friday, starting at 15
hours and 45 minutes (Berlin time), a metric with the value of 120 will
be returned for 10 minutes. It’s not the case of this example, but if multiple
schedules collide in time, the biggest value is returned.

Check the CRDs definitions
(ScalingSchedule,
ClusterScalingSchedule) for
a better understanding of the possible fields and their behavior.

An HPA can reference the deployed ClusterScalingSchedule object as
this example:

  1. apiVersion: autoscaling/v2
  2. kind: HorizontalPodAutoscaler
  3. metadata:
  4. name: "myapp-hpa"
  5. spec:
  6. scaleTargetRef:
  7. apiVersion: apps/v1
  8. kind: Deployment
  9. name: myapp
  10. minReplicas: 1
  11. maxReplicas: 15
  12. metrics:
  13. - type: Object
  14. object:
  15. describedObject:
  16. apiVersion: zalando.org/v1
  17. kind: ClusterScalingSchedule
  18. name: "scheduling-event"
  19. metric:
  20. name: "scheduling-event"
  21. target:
  22. type: AverageValue
  23. averageValue: "10"

The name of the metric is equal to the name of the referenced object.
The target.averageValue in this example is set to 10. This value will
be used by the HPA controller to define the desired number of pods,
based on the metric obtained (check the HPA algorithm
details

for more context). This HPA configuration explicitly says that each pod
of this application supports 10 units of the ClusterScalingSchedule
metric. Multiple applications can share the same
ClusterScalingSchedule or ScalingSchedule event and have a different
number of pods based on its target.averageValue configuration.

In our specific example at 2021-10-02T08:08:08+02:00 as the metric has
the value 100, this application will scale to 10 pods (100/10). Every
Monday, Wednesday and Friday, starting at 15 hours and 45 minutes
(Berlin time) the application will scale to 12 pods (120/10). Both
scaling up will last at least the configured duration times of the
schedules. After that, regular HPA scale down behavior applies.

Note that these number of pods are just considering these custom
metrics, the normal HPA behavior still applies, such as: in case of
multiple metrics the biggest number of pods is the utilized one, HPA max
and min replica configuration, autoscaling policies, etc.