Compare commits

...
Author SHA1 Message Date
Mikkel Oscar Lyderik LarsenandGitHub b7aa886546 Namespace external metrics (#259)
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2021-02-19 11:11:29 +01:00
Mikkel Oscar Lyderik LarsenandGitHub 942e753f87 Use Github actions instead of travis (#270)
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2021-02-19 10:31:20 +01:00
Jonathan Juares BeberandGitHub 86ed6ec102 Merge pull request #269 from zalando-incubator/update-dependencies-2021-02-01
Update dependencies
2021-02-01 18:49:22 +01:00
Mikkel Oscar Lyderik Larsen b313c963b6 Update dependencies
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2021-02-01 10:26:01 +01:00
Jonathan Juares BeberandGitHub ca20ef3a3b Merge pull request #256 from zalando-incubator/label-maintainer
Use non-deprecated Dockerfile syntax
2021-01-15 14:56:27 +01:00
Mikkel Oscar Lyderik Larsen f2179846c5 Use non-deprecated Dockerfile syntax
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2021-01-15 14:21:26 +01:00
Jonathan Juares BeberandGitHub 85089c4773 Merge pull request #253 from tanersener/master
Fix the namespace of horizontal-pod-autoscaler service account used in the helm chart
2021-01-14 19:31:02 +01:00
Taner Sener 2ac9dd29d8 Fix the namespace of horizontal-pod-autoscaler service account in the helm chart
Signed-off-by: Taner Sener <tanersener@gmail.com>
2021-01-13 14:54:43 +00:00
Mikkel Oscar Lyderik LarsenandGitHub 2bfc85ff3d Merge pull request #251 from zalando-incubator/fix-aws-collector
Don't check for metric name on AWS init
2021-01-05 16:49:31 +01:00
Mikkel Oscar Lyderik Larsen d55a551bd8 Don't check for metric name on AWS init
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2021-01-05 11:52:44 +01:00
Jonathan Juares BeberandGitHub 3174100f5a Merge pull request #250 from zalando-incubator/base-image
Switch to compliant base image
2021-01-05 09:03:47 +01:00
Jonathan Juares Beber 16808f012d Switch to compliant base image
Use compliant base image according to our tech rules of play:
- KDP-1: use an approved Docker base image and version from the list of
    allowed Docker Base Images under the pierone namespace ‘library’.

Signed-off-by: Jonathan Juares Beber <jonathanbeber@gmail.com>
2021-01-05 08:57:15 +01:00
Mikkel Oscar Lyderik LarsenandGitHub 2531ddcbe3 Merge pull request #238 from tanersener/master
Add a kubernetes helm chart
2021-01-04 16:07:52 +01:00
Taner Sener 8ff2e3985b keep non-helm deployment files
Signed-off-by: Taner Sener <tanersener@gmail.com>
2021-01-04 13:11:33 +00:00
Mikkel Oscar Lyderik LarsenandGitHub dab5a66556 Update dependencies (#249)
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2021-01-04 09:59:04 +01:00
Taner Sener 995ec54aa5 re-organize helm deployment files as a helm chart
Signed-off-by: Taner Sener <tanersener@gmail.com>
2020-12-19 10:12:33 +00:00
Mikkel Oscar Lyderik LarsenandGitHub 6f71e9c6b0 Merge pull request #236 from zalando-incubator/update-deps-2020-12-01
Update dependencies
2020-12-01 12:42:34 +01:00
Mikkel Oscar Lyderik Larsen ed16830a77 Update dependencies
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2020-12-01 11:16:09 +01:00
Ewan ValentineandGitHub 7f84bc75f4 UPDATED to use NewSessionWithConfig instead of NewSession (#230)
NewSession is being deprecated, NewSessionWithConfig handles things
such as role arns. This fixes permissions issues for users using
role arns for service accounts.

Signed-off-by: Ewan Valentine <ewan.valentine89@gmail.com>
2020-11-21 19:49:49 +01:00
Mikkel Oscar Lyderik LarsenandGitHub b5fbb1beae Merge pull request #228 from zalando-incubator/prometheus-fix-query-mapping
Fix mapping of Prometheus query for new metric configuration
2020-11-16 17:52:56 +01:00
Mikkel Oscar Lyderik Larsen 4506701b7c Fix mapping of Prometheus query for new metric configuration
Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2020-11-16 17:44:06 +01:00
Mikkel Oscar Lyderik LarsenandGitHub df0ed1fca4 Use labels for mapping metric types to metrics (#219)
* Use labels for mapping metric types to metrics

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>

* Log warning when old format is used

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>

* Test NewCollector logic for external metrics

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
2020-11-04 20:40:53 +01:00
Jannis Rake-RevelantandGitHub bb107b678c Merge pull request #224 from zalando-incubator/update-deps-2020-11-02
Update dependencies
2020-11-02 09:56:51 +01:00
36 changed files with 2795 additions and 705 deletions
+25
View File
@@ -0,0 +1,25 @@
name: ci
on: [push, pull_request]
env:
GO111MODULE: on
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.16'
- run: go version
- run: go get github.com/mattn/goveralls
env:
GO111MODULE: off
- run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_RELEASE}
env:
GOLANGCI_RELEASE: v1.37.0
- run: make build.docker
- run: make test
- run: make check
- run: goveralls -coverprofile=profile.cov -service=github
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1
View File
@@ -1,4 +1,5 @@
build/
apiserver.local.config/
.idea/
profile.cov
vendor/
-20
View File
@@ -1,20 +0,0 @@
language: go
dist: xenial
go:
- "1.14.x"
env:
- GO111MODULE=on GOLANGCI_RELEASE="v1.21.0"
before_install:
- GO111MODULE=off go get github.com/mattn/goveralls
- GO111MODULE=off go get github.com/lawrencewoodman/roveralls
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_RELEASE}
script:
- make test
- make build.docker
- make check
- roveralls
- goveralls -v -coverprofile=roveralls.coverprofile -service=travis-ci
+2 -2
View File
@@ -1,5 +1,5 @@
FROM registry.opensource.zalan.do/stups/alpine:latest
MAINTAINER Team Teapot @ Zalando SE <team-teapot@zalando.de>
FROM registry.opensource.zalan.do/library/alpine-3.12:latest
LABEL maintainer="Team Teapot @ Zalando SE <team-teapot@zalando.de>"
# add binary
ADD build/linux/kube-metrics-adapter /
+1 -1
View File
@@ -19,7 +19,7 @@ clean:
rm -rf $(OPENAPI)
test:
go test -v $(GOPKGS)
go test -v -coverprofile=profile.cov $(GOPKGS)
check:
go mod download
+37 -39
View File
@@ -10,7 +10,7 @@ It supports scaling based on [Prometheus metrics](https://prometheus.io/), [SQS
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](https://github.com/kubernetes-incubator/custom-metrics-apiserver)
[custom-metrics-apiserver](https://github.com/kubernetes-sigs/custom-metrics-apiserver)
library.
Here's an example of a `HorizontalPodAutoscaler` resource configured to get
@@ -22,7 +22,7 @@ kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
annotations:
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
metric-config.pods.requests-per-second.json-path/json-key: "$.http_server.rps"
metric-config.pods.requests-per-second.json-path/path: /metrics
metric-config.pods.requests-per-second.json-path/port: "9090"
@@ -104,7 +104,7 @@ kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
annotations:
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
metric-config.pods.requests-per-second.json-path/json-key: "$.http_server.rps"
metric-config.pods.requests-per-second.json-path/path: /metrics
metric-config.pods.requests-per-second.json-path/port: "9090"
@@ -216,11 +216,10 @@ Default is 1 minute.
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.prometheus-query.prometheus/processed-events-per-second`
`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. A matching `query-name` label must be defined in
the `matchLabels` of the metric definition. This allows having multiple
prometheus queries associated with a single HPA.
with the result of the query.
This allows having multiple prometheus queries associated with a single HPA.
```yaml
apiVersion: autoscaling/v2beta2
@@ -231,10 +230,9 @@ metadata:
# This annotation is optional.
# If specified, then this prometheus server is used,
# instead of the prometheus server specified as the CLI argument `--prometheus-server`.
metric-config.external.prometheus-query.prometheus/prometheus-server: http://prometheus.my-namespace.svc
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
# <configKey> == query-name
metric-config.external.prometheus-query.prometheus/processed-events-per-second: |
metric-config.external.processed-events-per-second.prometheus/prometheus-server: http://prometheus.my-namespace.svc
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
metric-config.external.processed-events-per-second.prometheus/query: |
scalar(sum(rate(event-service_events_count{application="event-service",processed="true"}[1m])))
spec:
scaleTargetRef:
@@ -247,10 +245,10 @@ spec:
- type: External
external:
metric:
name: prometheus-query
name: processed-events-per-second
selector:
matchLabels:
query-name: processed-events-per-second
type: prometheus
target:
type: AverageValue
averageValue: "10"
@@ -281,7 +279,7 @@ kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
annotations:
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
metric-config.object.processed-events-per-second.prometheus/query: |
scalar(sum(rate(event-service_events_count{application="event-service",processed="true"}[1m])))
metric-config.object.processed-events-per-second.prometheus/per-replica: "true"
@@ -382,10 +380,9 @@ we only support Flux instead of InfluxQL.
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.flux-query.influxdb/queue_depth`
where `queue_depth` is the query name which will be associated with the result of the query.
A matching `query-name` label must be defined in the `matchLabels` of the metric definition.
This allows having multiple flux queries associated with a single HPA.
`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.
```yaml
apiVersion: autoscaling/v2beta2
@@ -399,13 +396,13 @@ metadata:
# - --influxdb-address
# - --influxdb-token
# - --influxdb-org
metric-config.external.flux-query.influxdb/address: "http://influxdbv2.my-namespace.svc"
metric-config.external.flux-query.influxdb/token: "secret-token"
metric-config.external.queue-depth.influxdb/address: "http://influxdbv2.my-namespace.svc"
metric-config.external.queue-depth.influxdb/token: "secret-token"
# This could be either the organization name or the ID.
metric-config.external.flux-query.influxdb/org: "deadbeef"
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
metric-config.external.queue-depth.influxdb/org: "deadbeef"
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
# <configKey> == query-name
metric-config.external.flux-query.influxdb/queue_depth: |
metric-config.external.queue-depth.influxdb/query: |
from(bucket: "apps")
|> range(start: -30s)
|> filter(fn: (r) => r._measurement == "queue_depth")
@@ -425,10 +422,10 @@ spec:
- type: External
external:
metric:
name: flux-query
name: queue-depth
selector:
matchLabels:
query-name: queue_depth
type: influxdb
target:
type: Value
value: "1"
@@ -490,9 +487,10 @@ spec:
- type: External
external:
metric:
name: sqs-queue-length
name: my-sqs
selector:
matchLabels:
type: sqs-queue-length
queue-name: foobar
region: eu-central-1
target:
@@ -531,9 +529,9 @@ kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
annotations:
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
metric-config.external.zmon-check.zmon/key: "custom.*"
metric-config.external.zmon-check.zmon/tag-application: "my-custom-app-*"
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
metric-config.external.my-zmon-check.zmon/key: "custom.*"
metric-config.external.my-zmon-check.zmon/tag-application: "my-custom-app-*"
spec:
scaleTargetRef:
apiVersion: apps/v1
@@ -545,9 +543,10 @@ spec:
- type: External
external:
metric:
name: zmon-check
name: my-zmon-check
selector:
matchLabels:
type: zmon
check-id: "1234" # the ZMON check to query for metrics
key: "custom.value"
tag-application: my-custom-app
@@ -597,8 +596,8 @@ 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.zmon-check.zmon/key` and
`metric-config.external.zmon-check.zmon/tag-<name>` can be optionally used if
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
@@ -626,10 +625,10 @@ kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
annotations:
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
metric-config.external.http.json/json-key: "$.some-metric.value"
metric-config.external.http.json/endpoint: "http://metric-source.app-namespace:8080/metrics"
metric-config.external.http.json/aggregator: "max"
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
metric-config.external.unique-metric-name.json-path/json-key: "$.some-metric.value"
metric-config.external.unique-metric-name.json-path/endpoint: "http://metric-source.app-namespace:8080/metrics"
metric-config.external.unique-metric-name.json-path/aggregator: "max"
spec:
scaleTargetRef:
apiVersion: apps/v1
@@ -641,10 +640,10 @@ spec:
- type: External
external:
metric:
name: http
name: unique-metric-name
selector:
matchLabels:
identifier: unique-metric-name
type: json-path
target:
averageValue: 1
type: AverageValue
@@ -659,4 +658,3 @@ target. The following configuration values are supported:
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`.
+9
View File
@@ -0,0 +1,9 @@
apiVersion: v2
name: kube-metrics-adapter
version: 0.1.9
description: kube-metrics-adapter helm chart
home: https://github.com/zalando-incubator/kube-metrics-adapter
maintainers:
- name: The Zalando Incubator
email: opensource@zalando.de
url: https://github.com/zalando-incubator
@@ -0,0 +1,15 @@
{{- if .Values.enableCustomMetricsApi }}
apiVersion: apiregistration.k8s.io/v1beta1
kind: APIService
metadata:
name: v1beta1.custom.metrics.k8s.io
spec:
service:
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
group: custom.metrics.k8s.io
version: v1beta1
insecureSkipTLSVerify: {{ .Values.tls.skipTLSVerify }}
groupPriorityMinimum: 100
versionPriority: 100
{{- end}}
+191
View File
@@ -0,0 +1,191 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
labels:
application: kube-metrics-adapter
version: {{ .Values.registry.imageTag }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
application: kube-metrics-adapter
template:
metadata:
labels:
application: kube-metrics-adapter
version: {{ .Values.registry.imageTag }}
spec:
serviceAccountName: kube-metrics-adapter
containers:
- name: kube-metrics-adapter
image: {{ .Values.registry.image}}:{{ .Values.registry.imageTag }}
args:
{{- if .Values.addDirectoryHeader }}
- --add_dir_header={{ .Values.addDirectoryHeader }}
{{- end}}
{{- if .Values.log.alsoToStderr }}
- --alsologtostderr={{ .Values.log.alsoToStderr }}
{{- end}}
{{- if .Values.authentication.kubeConfig }}
- --authentication-kubeconfig={{ .Values.authentication.kubeConfig }}
{{- end}}
{{- if .Values.authentication.skipLookup }}
- --authentication-skip-lookup={{ .Values.authentication.skipLookup }}
{{- end}}
{{- if .Values.authentication.tokenWebhookCacheTtl }}
- --authentication-token-webhook-cache-ttl={{ .Values.authentication.tokenWebhookCacheTtl }}
{{- end}}
{{- if .Values.authentication.tolerateLookupFailure }}
- --authentication-tolerate-lookup-failure={{ .Values.authentication.tolerateLookupFailure }}
{{- end}}
{{- if .Values.authorization.alwaysAllowPaths }}
- --authorization-always-allow-paths={{ .Values.authorization.alwaysAllowPaths }}
{{- end}}
{{- if .Values.authorization.kubeConfig }}
- --authorization-kubeconfig={{ .Values.authorization.kubeConfig }}
{{- end}}
{{- if .Values.authorization.webhookCache.authorizedTtl }}
- --authorization-webhook-cache-authorized-ttl={{ .Values.authorization.webhookCache.authorizedTtl }}
{{- end}}
{{- if .Values.authorization.webhookCache.unauthorizedTtl }}
- --authorization-webhook-cache-unauthorized-ttl={{ .Values.authorization.webhookCache.unauthorizedTtl }}
{{- end}}
{{- if .Values.aws.externalMetrics }}
- --aws-external-metrics={{ .Values.aws.externalMetrics }}
{{- end}}
{{- if .Values.aws.region }}
- --aws-region={{ .Values.aws.region }}
{{- end}}
{{- if .Values.tls.certificateDirectory }}
- --cert-dir={{ .Values.tls.certificateDirectory }}
{{- end}}
{{- if .Values.tls.clientCaFile }}
- --client-ca-file={{ .Values.tls.clientCaFile }}
{{- end}}
{{- if .Values.contentionProfiling }}
- --contention-profiling={{ .Values.contentionProfiling }}
{{- end}}
{{- if .Values.credentialsDirectory }}
- --credentials-dir={{ .Values.credentialsDirectory }}
{{- end}}
{{- if .Values.disregardIncompatibleHPAs }}
- --disregard-incompatible-hpas={{ .Values.disregardIncompatibleHPAs }}
{{- end}}
{{- if .Values.enableCustomMetricsApi }}
- --enable-custom-metrics-api={{ .Values.enableCustomMetricsApi }}
{{- end}}
{{- if .Values.enableExternalMetricsApi }}
- --enable-external-metrics-api={{ .Values.enableExternalMetricsApi }}
{{- end}}
{{- if .Values.http2MaxStreamsPerConnection }}
- --http2-max-streams-per-connection={{ .Values.http2MaxStreamsPerConnection }}
{{- end}}
{{- if .Values.influxDB.address }}
- --influxdb-address={{ .Values.influxDB.address }}
{{- end}}
{{- if .Values.influxDB.organization }}
- --influxdb-org={{ .Values.influxDB.organization }}
{{- end}}
{{- if .Values.influxDB.token }}
- --influxdb-token={{ .Values.influxDB.token }}
{{- end}}
{{- if .Values.listerKubeConfig }}
- --lister-kubeconfig={{ .Values.listerKubeConfig }}
{{- end}}
{{- if .Values.log.flushFrequency }}
- --log-flush-frequency={{ .Values.log.flushFrequency }}
{{- end}}
{{- if .Values.log.backtraceAtTraceLocation }}
- --log_backtrace_at={{ .Values.log.backtraceAtTraceLocation }}
{{- end}}
{{- if .Values.log.directory }}
- --log_dir={{ .Values.log.directory }}
{{- end}}
{{- if .Values.log.file }}
- --log_file={{ .Values.log.file }}
{{- end}}
{{- if .Values.log.fileMaxSize }}
- --log_file_max_size={{ .Values.log.fileMaxSize }}
{{- end}}
{{- if .Values.log.toStderr }}
- --logtostderr={{ .Values.log.toStderr }}
{{- end}}
{{- if .Values.prometheus.metricsAddress }}
- --metrics-address={{ .Values.prometheus.metricsAddress }}
{{- end}}
{{- if .Values.profiling }}
- --profiling={{ .Values.profiling }}
{{- end}}
{{- if .Values.prometheus.server }}
- --prometheus-server={{ .Values.prometheus.server }}
{{- end}}
{{- if .Values.requestHeader.allowedNames }}
- --requestheader-allowed-names={{ .Values.requestHeader.allowedNames }}
{{- end}}
{{- if .Values.requestHeader.clientCaFile }}
- --requestheader-client-ca-file={{ .Values.requestHeader.clientCaFile }}
{{- end}}
{{- if .Values.requestHeader.extraHeadersPrefix }}
- --requestheader-extra-headers-prefix={{ .Values.requestHeader.extraHeadersPrefix }}
{{- end}}
{{- if .Values.requestHeader.groupHeaders }}
- --requestheader-group-headers={{ .Values.requestHeader.groupHeaders }}
{{- end}}
{{- if .Values.requestHeader.usernameHeaders }}
- --requestheader-username-headers={{ .Values.requestHeader.usernameHeaders }}
{{- end}}
- --secure-port={{ .Values.service.internalPort }}
{{- if .Values.log.skipHeaders }}
- --skip_headers={{ .Values.log.skipHeaders }}
{{- end}}
{{- if .Values.log.skipLogHeaders }}
- --skip_log_headers={{ .Values.log.skipLogHeaders }}
{{- end}}
{{- if .Values.skipperBackendsAnnotation }}
- --skipper-backends-annotation={{ .Values.skipperBackendsAnnotation }}
{{- end}}
{{- if .Values.skipperIngressMetrics }}
- --skipper-ingress-metrics={{ .Values.skipperIngressMetrics }}
{{- end}}
{{- if .Values.log.stderrThreshold }}
- --stderrthreshold={{ .Values.log.stderrThreshold }}
{{- end}}
{{- if .Values.tls.certFile }}
- --tls-cert-file={{ .Values.tls.certFile }}
{{- end}}
{{- if .Values.tls.cipherSuites }}
- --tls-cipher-suites={{ .Values.tls.cipherSuites }}
{{- end}}
{{- if .Values.tls.minVersion }}
- --tls-min-version={{ .Values.tls.minVersion }}
{{- end}}
{{- if .Values.tls.privateKeyFile }}
- --tls-private-key-file={{ .Values.tls.privateKeyFile }}
{{- end}}
{{- if .Values.tls.sniCertKey }}
- --tls-sni-cert-key={{ .Values.tls.sniCertKey }}
{{- end}}
{{- if .Values.token }}
- --token={{ .Values.token }}
{{- end}}
{{- if .Values.log.level }}
- --v={{ .Values.log.level }}
{{- end}}
{{- if .Values.vmodule }}
- --vmodule={{ .Values.vmodule }}
{{- end}}
{{- if .Values.zmon.kariosdbEndpoint }}
- --zmon-kariosdb-endpoint={{ .Values.zmon.kariosdbEndpoint }}
{{- end}}
{{- if .Values.zmon.tokenName }}
- --zmon-token-name={{ .Values.zmon.tokenName }}
{{- end}}
resources:
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
@@ -0,0 +1,15 @@
{{- if .Values.enableExternalMetricsApi }}
apiVersion: apiregistration.k8s.io/v1beta1
kind: APIService
metadata:
name: v1beta1.external.metrics.k8s.io
spec:
service:
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
group: external.metrics.k8s.io
version: v1beta1
insecureSkipTLSVerify: {{ .Values.tls.skipTLSVerify }}
groupPriorityMinimum: 100
versionPriority: 100
{{- end}}
+154
View File
@@ -0,0 +1,154 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube-metrics-adapter-server-resources
rules:
- apiGroups:
- custom.metrics.k8s.io
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-metrics-server-resources
rules:
- apiGroups:
- external.metrics.k8s.io
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube-metrics-adapter-resource-reader
rules:
- apiGroups:
- ""
resources:
- namespaces
- pods
- services
- configmaps
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube-metrics-adapter-resource-collector
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- apiGroups:
- apps
resources:
- deployments
- statefulsets
verbs:
- get
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: hpa-controller-custom-metrics
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kube-metrics-adapter-server-resources
subjects:
- kind: ServiceAccount
name: horizontal-pod-autoscaler
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: hpa-controller-external-metrics
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-metrics-server-resources
subjects:
- kind: ServiceAccount
name: horizontal-pod-autoscaler
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kube-metrics-adapter-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: custom-metrics:system:auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-metrics-adapter-resource-collector
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kube-metrics-adapter-resource-collector
subjects:
- kind: ServiceAccount
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-metrics-adapter-resource-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kube-metrics-adapter-resource-reader
subjects:
- kind: ServiceAccount
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
+5
View File
@@ -0,0 +1,5 @@
kind: ServiceAccount
apiVersion: v1
metadata:
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: kube-metrics-adapter
namespace: {{ .Values.namespace }}
spec:
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.internalPort }}
selector:
application: kube-metrics-adapter
+94
View File
@@ -0,0 +1,94 @@
namespace: kube-system
replicas: 1
registry:
image: registry.opensource.zalan.do/teapot/kube-metrics-adapter
imageTag: v0.1.9
imagePullPolicy: IfNotPresent
service:
port: 443
internalPort: 443
addDirectoryHeader:
contentionProfiling:
profiling:
enableCustomMetricsApi:
enableExternalMetricsApi:
credentialsDirectory:
disregardIncompatibleHPAs:
http2MaxStreamsPerConnection:
listerKubeConfig:
skipperBackendsAnnotation:
skipperIngressMetrics:
token:
vmodule:
authentication:
kubeConfig:
skipLookup:
tokenWebhookCacheTtl:
tolerateLookupFailure:
authorization:
kubeConfig:
alwaysAllowPaths:
webhookCache:
authorizedTtl:
unauthorizedTtl:
aws:
externalMetrics:
region:
influxDB:
address:
organization:
token:
log:
alsoToStderr:
toStderr:
flushFrequency:
backtraceAtTraceLocation:
directory:
file:
fileMaxSize:
level:
stderrThreshold:
skipHeaders:
skipLogHeaders:
prometheus:
server: http://prometheus.kube-system.svc.cluster.local
metricsAddress:
requestHeader:
allowedNames:
clientCaFile:
extraHeadersPrefix:
groupHeaders:
usernameHeaders:
tls:
skipTLSVerify: true
certificateDirectory:
clientCaFile:
certFile:
cipherSuites:
minVersion:
privateKeyFile:
sniCertKey:
zmon:
kariosdbEndpoint:
tokenName:
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi
+3 -2
View File
@@ -6,7 +6,7 @@ metadata:
labels:
application: custom-metrics-consumer
annotations:
# metric-config.<metricType>.<metricName>.<collectorName>/<configKey>
# metric-config.<metricType>.<metricName>.<collectorType>/<configKey>
metric-config.pods.queue-length.json-path/json-key: "$.queue.length"
metric-config.pods.queue-length.json-path/path: /metrics
metric-config.pods.queue-length.json-path/port: "9090"
@@ -50,9 +50,10 @@ spec:
- type: External
external:
metric:
name: sqs-queue-length
name: app-queue-length
selector:
matchLabels:
type: sqs-queue-length
queue-name: foobar
region: eu-central-1
target:
+19 -23
View File
@@ -2,36 +2,32 @@ module github.com/zalando-incubator/kube-metrics-adapter
require (
github.com/NYTimes/gziphandler v1.0.1 // indirect
github.com/aws/aws-sdk-go v1.35.19
github.com/go-openapi/spec v0.19.12
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/influxdata/influxdb-client-go v0.1.5
github.com/kubernetes-incubator/custom-metrics-apiserver v0.0.0-20201023074945-51cc7b53320e
github.com/lib/pq v1.2.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/aws/aws-sdk-go v1.37.1
github.com/go-openapi/spec v0.20.2
github.com/influxdata/influxdb-client-go v0.2.0
github.com/influxdata/line-protocol v0.0.0-20201012155213-5f565037cbc9 // indirect
github.com/kubernetes-sigs/custom-metrics-apiserver v0.0.0-20201216091021-1b9fa998bbaa
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/onsi/gomega v1.8.1 // indirect
github.com/prometheus/client_golang v1.8.0
github.com/prometheus/common v0.14.0
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/common v0.15.0
github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v0.0.7
github.com/spyzhov/ajson v0.4.2
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
github.com/zalando-incubator/cluster-lifecycle-manager v0.0.0-20180921141935-824b77fb1f84
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/text v0.3.4 // indirect
golang.org/x/tools v0.0.0-20200204192400-7124308813f3 // indirect
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e // indirect
honnef.co/go/tools v0.0.1-2020.1.3 // indirect
k8s.io/api v0.18.8
k8s.io/apimachinery v0.18.8
k8s.io/apiserver v0.18.8
k8s.io/client-go v0.18.8
k8s.io/component-base v0.18.8
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/api v0.20.0
k8s.io/apimachinery v0.20.0
k8s.io/apiserver v0.20.0
k8s.io/client-go v0.20.0
k8s.io/component-base v0.20.0
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6
k8s.io/metrics v0.18.8
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd
k8s.io/metrics v0.20.0
)
go 1.15
+303 -123
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -15,7 +15,7 @@ const (
)
type AnnotationConfigs struct {
CollectorName string
CollectorType string
Configs map[string]string
PerReplica bool
Interval time.Duration
@@ -64,14 +64,14 @@ func (m AnnotationConfigMap) Parse(annotations map[string]string) error {
config, ok := m[key]
if !ok {
config = &AnnotationConfigs{
CollectorName: metricCollector,
CollectorType: metricCollector,
Configs: map[string]string{},
}
m[key] = config
}
// TODO: fail if collector name doesn't match
if config.CollectorName != metricCollector {
if config.CollectorType != metricCollector {
continue
}
File diff suppressed because it is too large Load Diff
+6 -8
View File
@@ -33,12 +33,7 @@ func NewAWSCollectorPlugin(sessions map[string]*session.Session) *AWSCollectorPl
// NewCollector initializes a new skipper collector from the specified HPA.
func (c *AWSCollectorPlugin) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error) {
switch config.Metric.Name {
case AWSSQSQueueLengthMetric:
return NewAWSSQSCollector(c.sessions, config, interval)
}
return nil, fmt.Errorf("metric '%s' not supported", config.Metric.Name)
return NewAWSSQSCollector(c.sessions, hpa, config, interval)
}
type AWSSQSCollector struct {
@@ -47,11 +42,12 @@ type AWSSQSCollector struct {
region string
queueURL string
queueName string
namespace string
metric autoscalingv2.MetricIdentifier
metricType autoscalingv2.MetricSourceType
}
func NewAWSSQSCollector(sessions map[string]*session.Session, config *MetricConfig, interval time.Duration) (*AWSSQSCollector, error) {
func NewAWSSQSCollector(sessions map[string]*session.Session, hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (*AWSSQSCollector, error) {
if config.Metric.Selector == nil {
return nil, fmt.Errorf("selector for queue is not specified")
}
@@ -85,6 +81,7 @@ func NewAWSSQSCollector(sessions map[string]*session.Session, config *MetricConf
interval: interval,
queueURL: aws.StringValue(resp.QueueUrl),
queueName: name,
namespace: hpa.Namespace,
metric: config.Metric,
metricType: config.Type,
}, nil
@@ -108,7 +105,8 @@ func (c *AWSSQSCollector) GetMetrics() ([]CollectedMetric, error) {
}
metricValue := CollectedMetric{
Type: c.metricType,
Namespace: c.namespace,
Type: c.metricType,
External: external_metrics.ExternalMetricValue{
MetricName: c.metric.Name,
MetricLabels: c.metric.Selector.MatchLabels,
+31 -9
View File
@@ -4,12 +4,17 @@ import (
"fmt"
"time"
log "github.com/sirupsen/logrus"
"github.com/zalando-incubator/kube-metrics-adapter/pkg/annotations"
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
"k8s.io/metrics/pkg/apis/custom_metrics"
"k8s.io/metrics/pkg/apis/external_metrics"
)
const (
typeLabelKey = "type"
)
type ObjectReference struct {
autoscalingv2.CrossVersionObjectReference
Namespace string
@@ -19,6 +24,7 @@ type CollectorFactory struct {
podsPlugins pluginMap
objectPlugins objectPluginMap
externalPlugins map[string]CollectorPlugin
logger *log.Entry
}
type objectPluginMap struct {
@@ -39,6 +45,7 @@ func NewCollectorFactory() *CollectorFactory {
Named: map[string]*pluginMap{},
},
externalPlugins: map[string]CollectorPlugin{},
logger: log.WithFields(log.Fields{"collector": "true"}),
}
}
@@ -112,7 +119,7 @@ func (c *CollectorFactory) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscal
switch config.Type {
case autoscalingv2.PodsMetricSourceType:
// first try to find a plugin by format
if plugin, ok := c.podsPlugins.Named[config.CollectorName]; ok {
if plugin, ok := c.podsPlugins.Named[config.CollectorType]; ok {
return plugin.NewCollector(hpa, config, interval)
}
@@ -123,7 +130,7 @@ func (c *CollectorFactory) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscal
case autoscalingv2.ObjectMetricSourceType:
// first try to find a plugin by kind
if kinds, ok := c.objectPlugins.Named[config.ObjectReference.Kind]; ok {
if plugin, ok := kinds.Named[config.CollectorName]; ok {
if plugin, ok := kinds.Named[config.CollectorType]; ok {
return plugin.NewCollector(hpa, config, interval)
}
@@ -134,7 +141,7 @@ func (c *CollectorFactory) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscal
}
// else try to find a default plugin for this kind
if plugin, ok := c.objectPlugins.Any.Named[config.CollectorName]; ok {
if plugin, ok := c.objectPlugins.Any.Named[config.CollectorType]; ok {
return plugin.NewCollector(hpa, config, interval)
}
@@ -142,7 +149,21 @@ func (c *CollectorFactory) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscal
return c.objectPlugins.Any.Any.NewCollector(hpa, config, interval)
}
case autoscalingv2.ExternalMetricSourceType:
if plugin, ok := c.externalPlugins[config.Metric.Name]; ok {
// First type to get metric type from the `type` label,
// otherwise fall back to the legacy metric name based mapping.
var pluginKey string
if config.Metric.Selector != nil && config.Metric.Selector.MatchLabels != nil {
if typ, ok := config.Metric.Selector.MatchLabels[typeLabelKey]; ok {
pluginKey = typ
}
}
if pluginKey == "" {
pluginKey = config.Metric.Name
c.logger.Warnf("HPA %s/%s is using deprecated metric type identifier '%s'", hpa.Namespace, hpa.Name, config.Metric.Name)
}
if plugin, ok := c.externalPlugins[pluginKey]; ok {
return plugin.NewCollector(hpa, config, interval)
}
}
@@ -156,9 +177,10 @@ type MetricTypeName struct {
}
type CollectedMetric struct {
Type autoscalingv2.MetricSourceType
Custom custom_metrics.MetricValue
External external_metrics.ExternalMetricValue
Type autoscalingv2.MetricSourceType
Namespace string
Custom custom_metrics.MetricValue
External external_metrics.ExternalMetricValue
}
type Collector interface {
@@ -168,7 +190,7 @@ type Collector interface {
type MetricConfig struct {
MetricTypeName
CollectorName string
CollectorType string
Config map[string]string
ObjectReference custom_metrics.ObjectReference
PerReplica bool
@@ -228,7 +250,7 @@ func ParseHPAMetrics(hpa *autoscalingv2.HorizontalPodAutoscaler) ([]*MetricConfi
annotationConfigs, present := parser.GetAnnotationConfig(typeName.Metric.Name, typeName.Type)
if present {
config.CollectorName = annotationConfigs.CollectorName
config.CollectorType = annotationConfigs.CollectorType
config.Interval = annotationConfigs.Interval
config.PerReplica = annotationConfigs.PerReplica
// configs specified in annotations takes precedence
+129
View File
@@ -0,0 +1,129 @@
package collector
import (
"testing"
"time"
"github.com/stretchr/testify/require"
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type mockCollectorPlugin struct {
Name string
}
func (c *mockCollectorPlugin) NewCollector(hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error) {
return &mockCollector{Name: c.Name}, nil
}
type mockCollector struct {
Name string
}
func (c *mockCollector) GetMetrics() ([]CollectedMetric, error) {
return nil, nil
}
func (c *mockCollector) Interval() time.Duration {
return 0
}
func TestNewCollector(t *testing.T) {
for _, tc := range []struct {
msg string
hpa *autoscalingv2.HorizontalPodAutoscaler
expectedCollector string
}{
{
msg: "should get create collector type from legacy metric name",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "external-1",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"x": "y"},
},
},
},
},
},
},
},
expectedCollector: "external-1",
},
{
msg: "should get create collector type from type label (ignore legacy metric name)",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "external-1",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"type": "external-2"},
},
},
},
},
},
},
},
expectedCollector: "external-2",
},
{
msg: "should not find collector when no collector matches",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "external-3",
},
},
},
},
},
},
expectedCollector: "",
},
} {
t.Run(tc.msg, func(t *testing.T) {
collectorFactory := NewCollectorFactory()
for _, collector := range []string{"1", "2"} {
collectorFactory.RegisterExternalCollector([]string{"external-" + collector}, &mockCollectorPlugin{Name: "external-" + collector})
}
configs, err := ParseHPAMetrics(tc.hpa)
require.NoError(t, err)
require.Len(t, configs, 1)
collector, err := collectorFactory.NewCollector(tc.hpa, configs[0], 0)
if tc.expectedCollector == "" {
require.Error(t, err)
} else {
require.NoError(t, err)
c, ok := collector.(*mockCollector)
require.True(t, ok)
require.Equal(t, tc.expectedCollector, c.Name)
}
})
}
}
+9 -4
View File
@@ -14,7 +14,8 @@ import (
)
const (
HTTPMetricName = "http"
HTTPJSONPathType = "json-path"
HTTPMetricNameLegacy = "http"
HTTPEndpointAnnotationKey = "endpoint"
HTTPJsonPathAnnotationKey = "json-key"
identifierLabel = "identifier"
@@ -26,8 +27,10 @@ func NewHTTPCollectorPlugin() (*HTTPCollectorPlugin, error) {
return &HTTPCollectorPlugin{}, nil
}
func (p *HTTPCollectorPlugin) NewCollector(_ *v2beta2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error) {
collector := &HTTPCollector{}
func (p *HTTPCollectorPlugin) NewCollector(hpa *v2beta2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error) {
collector := &HTTPCollector{
namespace: hpa.Namespace,
}
var (
value string
ok bool
@@ -73,6 +76,7 @@ func (p *HTTPCollectorPlugin) NewCollector(_ *v2beta2.HorizontalPodAutoscaler, c
type HTTPCollector struct {
endpoint *url.URL
interval time.Duration
namespace string
metricType v2beta2.MetricSourceType
metricsGetter *httpmetrics.JSONPathMetricsGetter
metric v2beta2.MetricIdentifier
@@ -85,7 +89,8 @@ func (c *HTTPCollector) GetMetrics() ([]CollectedMetric, error) {
}
value := CollectedMetric{
Type: c.metricType,
Namespace: c.namespace,
Type: c.metricType,
External: external_metrics.ExternalMetricValue{
MetricName: c.metric.Name,
MetricLabels: c.metric.Selector.MatchLabels,
+8 -1
View File
@@ -8,6 +8,8 @@ import (
"github.com/stretchr/testify/require"
"k8s.io/api/autoscaling/v2beta2"
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -60,7 +62,12 @@ func TestHTTPCollector(t *testing.T) {
plugin, err := NewHTTPCollectorPlugin()
require.NoError(t, err)
testConfig := makeTestHTTPCollectorConfig(testServer, tc.aggregator)
collector, err := plugin.NewCollector(nil, testConfig, testInterval)
hpa := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
}
collector, err := plugin.NewCollector(hpa, testConfig, testInterval)
require.NoError(t, err)
metrics, err := collector.GetMetrics()
require.NoError(t, err)
+8 -4
View File
@@ -15,7 +15,8 @@ import (
)
const (
InfluxDBMetricName = "flux-query"
InfluxDBMetricType = "influxdb"
InfluxDBMetricNameLegacy = "flux-query"
influxDBAddressKey = "address"
influxDBTokenKey = "token"
influxDBOrgKey = "org"
@@ -39,7 +40,7 @@ func NewInfluxDBCollectorPlugin(client kubernetes.Interface, address, token, org
}
func (p *InfluxDBCollectorPlugin) NewCollector(hpa *v2beta2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration) (Collector, error) {
return NewInfluxDBCollector(p.address, p.token, p.org, config, interval)
return NewInfluxDBCollector(hpa, p.address, p.token, p.org, config, interval)
}
type InfluxDBCollector struct {
@@ -52,13 +53,15 @@ type InfluxDBCollector struct {
metric autoscalingv2.MetricIdentifier
metricType autoscalingv2.MetricSourceType
query string
namespace string
}
func NewInfluxDBCollector(address string, token string, org string, config *MetricConfig, interval time.Duration) (*InfluxDBCollector, error) {
func NewInfluxDBCollector(hpa *v2beta2.HorizontalPodAutoscaler, address string, token string, org string, config *MetricConfig, interval time.Duration) (*InfluxDBCollector, error) {
collector := &InfluxDBCollector{
interval: interval,
metric: config.Metric,
metricType: config.Type,
namespace: hpa.Namespace,
}
switch configType := config.Type; configType {
case autoscalingv2.ObjectMetricSourceType:
@@ -134,7 +137,8 @@ func (c *InfluxDBCollector) GetMetrics() ([]CollectedMetric, error) {
return nil, err
}
cm := CollectedMetric{
Type: c.metricType,
Namespace: c.namespace,
Type: c.metricType,
External: external_metrics.ExternalMetricValue{
MetricName: c.metric.Name,
MetricLabels: c.metric.Selector.MatchLabels,
+13 -6
View File
@@ -6,10 +6,17 @@ import (
"time"
"k8s.io/api/autoscaling/v2beta2"
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestInfluxDBCollector_New(t *testing.T) {
hpa := &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
}
t.Run("simple", func(t *testing.T) {
m := &MetricConfig{
MetricTypeName: MetricTypeName{
@@ -24,7 +31,7 @@ func TestInfluxDBCollector_New(t *testing.T) {
},
},
},
CollectorName: "influxdb",
CollectorType: "influxdb",
Config: map[string]string{
"range1m": `from(bucket: "?") |> range(start: -1m)`,
"range2m": `from(bucket: "?") |> range(start: -2m)`,
@@ -32,7 +39,7 @@ func TestInfluxDBCollector_New(t *testing.T) {
"query-name": "range2m",
},
}
c, err := NewInfluxDBCollector("http://localhost:9999", "secret", "deadbeef", m, time.Second)
c, err := NewInfluxDBCollector(hpa, "http://localhost:9999", "secret", "deadbeef", m, time.Second)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -62,7 +69,7 @@ func TestInfluxDBCollector_New(t *testing.T) {
},
},
},
CollectorName: "influxdb",
CollectorType: "influxdb",
Config: map[string]string{
"range1m": `from(bucket: "?") |> range(start: -1m)`,
"range2m": `from(bucket: "?") |> range(start: -2m)`,
@@ -73,7 +80,7 @@ func TestInfluxDBCollector_New(t *testing.T) {
"query-name": "range3m",
},
}
c, err := NewInfluxDBCollector("http://localhost:8888", "secret", "deadbeef", m, time.Second)
c, err := NewInfluxDBCollector(hpa, "http://localhost:8888", "secret", "deadbeef", m, time.Second)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -140,10 +147,10 @@ func TestInfluxDBCollector_New(t *testing.T) {
t.Run("error - "+tc.name, func(t *testing.T) {
m := &MetricConfig{
MetricTypeName: tc.mTypeName,
CollectorName: "influxdb",
CollectorType: "influxdb",
Config: tc.config,
}
_, err := NewInfluxDBCollector("http://localhost:9999", "secret", "deadbeef", m, time.Second)
_, err := NewInfluxDBCollector(hpa, "http://localhost:9999", "secret", "deadbeef", m, time.Second)
if err == nil {
t.Fatal("expected error got none")
}
+4 -3
View File
@@ -61,7 +61,7 @@ func NewPodCollector(client kubernetes.Interface, hpa *autoscalingv2.HorizontalP
}
var getter httpmetrics.PodMetricsGetter
switch config.CollectorName {
switch config.CollectorType {
case "json-path":
var err error
getter, err = httpmetrics.NewPodMetricsJSONPathGetter(config.Config)
@@ -69,7 +69,7 @@ func NewPodCollector(client kubernetes.Interface, hpa *autoscalingv2.HorizontalP
return nil, err
}
default:
return nil, fmt.Errorf("format '%s' not supported", config.CollectorName)
return nil, fmt.Errorf("format '%s' not supported", config.CollectorType)
}
c.Getter = getter
@@ -118,7 +118,8 @@ func (c *PodCollector) getPodMetric(pod corev1.Pod, ch chan CollectedMetric, err
}
ch <- CollectedMetric{
Type: c.metricType,
Namespace: c.namespace,
Type: c.metricType,
Custom: custom_metrics.MetricValue{
DescribedObject: custom_metrics.ObjectReference{
APIVersion: "v1",
+1 -1
View File
@@ -97,7 +97,7 @@ func makeTestHTTPServer(t *testing.T, values [][]int64) (string, string, *testMe
func makeTestConfig(port string) *MetricConfig {
return &MetricConfig{
CollectorName: "json-path",
CollectorType: "json-path",
Config: map[string]string{"json-key": "$.values", "port": port, "path": "/metrics", "aggregator": "sum"},
}
}
+19 -10
View File
@@ -19,7 +19,8 @@ import (
)
const (
PrometheusMetricName = "prometheus-query"
PrometheusMetricType = "prometheus"
PrometheusMetricNameLegacy = "prometheus-query"
prometheusQueryNameLabelKey = "query-name"
prometheusServerAnnotationKey = "prometheus-server"
)
@@ -96,16 +97,22 @@ func NewPrometheusCollector(client kubernetes.Interface, promAPI promv1.API, hpa
return nil, fmt.Errorf("selector for prometheus query is not specified")
}
queryName, ok := config.Config[prometheusQueryNameLabelKey]
if !ok {
return nil, fmt.Errorf("query name not specified on metric")
}
if v, ok := config.Config[queryName]; ok {
if v, ok := config.Config["query"]; ok {
// TODO: validate query
c.query = v
} else {
return nil, fmt.Errorf("no prometheus query defined for metric")
// support legacy behavior of mapping query name to metric
queryName, ok := config.Config[prometheusQueryNameLabelKey]
if !ok {
return nil, fmt.Errorf("query or query name not specified on metric")
}
if v, ok := config.Config[queryName]; ok {
// TODO: validate query
c.query = v
} else {
return nil, fmt.Errorf("no prometheus query defined for metric")
}
}
// Use custom Prometheus URL if defined in HPA annotation.
@@ -167,7 +174,8 @@ func (c *PrometheusCollector) GetMetrics() ([]CollectedMetric, error) {
switch c.metricType {
case autoscalingv2.ObjectMetricSourceType:
metricValue = CollectedMetric{
Type: c.metricType,
Namespace: c.hpa.Namespace,
Type: c.metricType,
Custom: custom_metrics.MetricValue{
DescribedObject: c.objectReference,
Metric: custom_metrics.MetricIdentifier{Name: c.metric.Name, Selector: c.metric.Selector},
@@ -177,7 +185,8 @@ func (c *PrometheusCollector) GetMetrics() ([]CollectedMetric, error) {
}
case autoscalingv2.ExternalMetricSourceType:
metricValue = CollectedMetric{
Type: c.metricType,
Namespace: c.hpa.Namespace,
Type: c.metricType,
External: external_metrics.ExternalMetricValue{
MetricName: c.metric.Name,
MetricLabels: c.metric.Selector.MatchLabels,
+193
View File
@@ -0,0 +1,193 @@
package collector
import (
"testing"
"github.com/stretchr/testify/require"
autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestNewPrometheusCollector(t *testing.T) {
for _, tc := range []struct {
msg string
hpa *autoscalingv2.HorizontalPodAutoscaler
// config *MetricConfig
valid bool
expectedQuery string
}{
{
msg: "valid external metric configuration should work",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"metric-config.external.rps.prometheus/query": "sum(rate(rps[1m]))",
},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "rps",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"type": "prometheus"},
},
},
},
},
},
},
},
expectedQuery: "sum(rate(rps[1m]))",
},
{
msg: "missing query for external metric should not work",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"metric-config.external.rps.prometheus/not-query": "sum(rate(rps[1m]))",
},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: "rps",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"type": "prometheus"},
},
},
},
},
},
},
},
expectedQuery: "",
},
{
msg: "valid legacy external metric configuration should work",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"metric-config.external.prometheus-query.prometheus/rps": "sum(rate(rps[1m]))",
},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: PrometheusMetricNameLegacy,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{prometheusQueryNameLabelKey: "rps"},
},
},
},
},
},
},
},
expectedQuery: "sum(rate(rps[1m]))",
},
{
msg: "invalid legacy external metric configuration with wrong query name should not work",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"metric-config.external.prometheus-query.prometheus/rps": "sum(rate(rps[1m]))",
},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: PrometheusMetricNameLegacy,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{prometheusQueryNameLabelKey: "not-rps"},
},
},
},
},
},
},
},
expectedQuery: "",
},
{
msg: "invalid legacy external metric configuration with missing selector should not work",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"metric-config.external.prometheus-query.prometheus/rps": "sum(rate(rps[1m]))",
},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: PrometheusMetricNameLegacy,
},
},
},
},
},
},
expectedQuery: "",
},
{
msg: "invalid legacy external metric configuration with missing query name should not work",
hpa: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"metric-config.external.prometheus-query.prometheus/rps": "sum(rate(rps[1m]))",
},
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ExternalMetricSourceType,
External: &autoscalingv2.ExternalMetricSource{
Metric: autoscalingv2.MetricIdentifier{
Name: PrometheusMetricNameLegacy,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"not-query-name": "not-rps"},
},
},
},
},
},
},
},
expectedQuery: "",
},
} {
t.Run(tc.msg, func(t *testing.T) {
collectorFactory := NewCollectorFactory()
promPlugin, err := NewPrometheusCollectorPlugin(nil, "http://prometheus")
require.NoError(t, err)
collectorFactory.RegisterExternalCollector([]string{PrometheusMetricType, PrometheusMetricNameLegacy}, promPlugin)
configs, err := ParseHPAMetrics(tc.hpa)
require.NoError(t, err)
require.Len(t, configs, 1)
collector, err := collectorFactory.NewCollector(tc.hpa, configs[0], 0)
if tc.expectedQuery != "" {
require.NoError(t, err)
c, ok := collector.(*PrometheusCollector)
require.True(t, ok)
require.Equal(t, tc.expectedQuery, c.query)
} else {
require.Error(t, err)
}
})
}
}

Some files were not shown because too many files have changed in this diff Show More