Compare commits

..

446 commits

Author SHA1 Message Date
Kubernetes Prow Robot
01919d0ef1
Merge pull request #686 from kubernetes-sigs/dependabot/go_modules/golang.org/x/crypto-0.31.0
build(deps): bump golang.org/x/crypto from 0.22.0 to 0.31.0
2025-04-01 03:34:37 -07:00
dependabot[bot]
21ea0ab279
build(deps): bump golang.org/x/crypto from 0.22.0 to 0.31.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.22.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.22.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-12 00:01:25 +00:00
Kubernetes Prow Robot
c2ae4cdaf1
Merge pull request #672 from chc5/go-upgrade-cve-fix
Upgrade Go version to 1.22.5 to fix CVEs.
2024-07-23 10:06:51 -07:00
Chieh Chen
26d05b7ae9 Upgrade Go version to 1.22.5 to fix CVEs. 2024-07-17 20:13:25 +00:00
Damien Grisonnet
17cef511b1
Merge pull request #660 from dgrisonnet/cut-0.12.0
Cut release 0.12.0
2024-05-16 20:14:56 +02:00
Damien Grisonnet
9988fd3e91 *: cut release 0.12.0
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2024-05-16 20:01:59 +02:00
Kubernetes Prow Robot
06e1d3913e
Merge pull request #659 from dgrisonnet/bump-1.30
Bump to Kubernetes 1.30
2024-05-16 09:57:38 -07:00
Damien Grisonnet
39ef9fa0e7 test: use official image of kind
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2024-05-16 14:26:37 +02:00
Damien Grisonnet
01b29a6578 *: fix openapi-gen options
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2024-05-16 14:26:37 +02:00
Damien Grisonnet
1d31a46aa1 *: update-lint
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2024-05-16 14:26:37 +02:00
Damien Grisonnet
d3784c5725 test: bump test dependencies
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2024-05-16 14:26:37 +02:00
Damien Grisonnet
aba25ac4aa cmd: fix OpenAPI
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2024-05-16 14:26:37 +02:00
Damien Grisonnet
fdde189945 *: bump deps
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2024-05-16 14:26:36 +02:00
Kubernetes Prow Robot
63bd3e8d44
Merge pull request #653 from logicalhan/patch-1
Update OWNERS (add myself and dashpole to OWNERs)
2024-04-19 10:20:08 -07:00
Kubernetes Prow Robot
1692f124d3
Merge pull request #651 from kubernetes-sigs/dependabot/go_modules/google.golang.org/grpc-1.58.3
build(deps): bump google.golang.org/grpc from 1.58.2 to 1.58.3
2024-04-19 10:10:45 -07:00
Han Kang
11d7d2bb05
Update OWNERS (add myself and dashpole to OWNERs) 2024-04-18 10:11:01 -07:00
dependabot[bot]
b224085e86
build(deps): bump google.golang.org/grpc from 1.58.2 to 1.58.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.58.2 to 1.58.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.58.2...v1.58.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 15:31:29 +00:00
Kubernetes Prow Robot
5d9b01a57a
Merge pull request #649 from machine424/uppps
deps: upgrade github.com/golang/protobuf to v1.5.4 for better compati…
2024-04-05 08:30:48 -07:00
machine424
4d5c98d364
deps: upgrade github.com/golang/protobuf to v1.5.4 for better compatibilty, see https://github.com/golang/protobuf/issues/1596#issuecomment-1981208282
upgrade go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp to v0.49.0 to address CVE-2023-45142 even though prometheus-adapter isn't using it directly and isn't exposing any traces.
2024-04-04 09:48:53 +02:00
Kubernetes Prow Robot
b48bff400e
Merge pull request #599 from bogo-y/fix
Fix metric unregistered
2024-03-26 09:15:19 -07:00
Kubernetes Prow Robot
9156bf3fbc
Merge pull request #614 from kubernetes-sigs/dependabot/go_modules/google.golang.org/grpc-1.56.3
build(deps): bump google.golang.org/grpc from 1.53.0 to 1.56.3
2023-11-28 18:40:38 +01:00
Kubernetes Prow Robot
27cf936f32
Merge pull request #608 from jaybooth4/release-112
Cut release v0.11.2
2023-11-13 15:58:49 +01:00
Kubernetes Prow Robot
ed795c1ae2
Merge pull request #620 from jaybooth4/master
Update prometheus-adapter go patch versions
2023-11-08 18:22:35 +01:00
Jason
a64d132d91 Update prometheus-adapter go patch versions 2023-11-07 02:48:54 +00:00
dependabot[bot]
a01b094a63
build(deps): bump google.golang.org/grpc from 1.53.0 to 1.56.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.53.0 to 1.56.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.53.0...v1.56.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 21:56:01 +00:00
Kubernetes Prow Robot
f588141f08
Merge pull request #609 from kubernetes-sigs/dependabot/go_modules/golang.org/x/net-0.17.0
build(deps): bump golang.org/x/net from 0.8.0 to 0.17.0
2023-11-06 22:54:46 +01:00
Kubernetes Prow Robot
98e716c7d3
Merge pull request #618 from machine424/d-http2
Add a toggle to disable HTTP/2 on the server to mitigate CVE-2023-44487
2023-10-31 17:16:11 +01:00
machine424
ba77337ae4
Add a toggle to disable HTTP/2 on the server to mitigate CVE-2023-44487
until the Go standard library and golang.org/x/net are fully fixed.
2023-10-30 09:48:56 +01:00
bogo
a5bcb39046
replace "endpoint" with "path" 2023-10-23 11:15:51 +08:00
bogo
2a4a4316dd run make update-lint and set EnabledMetrics=false in the server config 2023-10-13 20:14:34 +08:00
dependabot[bot]
f82ee9d1dc
build(deps): bump golang.org/x/net from 0.8.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.8.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-11 23:39:51 +00:00
bogo
a53ee9eed1
replace "endpoint" with "path"
Co-authored-by: Simon Pasquier <spasquie@redhat.com>
2023-10-07 10:40:52 +08:00
Jason Booth
7ba3c13bb6 Cut release v0.11.2 2023-09-08 13:26:38 -04:00
Kubernetes Prow Robot
891c52fe00
Merge pull request #606 from jaybooth4/upversion
Update prometheus-adapter go patch version to 1.20.7
2023-09-08 10:12:15 -07:00
bogo
e772844ed8
normalize import order 2023-09-04 11:01:25 +08:00
Jason Booth
6a1ba321da Update prometheus-adapter go patch versions
Upgrade to 1.20.7 to address multiple CVE security findings
    CVE-2023-29404
    CVE-2023-29402
    CVE-2023-29405
    CVE-2023-29403
    CVE-2023-39533
    CVE-2023-29409
    CVE-2023-29406
2023-08-30 13:53:44 -04:00
bogo
0032610ace change the latency metric and dependency inject prometheus registry 2023-08-29 17:19:47 +08:00
bogo
fda3dad49b
Merge pull request #1 from kubernetes-sigs/master
update my dev brunch
2023-08-29 15:32:10 +08:00
Damien Grisonnet
4cc5de93cb
Merge pull request #604 from dgrisonnet/cut-release-0.11.1
Cut release v0.11.1
2023-08-24 15:30:25 +02:00
Damien Grisonnet
a4100f047a Cut release v0.11.1
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2023-08-24 15:13:50 +02:00
Damien Grisonnet
cb883fb789
Merge pull request #601 from dgrisonnet/fix-multiarch
Fix multiarch image build
2023-08-22 18:18:47 +02:00
Damien Grisonnet
198c469805 Fix multiarch image build
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2023-08-22 18:01:46 +02:00
bogo
7cf3ac5d90
Update metric buckets 2023-08-17 20:57:47 +08:00
bogo
966ef227fe Fix metric unregistered 2023-08-17 20:23:01 +08:00
Kubernetes Prow Robot
74ba84b76e
Merge pull request #592 from kubernetes-sigs/dependabot/go_modules/google.golang.org/grpc-1.53.0
build(deps): bump google.golang.org/grpc from 1.40.0 to 1.53.0
2023-07-27 05:56:09 -07:00
Damien Grisonnet
36fbcc78f1
Merge pull request #596 from dgrisonnet/release-0.11.0
Cut release 0.11.0
2023-07-25 14:23:33 +02:00
Damien Grisonnet
0a6e74a5b3 *: cut release 0.11.0
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2023-07-25 14:06:46 +02:00
dependabot[bot]
147dacee4a
build(deps): bump google.golang.org/grpc from 1.40.0 to 1.53.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.40.0 to 1.53.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.40.0...v1.53.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-25 09:38:23 +00:00
Damien Grisonnet
f733e2f74d
Merge pull request #586 from dgrisonnet/k8s-1.27
*: bump go to 1.20 and k8s deps to 0.27.2
2023-07-25 11:37:17 +02:00
Damien Grisonnet
b50333c035 cmd/adapter: add support for openapi v3
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2023-06-22 13:52:06 +02:00
Damien Grisonnet
f69aae4c78
Merge pull request #587 from dgrisonnet/update-lint
Update golangci-lint to 1.53.2
2023-06-20 18:42:42 +02:00
Damien Grisonnet
8579be6c7b manifests: remove deprecated klog flag
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2023-06-20 18:39:51 +02:00
Damien Grisonnet
e69388346f *: bump go to 1.20 and k8s deps to 0.27.2
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2023-06-20 18:39:50 +02:00
Damien Grisonnet
86efb37019 Update golangci-lint to 1.53.2
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2023-06-13 15:02:46 +02:00
Kubernetes Prow Robot
c8caa11da1
Merge pull request #583 from brcorey/master
fix: use dl.k8s.io, not kubernetes-release bucket
2023-05-17 22:30:33 -07:00
Corey
b3a3d97596 fix: use dl.k8s.io, not kubernetes-release bucket
Signed-off-by: Corey <brant742@gmail.com>
2023-05-11 20:55:13 +00:00
Kubernetes Prow Robot
27eb607509
Merge pull request #565 from kubernetes-sigs/dependabot/go_modules/golang.org/x/net-0.7.0
build(deps): bump golang.org/x/net from 0.4.0 to 0.7.0
2023-03-16 07:15:22 -07:00
dependabot[bot]
d58cdcee93
build(deps): bump golang.org/x/net from 0.4.0 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.4.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-09 19:18:02 +00:00
Kubernetes Prow Robot
aab718746b
Merge pull request #569 from kubernetes-sigs/dependabot/go_modules/golang.org/x/crypto-0.1.0
build(deps): bump golang.org/x/crypto from 0.0.0-20220214200702-86341886e292 to 0.1.0
2023-03-09 11:13:52 -08:00
dependabot[bot]
8528f29516
build(deps): bump golang.org/x/crypto
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220214200702-86341886e292 to 0.1.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/commits/v0.1.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-07 01:39:44 +00:00
Kubernetes Prow Robot
9a1ffb7b17
Merge pull request #559 from asherf/docs-yaml
Fix yaml in sample config & docs
2023-01-26 02:54:25 -08:00
Asher Foa
6a7f2b5ce1 Fix yaml in sample config & docs 2023-01-25 12:31:52 -05:00
Kubernetes Prow Robot
f607905cf6
Merge pull request #539 from olivierlemasle/e2e
Add initial e2e tests
2023-01-19 03:30:34 -08:00
Olivier Lemasle
7bdb7f14b9 manifests: use node_ metrics 2023-01-19 12:20:42 +01:00
Olivier Lemasle
1145dbfe93 Add initial e2e tests 2023-01-19 12:20:42 +01:00
Kubernetes Prow Robot
307795482f
Merge pull request #553 from gburton1/patch-golang-1.18.9
Patch upgrade of Golang to 1.18.9
2023-01-02 03:09:31 -08:00
Greg Burton
d341e8f67b Patch upgrade of Golang to 1.18.9
Signed-off-by: Greg Burton <9094087+gburton1@users.noreply.github.com>
2022-12-27 16:15:18 -08:00
Kubernetes Prow Robot
b03cc3e7c8
Merge pull request #551 from olivierlemasle/update-golang.org-x-net
Bump golang.org/x/net to v0.4.0 for GO-2022-1144
2022-12-16 08:16:18 -08:00
Kubernetes Prow Robot
b233597358
Merge pull request #518 from sillyfrog/master
Fix broken links in README.md
2022-12-13 11:19:34 -08:00
Olivier Lemasle
5d24df6353 Bump golang.org/x/net to v0.4.0 for GO-2022-1144 2022-12-12 12:11:21 +01:00
Sillyfrog
411763b355 Fix broken links in README.md 2022-12-11 07:09:03 +10:00
Kubernetes Prow Robot
062c42eccc
Merge pull request #550 from olivierlemasle/stylecheck
golangci-lint: Add stylecheck linter
2022-12-09 06:58:19 -08:00
Kubernetes Prow Robot
e18cc18201
Merge pull request #546 from olivierlemasle/log-flags
Refactor adding logging flags
2022-12-09 06:58:12 -08:00
Kubernetes Prow Robot
70604d2f54
Merge pull request #547 from olivierlemasle/GO-2022-0969
Fix GO-2022-0969
2022-12-09 06:52:13 -08:00
Olivier Lemasle
03cd31007e Add stylecheck linter 2022-12-08 23:12:26 +01:00
Olivier Lemasle
3d590269aa Update golang.org/x/net - Fix GO-2022-0969 2022-12-06 21:52:17 +01:00
Olivier Lemasle
09cc27e609 Refactor adding logging flags 2022-12-05 10:10:51 +01:00
Kubernetes Prow Robot
a5faf9f920
Merge pull request #544 from olivierlemasle/minversion
Set MinVersion: tls.VersionTLS12 in prometheus client's TLSClientConfig
2022-11-29 09:11:24 -08:00
Olivier Lemasle
dc0c0058d0 Set MinVersion: tls.VersionTLS12 in prometheus client's TLSClientConfig
Having no explicit MinVersion is reported by [gosec] as G402 (CWE-295):
`TLS MinVersion too low`

Using MinVersion: tls.VersionTLS12 because it's what client-go uses:
cf 1ac8d45935/transport/transport.go (L92)

That way, the Kubernetes API client and the Prometheus client in
prometheus-adapter use the same TLS config MinVersion.

[gosec]: https://github.com/securego/gosec
2022-11-29 17:24:50 +01:00
Kubernetes Prow Robot
8958457968
Merge pull request #540 from olivierlemasle/verify
Use golangci-lint
2022-11-29 08:15:24 -08:00
Olivier Lemasle
0ea1c1b8d3 Use Golangci-lint 2022-11-28 23:17:16 +01:00
Kubernetes Prow Robot
85e2d2052d
Merge pull request #542 from dgrisonnet/add-olivierlemasle
Add olivierlemasle as reviewer
2022-11-28 06:22:08 -08:00
Damien Grisonnet
d5c45b27b0 Add olivierlemasle as reviewer
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-11-28 15:10:40 +01:00
Kubernetes Prow Robot
fdfecc8d7f
Merge pull request #538 from olivierlemasle/fix-token-file
Fix segfault when using --prometheus-token-file
2022-11-22 04:12:13 -08:00
Olivier Lemasle
e740fee947 Fix segfault when using --prometheus-token-file 2022-11-09 18:06:38 +01:00
Kubernetes Prow Robot
268b2a8ec2
Merge pull request #531 from JoaoBraveCoding/426
Updates deploy/manifest to latest version in sync with kube-prom
2022-11-08 22:44:13 -08:00
Joao Marcal
372dfc9d3a
Updates README, docs/walkthrough and deploy/
Signed-off-by: JoaoBraveCoding <jmarcal@redhat.com>
2022-11-08 15:36:35 +00:00
Joao Marcal
3afe2c74bc
Updates deploy/manifest to latest version in sync with kube-prom
Issue https://github.com/kubernetes-sigs/prometheus-adapter/issues/426
2022-09-02 17:16:33 +01:00
Damien Grisonnet
dd75b55557
Merge pull request #529 from dgrisonnet/update-registry-location
Update registry location to registry.k8s.io
2022-08-31 14:54:01 +02:00
Kubernetes Prow Robot
4767a63a67
Merge pull request #526 from dgrisonnet/update-owners
Update OWNERS
2022-08-31 05:45:01 -07:00
Damien Grisonnet
204d5996a4 *: update registry location to registry.k8s.io
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-31 14:32:12 +02:00
Damien Grisonnet
d4d0a69514
Merge pull request #528 from dgrisonnet/fix-image
Fix image location in manifests
2022-08-31 14:28:38 +02:00
Damien Grisonnet
e5ad3d8903 deploy: fix image location
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-31 14:16:23 +02:00
Damien Grisonnet
465e4153f9 *: update OWNERS
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-23 17:29:11 +02:00
Damien Grisonnet
f23e67113a
Merge pull request #524 from dgrisonnet/recover-klog-flags
cmd/adapter: recover klog flags
2022-08-12 18:53:30 +02:00
Damien Grisonnet
303ac6fd45 cmd/adapter: recover klog flags
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-12 18:50:41 +02:00
Damien Grisonnet
7b4ba08b5d
Merge pull request #523 from dgrisonnet/cut-release-0.10
Cut release 0.10.0
2022-08-12 17:52:11 +02:00
Damien Grisonnet
56b57a0b0e VERSION: update to v0.10.0
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-12 17:45:28 +02:00
Kubernetes Prow Robot
dd85956fbf
Merge pull request #509 from ksauzz/feature/query-verb
Add --prometheus-verb to support POST requests to prometheus servers
2022-08-12 05:34:43 -07:00
Kazuhiro Suzuki
65abf73917 Update help about --prometheus-verb option 2022-08-12 12:45:22 +09:00
Damien Grisonnet
47ca16ef50
Merge pull request #521 from dgrisonnet/bump-k8s-deps-1.24
Update dependencies
2022-08-11 16:30:52 +02:00
Damien Grisonnet
cca107d97c *: support new MetricsGetter interface
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-11 15:04:45 +02:00
Damien Grisonnet
9321bf0162 zz_generated.openapi.go: regenerate
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-11 15:04:45 +02:00
Damien Grisonnet
d2ae4c1569 go.mod: bump golang and k8s deps to 0.24.3
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2022-08-11 15:04:43 +02:00
Kubernetes Prow Robot
c6e518beac
Merge pull request #491 from Ruwan-Ranganath/master
Update README.md with Helm-3 Command
2022-07-07 07:11:34 -07:00
Kubernetes Prow Robot
508b82b712
Merge pull request #494 from grzesuav/patch-2
Change apiregistration.k8s.io to v1
2022-07-07 07:07:34 -07:00
Kazuhiro Suzuki
a8742cff28 Add --prometheus-verb to support POST requests to prometheus servers 2022-06-28 18:23:52 +09:00
Kubernetes Prow Robot
9008b12a01
Merge pull request #498 from lokichoggio/master
fix: close file
2022-04-27 07:38:11 -07:00
lokichoggio
df3080de31
fix: close file 2022-04-11 17:54:51 +08:00
Grzegorz Głąb
00920756a4
Change apiregistration.k8s.io to v1 2022-03-23 20:48:46 +01:00
Ruwan Ranganath
e85e426ee0
Update README.md 2022-03-10 14:38:05 +05:30
Kubernetes Prow Robot
bf33cafefc
Merge pull request #482 from peizhouyu/Validate_OWNERS_files
Validate OWNERS files
2022-01-26 01:56:26 -08:00
peizhouyu
0aaf002fbc Validate OWNERS files 2022-01-25 11:21:29 +08:00
Kubernetes Prow Robot
2cc6362964
Merge pull request #476 from dims/drop-unused-alias-in-owners-aliases
Drop unused alias in OWNERS_ALIASES
2022-01-10 08:25:13 -08:00
Davanum Srinivas
8441ee2f74
Drop unused alias in OWNERS_ALIASES
Signed-off-by: Davanum Srinivas <davanum@gmail.com>
2021-12-24 17:03:30 -05:00
Kubernetes Prow Robot
c9e69613d3
Merge pull request #472 from spiffxp/use-k8s-infra-for-gcb-image
images: use k8s-staging-test-infra/gcb-docker-gcloud
2021-12-01 00:09:17 -08:00
Aaron Crickenberger
b877e9d1bb images: use k8s-staging-test-infra/gcb-docker-gcloud 2021-11-30 13:04:55 -08:00
Kubernetes Prow Robot
bd568beea0
Merge pull request #461 from dgrisonnet/version-v0.9.1
*: merge changes from v0.9.1
2021-11-09 08:39:47 -08:00
Kubernetes Prow Robot
57a6fda6b1
Merge pull request #465 from mbutkereit/typo-sample-config
Add s to metricQuery
2021-11-09 06:23:47 -08:00
mbutkereit
6720d67d3a Add s to metricQuery 2021-10-29 08:13:50 +02:00
Damien Grisonnet
4f58885c9a *: merge changes from v0.9.1
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-10-15 18:30:08 +02:00
Kubernetes Prow Robot
3206c65b47
Merge pull request #455 from leoskyrocker/master
Fix external metrics provider not respecting metrics-max-age
2021-10-06 08:12:34 -07:00
Leo Lei
bb4722e38b Fix external metrics provider not respecting metrics-max-age 2021-09-24 18:42:24 +08:00
Kubernetes Prow Robot
dd107a714b
Merge pull request #454 from spiffxp/follow-k8sio-default-branch
docs: follow kubernetes/k8s.io branch rename:
2021-09-16 02:19:45 -07:00
Aaron Crickenberger
d76d3eaa49 docs: follow kubernetes/k8s.io branch rename: 2021-09-15 15:32:26 -07:00
Kubernetes Prow Robot
12309c9d1d
Merge pull request #438 from fpetkovski/bug-template
Add bug template
2021-09-13 03:40:07 -07:00
fpetkovski
3288fb9d41 Add collapsible blocks 2021-08-23 10:44:30 +02:00
Kubernetes Prow Robot
56df87890c
Merge pull request #447 from dgrisonnet/gcr.k8s.io
README: improve gcr.k8s.io instructions
2021-08-18 05:18:09 -07:00
Kubernetes Prow Robot
ae458c4464
Merge pull request #448 from aw1cks-forks/master
v0.9.0: Bump version file to reflect new release
2021-08-18 01:32:08 -07:00
Alex Wicks
1ef79d0a86 Bump VERSION file to reflect latest release 2021-08-17 17:36:07 +01:00
Damien Grisonnet
7040f70905 README: improve gcr.k8s.io instructions
Images hosted on gcr.k8s.io aren't browsable to the users, so linking to
the website results in a 403 HTTP error which is confusing.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-08-17 17:54:25 +02:00
Kubernetes Prow Robot
0a9c781e5c
Merge pull request #442 from leoskyrocker/master
Update documentation to include metrics-max-age
2021-08-16 02:35:49 -07:00
Leo Lei
11ee7ee7e1 Fix typo 2021-08-12 21:58:33 +08:00
Kubernetes Prow Robot
0f60f49639
Merge pull request #444 from dgrisonnet/context
Propagate metric providers context
2021-08-11 08:44:46 -07:00
Damien Grisonnet
8b85c68c9e pkg: propagate metric providers context
In custom-metrics-apiserver v1.22.0, contexts were added to the metric
providers. We can benefit from that by propagating the context given to
the provider down to the requests.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-08-11 17:04:01 +02:00
Kubernetes Prow Robot
4264c97f7b
Merge pull request #443 from dgrisonnet/bump-k8s-deps-1.22
Update golang dependencies
2021-08-11 05:28:46 -07:00
Damien Grisonnet
4eb6c313a1 go.mod: update custom-metrics-apiserver to v1.22.0
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-08-11 13:56:45 +02:00
Damien Grisonnet
cc5d3b8ed2 go.mod: update dependencies
* Bump Kubernetes dependencies to v0.22.0
* Bump ginkgo to v1.16.4
* Bump gomega to v1.15.0
* Bump cobra to v1.2.1

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-08-11 13:35:42 +02:00
Leo Lei
0a2c697e0b Update documentation to include metrics-max-age 2021-08-11 13:28:50 +08:00
Kubernetes Prow Robot
c6f774e28a
Merge pull request #440 from dgrisonnet/remove-travis-deploy
Remove unused travis deploy file
2021-08-10 02:11:18 -07:00
Damien Grisonnet
20a5b7a80d chore: remove unused travis deploy file
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-08-10 10:58:32 +02:00
fpetkovski
ac814833e1 Add bug template 2021-07-27 13:50:42 +02:00
Kubernetes Prow Robot
eef6b8fef1
Merge pull request #436 from arajkumar/openapi-for-external-and-custom-metrics
fix: add openapi spec for custom and external metrics types
2021-07-19 07:36:52 -07:00
Arunprasad Rajkumar
6cea5b88ca
fix: add openapi spec for custom and external metrics types
Signed-off-by: Arunprasad Rajkumar <arajkuma@redhat.com>
2021-07-19 19:27:43 +05:30
Kubernetes Prow Robot
97236f92ed
Merge pull request #432 from discordianfish/prometheus-request-headers
Support setting headers on requests to Prometheus
2021-07-19 05:34:51 -07:00
Johannes 'fish' Ziemke
d84340cc85 Support setting headers on requests to Prometheus 2021-07-17 14:44:35 +02:00
Damien Grisonnet
3fde77674e
Merge pull request #431 from dgrisonnet/neg-resource-metrics
Prevent prometheus-adapter from returning negative resource metrics
2021-07-16 16:41:09 +02:00
Kubernetes Prow Robot
95995bcf4b
Merge pull request #434 from fpetkovski/improve-docs
Document image registries
2021-07-15 09:08:48 -07:00
Filip Petkovski
5cf9dc3427
Apply suggestions from code review
Co-authored-by: Damien Grisonnet <damien.grisonnet@epita.fr>
2021-07-15 17:39:19 +02:00
Kubernetes Prow Robot
71ab6c4d90
Merge pull request #435 from arajkumar/fix-incorrect-type-used-for-swagger
fix: incorrect type used for openapi spec
2021-07-15 08:32:47 -07:00
Arunprasad Rajkumar
aed49ff54f
fix: incorrect type used for openapi spec
Prior to this fix, openapi spec for prometheus-adapter apiextension was based on the type "k8s.io/sample-apiserver/pkg/apiserver" which is incorrect. Due to the incorrect type, `kubectl explain podmetrics` (or nodemetrics) wasn't showing any doc for any resources from metrics.k8s.io/v1beta1.

This changeset fixes the problem by using the right type(sigs.k8s.io/metrics-server/pkg/api) for the openapi generation.

This also helped to remove the sample-apiserver dependency from
prometheus-adapter.

Signed-off-by: Arunprasad Rajkumar <arajkuma@redhat.com>
2021-07-15 19:39:59 +05:30
fpetkovski
134774884c Document image registries
Signed-off-by: fpetkovski <filip.petkovsky@gmail.com>
2021-07-15 09:18:13 +02:00
Damien Grisonnet
0b3ac78d19 pkg/resourceprovider: guard from negative metrics
When serving the resource metrics API, prometheus-adapter may return
negative values for pods/nodes memory and CPU usage. This happens
because Prometheus sees counter resets which results in Prometheus
interpolating data incorrectly to avoid the counter value going down.
To prevent that, we need to add some guards in prometheus-adapter to
replace the negative value by zero whenever it detects one.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-07-13 12:19:02 +02:00
Damien Grisonnet
93450fc29f
Merge pull request #424 from dgrisonnet/cloudbuild-timeout
Increase cloudbuild timeout to 1h
2021-07-06 17:01:57 +02:00
Damien Grisonnet
c8ee46b6b4
Merge pull request #423 from dgrisonnet/fix-push-multi-arch
Fix push-multi-arch image deployment
2021-07-06 17:01:36 +02:00
Damien Grisonnet
4a22d18a5d cloudbuild: increase timeout to 1h
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-07-06 16:52:52 +02:00
Damien Grisonnet
9fd8918914 image: fix push-multi-arch image deployment
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-07-06 16:51:17 +02:00
Damien Grisonnet
731e852494
Merge pull request #420 from dgrisonnet/fix-prow-deploy
Stop populating IMAGE env variable
2021-07-06 15:18:15 +02:00
Kubernetes Prow Robot
2dbb46f158
Merge pull request #421 from ashishranjan1457/issue-412-ashishranjan1457
Fix external rule tag in documentation
2021-07-02 08:06:13 -07:00
Ashish Ranjan
4256683587
Fix the tag for external rules 2021-07-02 16:23:28 +05:30
Ashish Ranjan
0ceb09085c
Fix external rule tag in documentation
Replaced external: by externalRules: in documentation for external rules
2021-07-02 12:17:59 +05:30
Damien Grisonnet
670b3def30 Makefile: stop populating IMAGE env variable
The IMAGE env variable is used by prow when building images, so it was
replacing what we would have expected to be the `prometheus-adapter`
image by the container image used to build the prometheus-adapter image.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-07-01 15:18:12 +02:00
Kubernetes Prow Robot
7cd63baccf
Merge pull request #418 from dgrisonnet/remove-travis
Remove travis in favor of prow.k8s.io
2021-07-01 04:29:54 -07:00
Kubernetes Prow Robot
89425b72cc
Merge pull request #419 from dgrisonnet/default-gcr
Default images to the official k8s.gcr.io and gcr.io registries
2021-07-01 04:25:54 -07:00
Kubernetes Prow Robot
5e59822274
Merge pull request #417 from dgrisonnet/release
RELEASE.md: update with gcr promotion guidelines
2021-07-01 04:15:54 -07:00
Damien Grisonnet
467f24d45c image: default to gcr.io instead of hub.docker.com
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-30 19:27:04 +02:00
Damien Grisonnet
231446751c deploy: remove travis in favor of prow.k8s.io
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-30 19:19:17 +02:00
Damien Grisonnet
a057c04b09 RELEASE.md: update with gcr promotion guidelines
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-30 18:54:15 +02:00
Kubernetes Prow Robot
ae1765153a
Merge pull request #416 from dgrisonnet/cloudbuild
Add cloudbuild.yaml
2021-06-30 06:31:03 -07:00
Kubernetes Prow Robot
e4d11e44e3
Merge pull request #415 from dgrisonnet/container-push
Improve container push rules
2021-06-30 06:27:03 -07:00
Damien Grisonnet
06e41b486c deploy: add cloudbuild.yaml
Add cloudbuild.yaml file required by the image pushing job.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-30 15:07:54 +02:00
Damien Grisonnet
046b970edb deploy: improve container push rules
Improve and cleanup container push rules to prepare for the move to the
official gcr.k8s.io registry.
As part of the improvements, I replaced the non cross platform busybox
image by gcr.io/distroless/static:latest which is platform agnostic.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-30 15:03:49 +02:00
Kubernetes Prow Robot
70418fdbf8
Merge pull request #410 from dgrisonnet/fix-pod-informer
Fix pod lister by running the pod informer
2021-06-07 07:54:40 -07:00
Damien Grisonnet
91b9b7afc2 .travis.yml: update go version to 1.16
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-07 16:17:37 +02:00
Damien Grisonnet
152cf3bbaa cmd: run pod informer
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-07 16:10:13 +02:00
Kubernetes Prow Robot
407217728b
Merge pull request #407 from dgrisonnet/localvendor
Remove localvendor directory
2021-06-03 08:23:38 -07:00
Kubernetes Prow Robot
82a71ebb6f
Merge pull request #408 from paulfantom/version-file
*: add version file
2021-06-03 07:07:38 -07:00
paulfantom
aae4ef6b51
*: add version file
Signed-off-by: paulfantom <pawel@krupa.net.pl>
2021-06-03 16:02:21 +02:00
Kubernetes Prow Robot
89b6c7e31c
Merge pull request #406 from dgrisonnet/fix-build
Makefile: consolidate docker-build
2021-06-03 06:49:38 -07:00
Kubernetes Prow Robot
a7ff3cb9c2
Merge pull request #405 from dgrisonnet/filter-pods
Filter non-running pods
2021-06-03 06:45:38 -07:00
Damien Grisonnet
ef7bb58ff2 go.mod: remove localvendor
The latest version of metrics-server, v0.5.0 doesn't seem to require
having kubelet dependencies locally anymore. Thus, we can safely remove
the localvendor directory that was used to store them.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-02 17:26:03 +02:00
Damien Grisonnet
03e8eb8ddb Makefile: consolidate docker-build
As part of this commit, I upgraded the golang image used for building to
1.16 and consolidated how the docker-build rule was working. Previously,
it was failing in master's CI because the go modules were not downloaded
in the build image. To improve that, I replaced the combination of
docker run and docker build by a multi-stage Dockerfile responsible for
building the adapter and running it.

In addition to that, I removed the `_output` directory completely as it
wasn't really meaningful to have it anymore. I also removed the
`build-local-image` rule as it was a duplicate of the `docker-build`
rule with the only different of using a scratch base image.

Also, since all the base images that we are using by default are based
on busybox, I change the UID used in the image to 65534 which correspond
to the nobody user in busybox.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-02 17:12:27 +02:00
Damien Grisonnet
cf45915a4a pkg: only account for Pods in running state
Create the pod lister based on a filtered informer factory that will
filter non-running pods so that prometheus-adapter don't expect metrics
from them.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-02 11:38:09 +02:00
Kubernetes Prow Robot
815fa20931
Merge pull request #404 from dgrisonnet/module
Move prometheus-adapter to sigs.k8s.io golang package
2021-06-02 02:30:06 -07:00
Damien Grisonnet
9dfbca09ca go.mod: move to sigs.k8s.io golang package
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-01 17:35:45 +02:00
Kubernetes Prow Robot
215cb0c292
Merge pull request #395 from dgrisonnet/panic-ms
Prevent metrics-server panics on GetContainerMetrics and GetNodeMetrics
2021-06-01 07:58:27 -07:00
Damien Grisonnet
76e61d47f6 pkg/resourceprovider: prevent metrics-server panic
The code that we are reusing from metrics-server to call
GetContainerMetrics and GetNodeMetrics requires that both functions
returns arrays of lengths of the number of pods/nodes given as
arguments. In some cases, prometheus-adapter was returning nil which
caused panics in metrics-server code.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-01 16:32:58 +02:00
Kubernetes Prow Robot
09334d3a6d
Merge pull request #399 from dgrisonnet/deps
go.mod: bump dependencies
2021-06-01 07:22:27 -07:00
Damien Grisonnet
c67e8f5956 go.mod: bump dependencies
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-06-01 15:25:56 +02:00
Kubernetes Prow Robot
dd7a263002
Merge pull request #401 from dgrisonnet/vendor
Remove vendor directory
2021-06-01 06:22:27 -07:00
Damien Grisonnet
9148122308 chore: update gitignore
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-05-26 09:45:52 +02:00
Damien Grisonnet
39b782bce9 chore: remove vendor directory
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-05-26 09:41:03 +02:00
Kubernetes Prow Robot
f3aafa7c8f
Merge pull request #400 from dgrisonnet/openapi
hack/tools: remove openapi-gen install in vendor
2021-05-26 00:17:21 -07:00
Damien Grisonnet
7bc0f0473d hack/tools: remove openapi-gen install in vendor
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-05-25 19:13:16 +02:00
Kubernetes Prow Robot
c893b1140c
Merge pull request #380 from carsonoid/issue-324-carsonoid
Allow metrics to be defined as `namespaced: false`
2021-05-12 06:12:17 -07:00
Carson Anderson
6c1d85ccf9 Split ExternalMetricsQuery and MetricsQuery funcs 2021-05-11 14:49:11 -06:00
Carson Anderson
c0ae5d6dd4 fix tests 2021-04-23 11:39:47 -06:00
Carson Anderson
fa5f8cd742 Add requested fixes 2021-04-23 11:27:38 -06:00
Carson Anderson
510c3724ce Add docs, tests, and move namespaced to metricsQuery 2021-04-08 11:07:50 -06:00
Kubernetes Prow Robot
4b40c1796d
Merge pull request #391 from andrewpollack/fix-docu-links-deploy
Updating deploy/README.md to fix links
2021-04-06 01:53:35 -07:00
andrewpkq
f4d5bc9045 Updating deploy/README.md to fix links 2021-04-03 18:55:16 -07:00
Kubernetes Prow Robot
b67ac3e747
Merge pull request #389 from dgrisonnet/signal-handler
Add signal handler
2021-03-29 08:40:46 -07:00
Damien Grisonnet
9db8d2f731 cmd/adapter: add signal handler
Add a signal handler stopping the adapter if it receives a SIGINT or
SIGTERM signal. This prevent the prometheus-adapter pod from being stuck
in "Terminating" state.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-03-29 17:24:49 +02:00
Kubernetes Prow Robot
c30c69de09
Merge pull request #387 from dgrisonnet/remove-travis-verify
.travis.yml: remove verify job
2021-03-25 10:15:36 -07:00
Kubernetes Prow Robot
7151cd83b9
Merge pull request #382 from dgrisonnet/test
Makefile: include tests from cmd directory
2021-03-25 10:15:29 -07:00
Kubernetes Prow Robot
c41a99a529
Merge pull request #386 from aackerman/aackerman/fix-metric-labels
Fix documented metrics labels to work for k8s 1.16+
2021-03-23 11:07:36 -07:00
aackerman
0e105eeeb1 Update tests to use container and pod labels instead of container_name and pod_name 2021-03-23 12:41:44 -05:00
aackerman
54cd969594 Fixup default config generation to use pod and container instead of pod_name and container_name, those labels have been removed in kubernetes 1.16 2021-03-23 10:59:40 -05:00
Damien Grisonnet
df347a1427 .travis.yml: remove verify job
The verify job has been moved to prow, so we can remove the one from
travis.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-03-23 16:52:30 +01:00
aackerman
dce6abfba9 Fix documented metrics labels to work for k8s 1.16+ 2021-03-23 09:01:56 -05:00
Kubernetes Prow Robot
2c1be65011
Merge pull request #384 from lilic/remove-myself
OWNERS: Remove myself from the OWNERS
2021-03-19 08:38:35 -07:00
Lili Cosic
9231ff996d OWNERS: Remove myself from the OWNERS 2021-03-19 16:33:24 +01:00
Kubernetes Prow Robot
976c38aee4
Merge pull request #379 from TheKangaroo/fix/walkthrough
fix walkthrough example
2021-03-10 08:01:14 -08:00
Damien Grisonnet
737c8232e1 Makefile: include tests from cmd directory
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-03-10 16:53:26 +01:00
Kubernetes Prow Robot
ef33937e43
Merge pull request #377 from dgrisonnet/update-owners
Add dgrisonnet to the OWNERS
2021-03-09 08:05:00 -08:00
Carson Anderson
3ae38c7417 Allow metrics to be defined as namespaced: false
When set to false, no namespace label will be set by the adapter based on the namespace
portion of the url in the request path.

This allows individual consumers to set namespace independent of the source kubernetes resource.

---

Example:

Given an adapter config like this:

```
    externalRules:
    - seriesQuery: 'nsq_topic_depth'
      resources:
        namespaced: false
```

An HPA could target a different namespace by setting it in the selector:

```
  - type: External
    external:
      metric:
        name: nsq_topic_depth
        selector:
          labelSelector:
            topic: my-topic
            namespace: nsq
```

This is useful for scaling on metrics from services that run in a differnt namespace than the source resource.
2021-03-05 15:25:37 -07:00
Till Adam
3bd8b54ad5
fix walkthrough example
The service created by this command did not match the port name used
later on in the walkthrough. Therefor it is defined explicitly here.
2021-03-05 13:06:40 +01:00
Damien Grisonnet
dcf0ece4ea Add dgrisonnet to the OWNERS
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-03-02 12:02:26 +01:00
Kubernetes Prow Robot
7e11fe30ee
Merge pull request #372 from paulfantom/json
pkg/config: allow configuration to be read from json schema
2021-03-01 05:22:46 -08:00
Kubernetes Prow Robot
019a27f200
Merge pull request #354 from iyalang/feature/add-cert-auth
add TLS auth for accessing Prometheus
2021-03-01 05:18:42 -08:00
Kubernetes Prow Robot
e087f72404
Merge pull request #319 from kehao95/update-walkthrough
Update Prometheus Operator Doc location
2021-03-01 01:18:39 -08:00
Kubernetes Prow Robot
30f6e2fd07
Merge pull request #374 from paulfantom/imports
*: move all imports to github.com/kubernetes-sigs/prometheus-adapter
2021-02-23 01:22:03 -08:00
Iya Lang
808bd76c5a add TLS auth for accessing Prometheus 2021-02-22 21:39:58 +01:00
paulfantom
cd55a67b89
*: move all imports to github.com/kubernetes-sigs/prometheus-adapter
Signed-off-by: paulfantom <pawel@krupa.net.pl>
2021-02-22 15:49:03 +01:00
paulfantom
1cc7bed020
pkg/config: allow configuration to be read from json schema 2021-02-22 10:17:27 +01:00
Nikhita Raghunath
dd841a6e5e Fix SECURIY_CONTACTS filename 2021-01-30 11:01:32 +05:30
Nikhita Raghunath
079f67825f Fix OWNERS, OWNERS_ALIASES file names 2021-01-30 10:54:21 +05:30
Sergiusz Urbaniak
12d1fb4a72
Merge pull request #362 from dgrisonnet/fix-auth-webhook-panic
Fix authorizer webhook panic
2021-01-22 10:28:16 +01:00
Damien Grisonnet
78eec11706 vendor: revendor
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-01-21 17:40:42 +01:00
Damien Grisonnet
61a30408f6 go.mod: bump apiserver to fix auth webhook panics
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-01-21 17:39:56 +01:00
Damien Grisonnet
b0423f39ac go.mod: bump kubernetes dependencies to v0.20.2
Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-01-21 17:22:19 +01:00
Sergiusz Urbaniak
f604e07020
Merge pull request #359 from dgrisonnet/add-notice
Add NOTICE to comply with the CNCF rules
2021-01-21 16:53:18 +01:00
Damien Grisonnet
48e9f418fa Add NOTICE to comply with the CNCF rules
If (a) contributor(s) have not signed the CLA and could not be reached,
a NOTICE file should be added referencing section 7 of the CLA with a
list of the developers who could not be reached.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2021-01-18 10:09:16 +01:00
Sergiusz Urbaniak
f33fc94229
Merge pull request #348 from dgrisonnet/populate-selector
Populate metric selector for custom metrics
2020-12-15 15:54:52 +01:00
Sergiusz Urbaniak
aa77eca551
Merge pull request #352 from s-urbaniak/bump-1.20
bump to k8s 1.20, go 1.15
2020-12-14 13:26:04 +01:00
Sergiusz Urbaniak
96cdc4d143 Makefile: bump to Go 1.15 2020-12-14 12:54:22 +01:00
Sergiusz Urbaniak
147b5c8858
pkg/api/generated: regenerate 2020-12-14 12:43:59 +01:00
Sergiusz Urbaniak
9f0440be0f
vendor: revendor 2020-12-14 12:43:28 +01:00
Sergiusz Urbaniak
269295a414
go.mod: bump to k8s 1.20 2020-12-14 12:43:07 +01:00
Damien Grisonnet
76020f6618 pkg/custom-provider: populate metric selector
When querying custom-metrics, the metric label selectors weren't
populated in the resulting values.

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2020-12-10 16:14:22 +01:00
Sergiusz Urbaniak
be35274475
Merge pull request #344 from dgrisonnet/bump-metrics-server
Make NodeMetrics and PodMetrics APIs match K8s conventions
2020-12-03 14:37:19 +01:00
Damien Grisonnet
6d8a82f423 go.mod: bump metrics-server
Include a fix to the NodeMetrics and PodMetrics APIs to match k8s
conventions.
- NodeMetrics and PodMetrics now have labels as they can be filtered by
them
- Field selector is now applied to NodeMetrics and PodMetrics instead
of Nodes and Pods

Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
2020-12-02 18:28:56 +01:00
Sergiusz Urbaniak
e0ddb886a7
Merge pull request #334 from Ladicle/update-installation
Update helm charts repository in README
2020-11-11 09:24:52 +01:00
Sergiusz Urbaniak
e16510e3e6
Merge pull request #335 from johanneswuerbach/fix-openapi
Fix openapi
2020-11-10 17:54:26 +01:00
Johannes Würbach
4d0d0f3a38
update custom-metrics-apiserver 2020-11-10 16:07:17 +01:00
Johannes Würbach
b09c680295
ensure openapi is up-to-date and use same kube-openapi version 2020-11-10 16:07:17 +01:00
Johannes Würbach
95df8b43dd
vendor kube-openapi 2020-11-09 15:16:51 +01:00
Aya Igarashi
b88f59a02f Update helm charts repository in README 2020-11-06 11:49:55 +09:00
Sergiusz Urbaniak
87c429b5c6
Merge pull request #330 from joelsmith/master
Populate both CPU and Memory resource container metrics if one is specified
2020-10-30 07:30:26 +01:00
Sergiusz Urbaniak
82450eb6ec
Merge pull request #328 from s-urbaniak/bump-klog
bump to klog/v2
2020-10-28 17:32:39 +01:00
Sergiusz Urbaniak
69569bf7ab pkg/client: refactor to klog/v2 api 2020-10-28 15:57:23 +01:00
Sergiusz Urbaniak
523aa52367 vendor: revendor metrics-server, custom-metrics-apiserver 2020-10-28 15:52:52 +01:00
Sergiusz Urbaniak
752ce84723 cmd/*,pkg/*,docs,go.mod: bump custom-metrics-apiserver, metrics-server 2020-10-28 15:52:12 +01:00
Sergiusz Urbaniak
6b412c4a36 vendor: revendor k8s.io/klog 2020-10-28 15:48:39 +01:00
Sergiusz Urbaniak
a858d53495 pkg/*,cmd/*: move to k8s.io/klog/v2 2020-10-28 15:48:05 +01:00
Joel Smith
bdc8b487ba Populate both CPU and Memory resource container metrics if one is specified 2020-10-27 19:39:13 -06:00
Sergiusz Urbaniak
b9e5a71d72
Merge pull request #317 from johanneswuerbach/openapi
Serve openapi spec
2020-10-26 09:54:37 +01:00
Johannes Würbach
7a1bdecc98
Re-vendor 2020-10-23 11:06:42 +02:00
Johannes Würbach
1d44cbbbb8
Serve openapi spec 2020-10-23 11:06:41 +02:00
Johannes Würbach
47a5ed8047
Adjustments after metrics-server update 2020-10-23 11:06:40 +02:00
Johannes Würbach
b480e45a67
Update custom-metrics-apiserver and metrics-server 2020-10-23 10:57:16 +02:00
Hao Ke
43043ced4a
Update Prometheus Operator Doc location 2020-09-28 14:29:15 -04:00
Sergiusz Urbaniak
4c673534f2
Merge pull request #314 from s-urbaniak/k8s-templates
*: add Kubernetes templates
2020-09-18 08:22:12 +02:00
Sergiusz Urbaniak
7d16598ef4
add Kubernetes templates 2020-09-18 08:18:27 +02:00
Sergiusz Urbaniak
2678f90c5e
Merge pull request #230 from weibeld/master
Set --metrics-max-age default value equal to relist interval
2020-09-04 10:02:56 +02:00
Sergiusz Urbaniak
9c7743822c
Merge pull request #290 from nirrozenbaum/master
fix forbidden issue #274 by adding appropriate verbs in cluster role
2020-09-04 09:57:30 +02:00
Sergiusz Urbaniak
c45a40bec0
Merge pull request #308 from Ladicle/handle-nan
Fix NaN not to be cast to int64
2020-09-04 09:27:59 +02:00
Sergiusz Urbaniak
f5a0de3b44
Merge pull request #302 from cbroglie/external-metrics
Demonstrate how to use external metrics
2020-09-04 09:22:27 +02:00
Aya Igarashi
5a6322b4ce Fix NaN not to be cast to int64 2020-08-11 20:49:27 +09:00
Christopher Broglie
936bda2ef0 Demonstrate how to use external metrics 2020-07-17 01:47:05 -07:00
Nir Rozenbaum
26e4d23789 fix forbidden issue by adding appropriate verbs in cluster role
https://github.com/DirectXMan12/k8s-prometheus-adapter/issues/274#issuecomment-602282471
2020-05-14 08:25:01 -04:00
Sergiusz Urbaniak
4b5788e847
Merge pull request #286 from johanneswuerbach/macos-build
Use build args to fix building on macos
2020-04-20 09:54:54 +02:00
Johannes Würbach
d6b587d52b
Use build args to fix building on macos 2020-04-15 13:51:44 +02:00
Sergiusz Urbaniak
6e05ab938e
Merge pull request #284 from smarterclayton/hack_vendor_upstream
*: Update to sigs.k8s.io/metrics-server latest
2020-04-15 09:56:31 +02:00
Clayton Coleman
4ca64b85f0
vendor: Update vendor logic 2020-04-08 14:34:43 -04:00
Clayton Coleman
c6ac5cbc87
*: Update to sigs.k8s.io/metrics-server latest
Pick up changes to 1.17 to custom-metrics-apiserver and the latest
changes in metrics-server to allow us to show table results for
podmetrics and nodemetrics. Fix import and interface changes as
necessary.

The localvendor directory is an artifact of a change in sigs.k8s.io:

sigs.k8s.io/metrics-server now requires this dependency in order to
resolve, even though we do not use the scraper package.

go: sigs.k8s.io/metrics-server@v0.3.7 requires
    k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1@v0.0.0: reading k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1/pkg/kubelet/apis/stats/v1alpha1/go.mod at revision pkg/kubelet/apis/stats/v1alpha1/v0.0.0: unknown revision pkg/kubelet/apis/stats/v1alpha1/v0.0.0
2020-04-08 11:47:25 -04:00
Sergiusz Urbaniak
be9797dc49
Merge pull request #269 from s-urbaniak/go113
Makefile, go.mod: bump to 1.13
2020-02-13 12:12:19 +01:00
Sergiusz Urbaniak
cd12d4a020 Makefile, go.mod: bump to 1.13 2020-02-13 12:08:03 +01:00
Sergiusz Urbaniak
7225e3d6a7
Merge pull request #247 from sergii-koshel-exa/master
Ability to filter metrics according to labels provided
2020-02-13 10:52:29 +01:00
Sergii Koshel
128f9a29f5 vendored changes 2020-02-12 17:56:04 +02:00
Sergii Koshel
d091fff18b Update metrics apiserver to support filtering by labels 2020-02-12 17:54:40 +02:00
Sergiusz Urbaniak
5afd30edcf
Merge pull request #258 from rjackson90/patch-1
Small typo in config example snippet
2019-11-15 11:42:59 +01:00
Richard Jackson
ed4b59b359
Small typo in config example snippet 2019-11-13 15:29:13 -06:00
Daniel Weibel
4788770bf6 Fix formatting 2019-08-14 19:40:37 +02:00
Daniel Weibel
8d8bfc7c33 Set default value of --metrics-max-age equal to --metrics-relist-interval 2019-08-14 18:47:54 +02:00
Sergiusz Urbaniak
03bc47e9fb
Merge pull request #214 from thisisamurray/patch-1
Yaml typo for seriesFilters example
2019-07-22 16:57:27 +02:00
Sergiusz Urbaniak
28a807aa9f
Merge pull request #220 from dbenoit17/dx-master
remove hard-coded amd64 arch
2019-07-19 13:41:58 +02:00
dbenoit
dce4c3f75f remove hard-coded amd64 arch 2019-07-16 10:08:31 -04:00
Sergiusz Urbaniak
7e520a889e
Merge pull request #161 from jsenon/patch-1
Update example
2019-07-15 10:50:33 +02:00
Andrew Murray
571fb936bc
Yaml typo for seriesFilters example
Missing `-` in order to make seriesFilters an array.
2019-06-23 10:23:37 -04:00
Sergiusz Urbaniak
3e6d59813b
Merge pull request #213 from paulfantom/fixes
*: use proper golang image and always set go111module in build pipeline
2019-06-20 15:00:30 +02:00
paulfantom
3365677208
*: use proper golang image and always set go111module in build pipeline 2019-06-20 14:47:40 +02:00
Sergiusz Urbaniak
f2fc8dea85
Merge pull request #212 from paulfantom/go1.12
*: move to go1.12 and go modules
2019-06-20 14:12:25 +02:00
paulfantom
552284f174
*: revendor dependencies after moving to go1.12 2019-06-20 14:01:34 +02:00
paulfantom
a47edfe5a9
*: move to go1.12 and go modules 2019-06-20 13:50:10 +02:00
Sergiusz Urbaniak
8c4001831d
Merge pull request #207 from montenegrodr/patch-1
typo
2019-06-03 09:36:46 +02:00
Robson D. Montenegro
57f60449f3
typo 2019-06-02 22:22:19 +01:00
Sergiusz Urbaniak
dc65c75140
Merge pull request #204 from kublr/master
Fixed config generation command in README
2019-05-23 08:18:26 +02:00
Sergey Kozlov
e689c0bcfb Fixed config generation command in README 2019-05-22 17:59:24 +07:00
Sergiusz Urbaniak
9df21aa545
Merge pull request #197 from s-urbaniak/external-namespace
pkg/naming/BuildExternal: respect namespace
2019-05-17 15:44:57 +02:00
paulfantom
e84becd7ac
pkg/names: test namespaced external metrics selectors 2019-05-17 15:35:04 +02:00
Sergiusz Urbaniak
486324753e
pkg/naming/BuildExternal: respect namespace
Currently, the namespace is ignored for external labels.

This fixes it.
2019-05-17 15:02:57 +02:00
Sergiusz Urbaniak
4b695d4e06
Merge pull request #196 from s-urbaniak/external-metric-name
pkg/external-provider: set metric name for scalar values
2019-05-17 14:40:42 +02:00
paulfantom
2374cef641
pkg/naming: fix unit test 2019-05-17 14:36:02 +02:00
Sergiusz Urbaniak
99d52a4ce8
pkg/external-provider: set metric name for scalar values 2019-05-17 14:31:41 +02:00
Sergiusz Urbaniak
83036844f9
Merge pull request #192 from s-urbaniak/issue-191
pkg/naming: fix LabelValuesByName rendering
2019-05-17 11:59:02 +02:00
paulfantom
0d5c3ebd04
pkg/naming: add unit tests 2019-05-17 11:47:29 +02:00
Sergiusz Urbaniak
f0ec22a3f5
Merge pull request #193 from YaoZengzeng/faq
faq for namespace prefixed metrics access
2019-05-14 13:33:18 +02:00
YaoZengzeng
43c00d9c1c faq for namespace prefixed metrics access
Signed-off-by: YaoZengzeng <yaozengzeng@zju.edu.cn>
2019-05-10 15:54:58 +08:00
Sergiusz Urbaniak
9e072b2b57
pkg/naming: fix LabelValuesByName rendering
According to documentation LabelValuesByName is supposed to render "|" separated values.
Currently it is returned as a slice per label name. This fixes it

Fixes #191
2019-05-07 14:04:29 +02:00
Sergiusz Urbaniak
ff0a50100d
Merge pull request #190 from s-urbaniak/bump-1.14
bump to k8s 1.14
2019-04-24 12:16:31 +02:00
Sergiusz Urbaniak
72abf135d6 vendor dependencies 2019-04-24 11:06:03 +02:00
Sergiusz Urbaniak
604208ef4f pkg/custom-provider: refactor MetricValue signature 2019-04-24 11:04:42 +02:00
Sergiusz Urbaniak
f18b6fd370 *: replace glog with klog 2019-04-24 11:04:42 +02:00
Sergiusz Urbaniak
f69586b71c cmd/adapter: fix logs package
... as it moved.

https://github.com/kubernetes/kubernetes/pull/72890
2019-04-24 11:04:42 +02:00
Sergiusz Urbaniak
fe25941b91 gopkg: bump to k8s 1.14 2019-04-24 11:04:42 +02:00
Sergiusz Urbaniak
27a617bb9e
Merge pull request #189 from Multiply/remove-unnecessary-log-line
Remove unnecessary log line - Fix #188
2019-04-24 10:13:21 +02:00
Jens Ulrich Hjuler Pedersen
113f90ec5e
Remove unnecessary log line - Fix #188 2019-04-24 10:03:35 +02:00
Sergiusz Urbaniak
680e404250
Merge pull request #146 from john-delivuk/em-isolation
External Metrics contd
2019-04-03 16:02:57 +02:00
John Delivuk
fa27078586
Merge branch 'master' of github.com:DirectXMan12/k8s-prometheus-adapter into em-isolation 2019-03-28 21:10:29 -04:00
Sergiusz Urbaniak
d447eb1ec2
pkg/naming: add non-namespace resources
This is a merge commit back-porting PR #179
2019-03-28 21:04:42 -04:00
Sergiusz Urbaniak
5de0247b4a
Merge pull request #179 from xjas/namespaced-fix
Fix namespaced bug of node resource for issue #178
2019-03-28 13:05:25 +01:00
John Delivuk
e103a8eed2
Adding updated test 2019-03-26 19:05:32 -04:00
John Delivuk
ff409a0994
Refactoring external to leverage naming 2019-03-26 18:57:02 -04:00
song.chen
918954bd5e Amend for PR:[#179], relevant issue:[#178]
Fix namespaced bug of node resource && pv resource
By doing this fix, the CI can pass
2019-03-22 10:13:33 +08:00
song.chen
27c3bc1b88 Fix issue:[#178](https://github.com/DirectXMan12/k8s-prometheus-adapter/issues/178)
Fix namespaced bug of node resource, node resource should be a
non-namespaced resource as the resource namespace.

By doing this fix, node resouce can be accessed as expected, as the issue explains.

Closes issue:[#178](https://github.com/DirectXMan12/k8s-prometheus-adapter/issues/178)
2019-03-21 20:30:17 +08:00
John Delivuk
3f7f249cb8
Removing RestMapper, cleaning up notification hanlder 2019-03-08 13:29:36 -05:00
John Delivuk
0bb762b367
Adding license to externalprovider and naming. 2019-02-22 10:29:24 -05:00
John Delivuk
980bc01d67
Updating Errors, and adding license 2019-02-22 10:04:10 -05:00
John Delivuk
8ef8c8a291
Fixing dependency in custom-provider 2019-02-11 12:12:57 -05:00
John Delivuk
ed9eb31b3a
CustomProvider needed to have references updated as well 2019-02-10 19:12:29 -05:00
John Delivuk
b379be6818
Vendoring testify/require 2019-02-10 19:04:43 -05:00
John Delivuk
6030912cc0
Moving metric naming to it's own package 2019-02-10 15:59:14 -05:00
Solly Ross
c2e176bb23
Merge pull request #158 from linux-on-ibm-z/cross-compile
Edited Makefile to add cross build support for s390x.

Adding External Metrics Provider
2019-02-10 15:59:11 -05:00
Julien Senon
7480349096
Update example
Update sample deployment yaml
2019-02-08 14:00:08 +01:00
Solly Ross
ab6ada9081
Merge pull request #158 from linux-on-ibm-z/cross-compile
Edited Makefile to add cross build support for s390x.
2019-01-31 13:23:24 -08:00
root
a02ca0fbad Edited Makefile to add cross build support for s390x. As golang:1.10 has multi-arch support added for s390x. 2019-01-08 16:02:17 +05:30
Frederic Branczyk
dce283def1
Merge pull request #148 from brancz/empty-err
Fix empty pod or node list resulting in an error
2018-12-13 16:03:49 -08:00
Frederic Branczyk
7624952870
resourceprovider: Add comments on possible nil results 2018-12-13 15:50:02 -08:00
Frederic Branczyk
e9ef0bb4d0
Fix empty pod or node list resulting in an error 2018-12-11 10:21:21 -08:00
Frederic Branczyk
1e5a868378
Merge pull request #143 from s-urbaniak/token
cmd/adapter: add prometheus bearer token configuration
2018-12-05 17:13:47 +01:00
Sergiusz Urbaniak
326bf3c276 cmd/adapter: add prometheus bearer token configuration
Often prometheus is gated by some proxy requiring an auth bearer
token. Currently there is no possibility to configure one except for
providing a full-fledged kubeconfig.

This fixes it by adding a new flag pointing to an optional file
containing the auth bearer for prometheus communication.
2018-12-05 11:45:48 +01:00
Solly Ross
99104cba2a
Merge pull request #136 from nilebox/start-flag
Add a separate flag for 'start' parameter
2018-12-04 12:23:26 -08:00
Solly Ross
b3dfbe1b29
Merge pull request #128 from tmatias/typos
fix typos
2018-12-04 11:31:59 -08:00
Frederic Branczyk
d3bbe8247a
Merge pull request #141 from metalmatze/prom-ca-file
Support custom ca certificate to talk to Prometheus
2018-12-03 20:50:35 +01:00
Matthias Loibl
9fb46c3c55
Create a NewCertPool for custom CA certificate 2018-12-03 19:47:37 +01:00
Matthias Loibl
4a16ae6d9a
Log info when successfully loading certificates 2018-12-03 17:57:19 +01:00
Matthias Loibl
bef034e699
Improve error handling in adapter.go 2018-12-03 17:18:53 +01:00
Nail Islamov
88c0ad0b6a
Add validation 2018-11-28 12:20:09 +11:00
Nail Islamov
3f1b120eda
Add a flag metrics-start-duration 2018-11-28 12:13:07 +11:00
Matthias Loibl
5d837a29dd
Add --prometheus-ca-file flag for specific ca 2018-11-23 17:25:22 +01:00
Tiago Matias
2e82759ca9 fix typos
revert from less ideal wording

more typos
2018-11-20 10:09:02 -02:00
Solly Ross
3bd75f5c3a
Merge pull request #127 from tmatias/typo
fix typo
2018-11-12 14:42:15 -08:00
Tiago Matias
405a55521f fix typo 2018-11-12 15:50:11 -02:00
Frederic Branczyk
5a461c3fcd
Merge pull request #124 from metalmatze/readme-updates
Update README to container resource metrics API
2018-11-07 15:59:56 +01:00
Matthias Loibl
d12e5f0684
Improve sentence mentioning metrics server in README.md 2018-11-07 15:18:08 +01:00
Matthias Loibl
7360c51c0e
Update README to container resource metrics API 2018-11-06 15:57:33 +01:00
Frederic Branczyk
919c8bcbe9
Merge pull request #120 from tmatias/patch-1
fix typo
2018-10-26 16:13:16 +02:00
Tiago Matias
94b0063c52
fix typo 2018-10-19 16:31:07 -03:00
Solly Ross
7a421bb91e
Merge pull request #106 from DirectXMan12/feature/resource-metrics
Resource Metrics API Support
2018-10-04 07:34:08 -07:00
Solly Ross
083300bf32
Merge pull request #115 from richardbrks/fix-docs
Fix docs
2018-10-03 08:38:13 -07:00
Solly Ross
5f52b29d47 Disable the custom metrics API with no rules
This disables the custom metrics API when no custom metrics rules are
given (the resource metrics API acts equivalently).  This allows a given
adapter to serve only one of the APIs, if desired.
2018-10-03 11:05:43 -04:00
Solly Ross
6c8f44623e Add tests for the resource metrics provider
This adds basic tests for the resource metrics provider, and fixes up
the default config slightly.
2018-10-03 11:05:43 -04:00
Solly Ross
49287fecc9 Extract fake Prometheus client impl
This extracts the fake prometheus client for use across different tests.
2018-10-03 11:05:43 -04:00
Solly Ross
cc08a1fb41 Switch to ginkgo/gomega for tests
This switches over to ginkgo/gomega for tests, which makes writing
certain tests easier/more fluent in the future.
2018-10-03 11:05:43 -04:00
Solly Ross
c5801455ec Introduce support for the resource metrics API
This introduces support for the resource metrics in the adapter.
The individual queries and window sizes are fully customizable via a new
config section.  This uses just the generic machinery from
metrics-server to serve the API.
2018-10-03 11:05:43 -04:00
Solly Ross
74c0c53e4f Refactor metrics query building to interface
This moves the metrics query building to a separate interface in the
naming package so that it can be used across providers.
2018-10-03 11:05:43 -04:00
Solly Ross
7dd9e94aea Extract label-resource logic to separate package
This extracts the label-to-resource conversion to a separte "naming" package for
use across different providers.

Early versions of the commit were done by antoinne85 in #79.
2018-10-02 16:57:55 -04:00
Solly Ross
d02384477a Upgrade boilerplate to latest
The latest boilerplate comes with a lot of simplifications and helpers
that let us reduce the amount of code written.
2018-10-02 16:57:55 -04:00
richardbrks
94379a1780 delete repeated phrase 2018-09-28 12:53:39 -04:00
richardbrks
8d12f4f5fd correct capitalization 2018-09-28 12:40:07 -04:00
richardbrks
b381aef37b correct file path 2018-09-28 07:54:52 -04:00
richardbrks
93974115ad remove repeated word 2018-09-28 07:52:48 -04:00
Solly Ross
6b2c04dd61
Merge pull request #110 from DirectXMan12/deps/1.11-EKS-fix
Update deps to Kube 1.11.3
2018-09-27 15:53:58 -04:00
Solly Ross
c916572aca Update deps to Kube 1.11.3
This updates the dependencies to Kube 1.11.3 to pull in a fix allowing
requestheader auth to be used without normal client auth (which makes
things work on clusters that don't enable client auth normally, like
EKS).
2018-09-24 14:47:22 -04:00
Solly Ross
7f1b40728b
Merge pull request #98 from tomkerkhove/helm-install-docs
Add docs on helm installation
2018-09-17 05:21:14 -04:00
Tom Kerkhove
87fad2ab0a
Merge branch 'master' into helm-install-docs 2018-09-15 09:47:25 +02:00
Solly Ross
262493780f Clarify multi-metric queries
The example on the multi-metric query was a bit misleading, so this
clarifies it a bit so it looks closer to a real-world example.
2018-08-28 13:45:55 -04:00
Solly Ross
b755cf7f93
Merge pull request #95 from DirectXMan12/docs/common-issues
[docs] Config Walkthroughs and FAQs
2018-08-24 11:21:11 -04:00
Solly Ross
a1f4aab6d4 Restructure walkthrough
This restructures the walkthrough to focus on the goal of scaling an
application from the start.  We start out with an application and an
autoscaler, and then walk through how we can make the autoscaler
actually able to function.
2018-08-24 11:16:49 -04:00
Solly Ross
5118c9ee1e Add some FAQs to the README
This adds some FAQs to the README containing information about certs,
labelling, configuration, quantities, and multiple metrics.
2018-08-24 11:16:49 -04:00
Solly Ross
77614d151a Add a note on resource quanities to walkthrough
Quantities continue to confuse people, so adding concrete examples
should help.
2018-08-24 11:16:49 -04:00
Solly Ross
9f08038f07 Add a config walkthrough and update the readme
A helful community member rightly pointed out that configuring the
adapter was a bit confusing, and a step-by-step example would be useful.
This adds such an example, and links to it from relevant places.
2018-08-24 11:16:49 -04:00
Solly Ross
1f6df8eadf
Merge pull request #103 from DirectXMan12/deploy/extraneous-arg
Remove extraneous `/adapter` from deployment args
2018-08-20 18:59:51 -04:00
Solly Ross
f909290ab0
Merge pull request #102 from DirectXMan12/refactor/reduce-logspam
Reduce metric namer label-GVR logspam
2018-08-20 17:13:49 -04:00
Solly Ross
cfba614544 Remove extraneous /adapter from deployment args
There was an extraneous `/adapter` in the deployment args that was
causing people issues.  This removes it.
2018-08-20 17:12:27 -04:00
Solly Ross
9d7157f7cc Reduce metric namer label-GVR logspam
It's fairly common to have a label pattern that matches lots of
irrelevant labels, so this makes the "error" about being unable to
normalize/resolve a label to a GVR a V(9) info log, instead of an error
log.
2018-08-20 16:53:51 -04:00
Tom Kerkhove
e9a3e436c2
Optimize based on feedback 2018-08-15 19:55:49 +02:00
Tom Kerkhove
6c510c8f1b
Add docs on helm installation 2018-08-15 17:21:37 +02:00
Solly Ross
58857cfd37
Merge pull request #97 from tanner-bruce/strictly-unmarshal
use strict unmarshalling to ensure correct configuration
2018-08-13 08:53:16 -07:00
Tanner Bruce
1ec7809eaf use strict unmarshalling to ensure correct configuration 2018-08-09 16:30:06 -05:00
Solly Ross
c912ebed77 Fix naming typo in the config docs
The config docs had a find/replace error in the naming section, leading
to an erroneus `as` clause.  This fixes that.
2018-08-07 13:45:16 -04:00
Solly Ross
1667f1082b Don't rebuild vendor in Travis
Since we check vendor in, there's no need to rebuild the vendor
directory.
2018-08-01 11:17:13 -04:00
Solly Ross
df3d247308
Merge pull request #94 from zioproto/tls-secrets-docs
Use type kubernetes.io/tls for adapter TLS certificates
2018-07-24 14:49:05 -04:00
Saverio Proto
b5ee64bf38 Improve docs about TLS secret 2018-07-24 13:15:24 +02:00
Solly Ross
981e73562d
Merge pull request #93 from aabed/patch-1
sets the right path for config file
2018-07-23 13:56:05 -04:00
Aabed
beb6bb9663
sets the right path for config file
The configmap is mounted under /etc/adapter/config.yaml instead of /default/adapter-config.yaml
2018-07-22 10:39:34 +02:00
Solly Ross
3fc2dbdf71 Explicitly build on tags in Travis
Setting `branch: master` causes tag builds to be skipped, so we have to
explicitly also build on tags.
2018-07-13 20:07:23 -04:00
Solly Ross
a293b2bf94 Check in the vendor directory
Travis seems to be having issues pulling deps, so we'll have to check in
the vendor directory and prevent the makefile from trying to regenerate
it normally.
2018-07-13 17:32:49 -04:00
Solly Ross
98e16bc315
Merge pull request #87 from natefox/natefox-docs-update
Fixing broken links in walkthrough
2018-07-13 17:28:34 -04:00
Solly Ross
ec335c0777 Fix cross-compile typo in Makefile
The makefile target for the output directory was hardcoded to $(ARCH),
independent of the actual target input in the makefile.  This fixes
that.
2018-07-12 15:05:43 -04:00
Solly Ross
0d220eafef Clarify wording a bit on the relist interval
The wording on the relist interval confused a couple of people, so this
should clarify it a bit.
2018-07-03 16:49:55 -04:00
Solly Ross
8e91086e1e dep ensure verbosely
This makes the Makefile run `dep ensure` verbosely for easier debugging on
Travis.
2018-07-03 11:36:35 -04:00
Nate Fox
16b031e22f
Missed a link
Updating 1 more link
2018-07-02 14:20:33 -07:00
Nate Fox
75e690b430
Fixing broken links
Updating links to root so they work whilst reading on Github
2018-07-02 14:18:43 -07:00
Solly Ross
7e49e0733f
Merge pull request #84 from DirectXMan12/docs/fix-walkthrough
Fix up the walkthrough
2018-06-30 22:34:10 -04:00
Solly Ross
fb8a0c50b3 Fix up the walkthrough
This fixes up a couple of typos in the walkthrough and updates it to
mention config and the Prometheus Operator.

It probably eventually needs to be further modified with more specific
instructions about the Prometheus Operator.
2018-06-30 22:22:05 -04:00
Solly Ross
0fa468ac87
Merge pull request #82 from DirectXMan12/deploy/tmp-emptydir
Explicitly mount /tmp as an emptyDir
2018-06-30 21:33:38 -04:00
Solly Ross
414af01f58
Merge pull request #83 from DirectXMan12/docs/relist-interval-advice
[docs] Advice on relist interval
2018-06-30 21:32:36 -04:00
Solly Ross
9951568183 [docs] Advice on relist interval
This documents that the relist interval must be at least the Prometheus
scrape interval, lest metrics fade in and out.
2018-06-30 21:28:55 -04:00
Solly Ross
eb6949c2a4 Explicitly mount /tmp as an emptyDir
If using an image build with `FROM scratch` (as built by the
build-local-images make target), you need to explictly mount
/tmp as an emptyDir.
2018-06-30 21:21:44 -04:00
Solly Ross
7b606a79fc
Merge pull request #46 from DirectXMan12/feature/advanced-config
[WIP] Advanced Configuration
2018-06-27 17:04:27 -04:00
Solly Ross
be018f76e3 Update dependencies to Kubernetes 1.11
This updates our dependencies to the Kubernetes 1.11 versions.
In the future, this will also allow us to support the external
metrics API.
2018-06-27 16:57:50 -04:00
Solly Ross
1e5cd68533 Makefile with actual deps
This makes the makefile's build target have actual dependencies, so that
it only rebuilds any given adapter if that adapter's actual go files
have changed (yes, this is mostly redundant with Go 1.10, but it makes
working on read-only filesystems a bit nicer).
2018-06-27 16:56:20 -04:00
Joel Speed
6089fa8528 Use channel for series aggregation
This fixes asynchronous read/write issues to when performing series
discovery by pushing series results onto a channel, instead of trying to
write them directly to a map.
2018-06-27 16:56:20 -04:00
Solly Ross
32e4c5b1c7 Advanced Config Docs Updates
This updates the documentation and README to have information on the
configuration file format.
2018-06-27 16:56:20 -04:00
Solly Ross
40a9ee2474 Add a helper to generate legacy configuration
This moves the DefaultConfig method out into a helper to generate legacy
configuration.  Passing in a config file is now required.
2018-06-27 16:56:20 -04:00
Solly Ross
ad1837e9b5 Switch to dep for dependency management
I find it to be a bit faster in some cases, and easier to work with.
2018-06-22 16:06:12 -04:00
Solly Ross
2984604be8 Advanced Configuration
This commit introduces advanced configuration.  The rate-interval and
label-prefix flags are removed, and replaced by a configuration file
that allows you to specify series queries and the rules for transforming
those into metrics queries and API resources.
2018-06-22 15:37:12 -04:00
Solly Ross
c22681a91d
Merge pull request #67 from zioproto/patch-1
Fix creating the resource-lister clusterrole
2018-05-25 14:23:16 -04:00
Solly Ross
8c6dba34d9
Merge pull request #69 from zioproto/issues/57
docs: walkthrough, make consistent the volume name
2018-05-25 14:22:36 -04:00
Solly Ross
92930ec2d1
Merge pull request #70 from linux-on-ibm-z/k8s-prom-adapter-s390x
Update Makefile to support s390x
2018-05-25 14:21:48 -04:00
Duane D'Souza
5e307fb2b4
Update Makefile to support s390x
Updated Makefile to include changes to the docker-build step, to add support for s390x.
2018-05-15 11:40:37 +05:30
Saverio Proto
909f0a2599 docs: walkthrough, make consistent the volume name
Related-issue: #57
2018-05-14 09:20:26 +02:00
Saverio Proto
12d0e7b75f Fix creating the resource-lister clusterrole
Related-issue: #57
2018-05-08 11:06:52 +02:00
Solly Ross
83a7dd5d5e
Merge pull request #53 from huxiaoliang/enable_access_to_ssl_prometheus
Add support for auth when connecting to Prometheus
2018-03-22 12:07:46 -04:00
huxiaoliang
af1dcc0cda Add support for auth when connecting to Prometheus
Currently, the adapter uses http.DefaultClient to access Prometheus.
This means that it does support using TLS client certificates, custom
CA certificates, or token-base authentication, which are all common
setups when connecting to Prometheus behind an auth proxy.

This commit adds support for using a separate kubeconfig, or in-cluster
config, to configure auth when connecting to Prometheus.

Fixes #52

[directxman12: cleanups, spelling corrections, and slight refactor]
2018-03-22 12:07:28 -04:00
Solly Ross
3f5b54d728
Merge pull request #55 from huxiaoliang/fix_license_issue
Fix license issue
2018-03-15 11:18:55 -04:00
huxiaoliang
045baab990 Fix license issue
fix issue #54
2018-03-15 17:31:27 +08:00
Solly Ross
df48f2aa63 Merge pull request #50 from DirectXMan12/infra/deploy-from-travis
[infra] automatically publish new images
2018-02-28 14:22:10 -05:00
Solly Ross
d192346039 [infra] automatically publish new images
This tags new images every commit (via Travis) as latest, and every
tag gets pushed as a separate tag as well.
2018-02-28 14:22:09 -05:00
Solly Ross
42e839ece7
Merge pull request #47 from aschepis/patch-1
[docs] Fix serving cert path config in deployment spec
2018-02-22 14:28:33 -05:00
Solly Ross
cfe8f36c37
Merge pull request #49 from aschepis/patch-2
[docs] Add missing `create` on api registration
2018-02-22 14:26:30 -05:00
Adam Schepis
66527c6c6b
Add missing create on api registration
when creating the custom APIService the example was missing the `create` portion of the `kubectl` command.
2018-02-22 09:23:32 -05:00
Adam Schepis
6e4d4a4a93
Fix serving cert path config in deployment spec
The volume was mounted at `/var/run/serving/certs` but the container was sending an arg to look at `/var/run/serving-cert` (missing the trailing `s`)
2018-02-21 18:19:24 -05:00
Solly Ross
b9e8dd74bf
Merge pull request #44 from DirectXMan12/docs/update-walkthrough-1.10
[docs] update to v1.9 API versions
2018-02-13 13:25:46 -05:00
Solly Ross
f05ee274c8 [deploy] update types to Kube 1.9 versions
This commit updates the deploy YAML files to the Kubernetes 1.9 API
versions.
2018-02-13 13:25:13 -05:00
Solly Ross
79f9248ded [docs] update to v1.9 API versions
This updates the README and walkthrough to use v1.9 API versions,
and to use Prometheus v2.
2018-02-13 13:16:14 -05:00
Solly Ross
61b071c186
Merge pull request #41 from DirectXMan12/feature/label-prefixes
Allow setting a resource label prefix
2018-02-06 14:36:00 -05:00
Solly Ross
0fa0d36e17 Allow setting a resource label prefix
This allows setting a prefix on the labels used to determine which
resources a series belongs to.  The prefix may be set using the
`--label-prefix` flag.
2018-02-06 14:35:40 -05:00
Solly Ross
842b850fcd
Merge pull request #35 from parsifal-47/master
Add travis badge
2018-01-29 11:30:09 -05:00
Renat Idrisov
7c2a30f343 Add travis badge 2018-01-27 10:28:17 +01:00
Solly Ross
bb62d50490
Merge pull request #33 from vainu-arto/no_need_for_sudo
Use rm -rf instead of sudo when removing the temp dir
2018-01-24 10:46:31 -05:00
Arto Jantunen
0eddfe5e6d Use rm -rf instead of sudo when removing the temp dir 2018-01-23 13:35:02 +02:00
Solly Ross
c3ad5ef0ea
Merge pull request #25 from brancz/update-manifests
deploy: Update custom metrics API name and separate into files
2017-11-28 10:16:44 -08:00
Frederic Branczyk
80598a8bd3
deploy: Update custom metrics API name and separate into files 2017-11-13 09:54:35 +01:00
101 changed files with 24759 additions and 2572 deletions

52
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,52 @@
---
name: Bug report
about: Report a bug encountered while running prometheus-adapter
title: ''
labels: kind/bug
assignees: ''
---
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
If the matter is security related, please disclose it privately see https://github.com/kubernetes/kube-state-metrics/blob/master/SECURITY.md
-->
**What happened?**:
**What did you expect to happen?**:
**Please provide the prometheus-adapter config**:
<details open>
<summary>prometheus-adapter config</summary>
<!--- INSERT config HERE --->
</details>
**Please provide the HPA resource used for autoscaling**:
<details open>
<summary>HPA yaml</summary>
<!--- INSERT yaml HERE --->
</details>
**Please provide the HPA status**:
**Please provide the prometheus-adapter logs with -v=6 around the time the issue happened**:
<details open>
<summary>prometheus-adapter logs</summary>
<!--- INSERT logs HERE --->
</details>
**Anything else we need to know?**:
**Environment**:
- prometheus-adapter version:
- prometheus version:
- Kubernetes version (use `kubectl version`):
- Cloud provider or hardware configuration:
- Other info:

5
.gitignore vendored
View file

@ -1,4 +1,5 @@
*.swp *.swp
*~ *~
vendor /vendor
_output /adapter
.e2e

39
.golangci.yml Normal file
View file

@ -0,0 +1,39 @@
run:
deadline: 5m
linters:
disable-all: true
enable:
- bodyclose
- dogsled
- dupl
- errcheck
- exportloopref
- gocritic
- gocyclo
- gofmt
- goimports
- gosec
- goprintffuncname
- gosimple
- govet
- ineffassign
- misspell
- nakedret
- nolintlint
- revive
- staticcheck
- stylecheck
- typecheck
- unconvert
- unused
- whitespace
linters-settings:
goimports:
local-prefixes: sigs.k8s.io/prometheus-adapter
revive:
rules:
- name: exported
arguments:
- disableStutteringCheck

View file

@ -1,23 +0,0 @@
language: go
go:
- 1.8
# blech, Travis downloads with capitals in DirectXMan12, which confuses go
go_import_path: github.com/directxman12/k8s-prometheus-adapter
addons:
apt:
sources:
- sourceline: 'ppa:masterminds/glide'
packages:
- glide
install:
- make -B vendor
script: make verify
cache:
directories:
- ~/.glide

31
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,31 @@
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
## Getting Started
We have full documentation on how to get started contributing here:
<!---
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
-->
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
## Mentorship
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
<!---
Custom Information - if you're copying this template for the first time you can add custom content here, for example:
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/kubernetes-users) - Replace `kubernetes-users` with your slack channel string, this will send users directly to your channel.
- [Mailing list](URL)
-->

22
Dockerfile Normal file
View file

@ -0,0 +1,22 @@
ARG ARCH
ARG GO_VERSION
FROM golang:${GO_VERSION} as build
WORKDIR /go/src/sigs.k8s.io/prometheus-adapter
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY pkg pkg
COPY cmd cmd
COPY Makefile Makefile
ARG ARCH
RUN make prometheus-adapter
FROM gcr.io/distroless/static:latest-$ARCH
COPY --from=build /go/src/sigs.k8s.io/prometheus-adapter/adapter /
USER 65534
ENTRYPOINT ["/adapter"]

201
LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

165
Makefile
View file

@ -1,73 +1,118 @@
REGISTRY?=directxman12 REGISTRY?=gcr.io/k8s-staging-prometheus-adapter
IMAGE?=k8s-prometheus-adapter IMAGE=prometheus-adapter
TEMP_DIR:=$(shell mktemp -d) ARCH?=$(shell go env GOARCH)
ARCH?=amd64
ALL_ARCH=amd64 arm arm64 ppc64le s390x ALL_ARCH=amd64 arm arm64 ppc64le s390x
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64,linux/ppc64le,linux/s390x GOPATH:=$(shell go env GOPATH)
OUT_DIR?=./_output
VENDOR_DOCKERIZED=0
VERSION?=latest VERSION=$(shell cat VERSION)
TAG_PREFIX=v
TAG?=$(TAG_PREFIX)$(VERSION)
ifeq ($(ARCH),amd64) GO_VERSION?=1.22.5
BASEIMAGE?=busybox GOLANGCI_VERSION?=1.56.2
endif
ifeq ($(ARCH),arm) .PHONY: all
BASEIMAGE?=armhf/busybox all: prometheus-adapter
endif
ifeq ($(ARCH),arm64) # Build
BASEIMAGE?=aarch64/busybox # -----
endif
ifeq ($(ARCH),ppc64le) SRC_DEPS=$(shell find pkg cmd -type f -name "*.go")
BASEIMAGE?=ppc64le/busybox
endif prometheus-adapter: $(SRC_DEPS)
ifeq ($(ARCH),s390x) CGO_ENABLED=0 GOARCH=$(ARCH) go build sigs.k8s.io/prometheus-adapter/cmd/adapter
BASEIMAGE?=s390x/busybox
.PHONY: container
container:
docker build -t $(REGISTRY)/$(IMAGE)-$(ARCH):$(TAG) --build-arg ARCH=$(ARCH) --build-arg GO_VERSION=$(GO_VERSION) .
# Container push
# --------------
PUSH_ARCH_TARGETS=$(addprefix push-,$(ALL_ARCH))
.PHONY: push
push: container
docker push $(REGISTRY)/$(IMAGE)-$(ARCH):$(TAG)
push-all: $(PUSH_ARCH_TARGETS) push-multi-arch;
.PHONY: $(PUSH_ARCH_TARGETS)
$(PUSH_ARCH_TARGETS): push-%:
ARCH=$* $(MAKE) push
.PHONY: push-multi-arch
push-multi-arch: export DOCKER_CLI_EXPERIMENTAL = enabled
push-multi-arch:
docker manifest create --amend $(REGISTRY)/$(IMAGE):$(TAG) $(shell echo $(ALL_ARCH) | sed -e "s~[^ ]*~$(REGISTRY)/$(IMAGE)\-&:$(TAG)~g")
@for arch in $(ALL_ARCH); do docker manifest annotate --arch $${arch} $(REGISTRY)/$(IMAGE):$(TAG) $(REGISTRY)/$(IMAGE)-$${arch}:$(TAG); done
docker manifest push --purge $(REGISTRY)/$(IMAGE):$(TAG)
# Test
# ----
.PHONY: test
test:
CGO_ENABLED=0 go test ./cmd/... ./pkg/...
.PHONY: test-e2e
test-e2e:
./test/run-e2e-tests.sh
# Static analysis
# ---------------
.PHONY: verify
verify: verify-lint verify-deps verify-generated
.PHONY: update
update: update-lint update-generated
# Format and lint
# ---------------
HAS_GOLANGCI_VERSION:=$(shell $(GOPATH)/bin/golangci-lint version --format=short)
.PHONY: golangci
golangci:
ifneq ($(HAS_GOLANGCI_VERSION), $(GOLANGCI_VERSION))
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v$(GOLANGCI_VERSION)
endif endif
.PHONY: all build docker-build push-% push test verify-gofmt gofmt verify .PHONY: verify-lint
verify-lint: golangci
$(GOPATH)/bin/golangci-lint run --modules-download-mode=readonly || (echo 'Run "make update-lint"' && exit 1)
all: build .PHONY: update-lint
build: vendor update-lint: golangci
CGO_ENABLED=0 GOARCH=$(ARCH) go build -a -tags netgo -o $(OUT_DIR)/$(ARCH)/adapter github.com/directxman12/k8s-prometheus-adapter/cmd/adapter $(GOPATH)/bin/golangci-lint run --fix --modules-download-mode=readonly
docker-build: vendor
cp deploy/Dockerfile $(TEMP_DIR)
cd $(TEMP_DIR) && sed -i "s|BASEIMAGE|$(BASEIMAGE)|g" Dockerfile
docker run -it -v $(TEMP_DIR):/build -v $(shell pwd):/go/src/github.com/directxman12/k8s-prometheus-adapter -e GOARCH=$(ARCH) golang:1.8 /bin/bash -c "\ # Dependencies
CGO_ENABLED=0 go build -a -tags netgo -o /build/adapter github.com/directxman12/k8s-prometheus-adapter/cmd/adapter" # ------------
docker build -t $(REGISTRY)/$(IMAGE)-$(ARCH):$(VERSION) $(TEMP_DIR) .PHONY: verify-deps
sudo rm -r $(TEMP_DIR) verify-deps:
go mod verify
go mod tidy
@git diff --exit-code -- go.mod go.sum
push-%: # Generation
$(MAKE) ARCH=$* docker-build # ----------
docker push $(REGISTRY)/$(IMAGE)-$*:$(VERSION)
push: ./manifest-tool $(addprefix push-,$(ALL_ARCH)) generated_files=pkg/api/generated/openapi/zz_generated.openapi.go
./manifest-tool push from-args --platforms $(ML_PLATFORMS) --template $(REGISTRY)/$(IMAGE)-ARCH:$(VERSION) --target $(REGISTRY)/$(IMAGE):$(VERSION)
./manifest-tool: .PHONY: verify-generated
curl -sSL https://github.com/estesp/manifest-tool/releases/download/v0.5.0/manifest-tool-linux-amd64 > manifest-tool verify-generated: update-generated
chmod +x manifest-tool @git diff --exit-code -- $(generated_files)
vendor: glide.lock .PHONY: update-generated
ifeq ($(VENDOR_DOCKERIZED),1) update-generated:
docker run -it -v $(shell pwd):/go/src/github.com/directxman12/k8s-prometheus-adapter -w /go/src/github.com/directxman12/k8s-prometheus-adapter golang:1.8 /bin/bash -c "\ go install -mod=readonly k8s.io/kube-openapi/cmd/openapi-gen
curl https://glide.sh/get | sh \ $(GOPATH)/bin/openapi-gen --logtostderr \
&& glide install -v" --go-header-file ./hack/boilerplate.go.txt \
else --output-pkg ./pkg/api/generated/openapi \
glide install -v --output-file zz_generated.openapi.go \
endif --output-dir ./pkg/api/generated/openapi \
-r /dev/null \
test: vendor "k8s.io/metrics/pkg/apis/custom_metrics" "k8s.io/metrics/pkg/apis/custom_metrics/v1beta1" "k8s.io/metrics/pkg/apis/custom_metrics/v1beta2" "k8s.io/metrics/pkg/apis/external_metrics" "k8s.io/metrics/pkg/apis/external_metrics/v1beta1" "k8s.io/metrics/pkg/apis/metrics" "k8s.io/metrics/pkg/apis/metrics/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/version" "k8s.io/api/core/v1"
CGO_ENABLED=0 go test ./pkg/...
verify-gofmt:
./hack/gofmt-all.sh -v
gofmt:
./hack/gofmt-all.sh
verify: verify-gofmt test

16
NOTICE Normal file
View file

@ -0,0 +1,16 @@
When donating the k8s-prometheus-adapter project to the CNCF, we could not
reach all the contributors to make them sign the CNCF CLA. As such, according
to the CNCF rules to donate a repository, we must add a NOTICE referencing
section 7 of the CLA with a list of developers who could not be reached.
`7. Should You wish to submit work that is not Your original creation, You may
submit it to the Foundation separately from any Contribution, identifying the
complete details of its source and of any license or other restriction
(including, but not limited to, related patents, trademarks, and license
agreements) of which you are personally aware, and conspicuously marking the
work as "Submitted on behalf of a third-party: [named here]".`
Submitted on behalf of a third-party: Andrew "thisisamurray" Murray
Submitted on behalf of a third-party: Duane "duane-ibm" D'Souza
Submitted on behalf of a third-party: John "john-delivuk" Delivuk
Submitted on behalf of a third-party: Richard "rrtaylor" Taylor

17
OWNERS Normal file
View file

@ -0,0 +1,17 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- dgrisonnet
- logicalhan
- dashpole
reviewers:
- dgrisonnet
- olivierlemasle
- logicalhan
- dashpole
emeritus_approvers:
- brancz
- directxman12
- lilic
- s-urbaniak

207
README.md
View file

@ -1,11 +1,44 @@
Kubernetes Custom Metrics Adapter for Prometheus # Prometheus Adapter for Kubernetes Metrics APIs
================================================
This repository contains an implementation of the Kubernetes custom This repository contains an implementation of the Kubernetes Custom, Resource and External
metrics API [Metric APIs](https://github.com/kubernetes/metrics).
([custom-metrics.metrics.k8s.io/v1alpha1](https://github.com/kubernetes/metrics/tree/master/pkg/apis/custom_metrics)),
suitable for use with the autoscaling/v2 Horizontal Pod Autoscaler in This adapter is therefore suitable for use with the autoscaling/v2 Horizontal Pod Autoscaler in Kubernetes 1.6+.
Kubernetes 1.6+. It can also replace the [metrics server](https://github.com/kubernetes-incubator/metrics-server) on clusters that already run Prometheus and collect the appropriate metrics.
Quick Links
-----------
- [Config walkthrough](docs/config-walkthrough.md) and [config reference](docs/config.md).
- [End-to-end walkthrough](docs/walkthrough.md)
- [Deployment info and files](deploy/README.md)
Installation
-------------
If you're a helm user, a helm chart is listed on prometheus-community repository as [prometheus-community/prometheus-adapter](https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-adapter).
To install it with the release name `my-release`, run this Helm command:
For Helm2
```console
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
$ helm install --name my-release prometheus-community/prometheus-adapter
```
For Helm3 ( as name is mandatory )
```console
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
$ helm install my-release prometheus-community/prometheus-adapter
```
Official images
---
All official images for releases after v0.8.4 are available in `registry.k8s.io/prometheus-adapter/prometheus-adapter:$VERSION`. The project also maintains a [staging registry](https://console.cloud.google.com/gcr/images/k8s-staging-prometheus-adapter/GLOBAL/) where images for each commit from the master branch are published. You can use this registry if you need to test a version from a specific commit, or if you need to deploy a patch while waiting for a new release.
Images for versions v0.8.4 and prior are only available in unofficial registries:
* https://quay.io/repository/coreos/k8s-prometheus-adapter-amd64
* https://hub.docker.com/r/directxman12/k8s-prometheus-adapter/
Configuration Configuration
------------- -------------
@ -16,7 +49,7 @@ will attempt to using [Kubernetes in-cluster
config](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) config](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod)
to connect to the cluster. to connect to the cluster.
It takes the following addition arguments specific to configuring how the It takes the following additional arguments specific to configuring how the
adapter talks to Prometheus and the main Kubernetes cluster: adapter talks to Prometheus and the main Kubernetes cluster:
- `--lister-kubeconfig=<path-to-kubeconfig>`: This configures - `--lister-kubeconfig=<path-to-kubeconfig>`: This configures
@ -25,34 +58,50 @@ adapter talks to Prometheus and the main Kubernetes cluster:
in-cluster config. in-cluster config.
- `--metrics-relist-interval=<duration>`: This is the interval at which to - `--metrics-relist-interval=<duration>`: This is the interval at which to
update the cache of available metrics from Prometheus. update the cache of available metrics from Prometheus. By default, this
value is set to 10 minutes.
- `--rate-interval=<duration>`: This is the duration used when requesting - `--metrics-max-age=<duration>`: This is the max age of the metrics to be
rate metrics from Prometheus. It *must* be larger than your Prometheus loaded from Prometheus. For example, when set to `10m`, it will query
collection interval. Prometheus for metrics since 10m ago, and only those that has datapoints
within the time period will appear in the adapter. Therefore, the metrics-max-age
should be equal to or larger than your Prometheus' scrape interval,
or your metrics will occaisonally disappear from the adapter.
By default, this is set to be the same as metrics-relist-interval to avoid
some confusing behavior (See this [PR](https://github.com/kubernetes-sigs/prometheus-adapter/pull/230)).
Note: We recommend setting this only if you understand what is happening.
For example, this setting could be useful in cases where the scrape duration is
over a network call, e.g. pulling metrics from AWS CloudWatch, or Google Monitoring,
more specifically, Google Monitoring sometimes have delays on when data will show
up in their system after being sampled. This means that even if you scraped data
frequently, they might not show up soon. If you configured the relist interval to
a short period but without configuring this, you might not be able to see your
metrics in the adapter in certain scenarios.
- `--prometheus-url=<url>`: This is the URL used to connect to Prometheus. - `--prometheus-url=<url>`: This is the URL used to connect to Prometheus.
It will eventually contain query parameters to configure the connection. It will eventually contain query parameters to configure the connection.
- `--config=<yaml-file>` (`-c`): This configures how the adapter discovers available
Prometheus metrics and the associated Kubernetes resources, and how it presents those
metrics in the custom metrics API. More information about this file can be found in
[docs/config.md](docs/config.md).
Presentation Presentation
------------ ------------
The adapter gathers the names of available metrics from Prometheus The adapter gathers the names of available metrics from Prometheus
a regular interval (see [Configuration](#configuration) above), and then at a regular interval (see [Configuration](#configuration) above), and then
only exposes metrics that follow specific forms. only exposes metrics that follow specific forms.
In general: The rules governing this discovery are specified in a [configuration file](docs/config.md).
If you were relying on the implicit rules from the previous version of the adapter,
you can use the included `config-gen` tool to generate a configuration that matches
the old implicit ruleset:
- Metrics must have the `namespace` label to be considered. ```shell
$ go run cmd/config-gen/main.go [--rate-interval=<duration>] [--label-prefix=<prefix>]
- For each label on a metric, if that label name corresponds to ```
a Kubernetes resource (like `pod` or `service`), the metric will be
associated with that resource.
- Metrics ending in `_total` are assumed to be cumulative, and will be
exposed without the suffix as a rate metric.
Detailed information can be found under [docs/format.md](docs/format.md).
Example Example
------- -------
@ -63,7 +112,8 @@ Additionally, [@luxas](https://github.com/luxas) has an excellent example
deployment of Prometheus, this adapter, and a demo pod which serves deployment of Prometheus, this adapter, and a demo pod which serves
a metric `http_requests_total`, which becomes the custom metrics API a metric `http_requests_total`, which becomes the custom metrics API
metric `pods/http_requests`. It also autoscales on that metric using the metric `pods/http_requests`. It also autoscales on that metric using the
`autoscaling/v2alpha1` HorizontalPodAutoscaler. `autoscaling/v2beta1` HorizontalPodAutoscaler. Note that @luxas's tutorial
uses a slightly older version of the adapter.
It can be found at https://github.com/luxas/kubeadm-workshop. Pay special It can be found at https://github.com/luxas/kubeadm-workshop. Pay special
attention to: attention to:
@ -72,3 +122,110 @@ attention to:
Operator](https://github.com/luxas/kubeadm-workshop#deploying-the-prometheus-operator-for-monitoring-services-in-the-cluster) Operator](https://github.com/luxas/kubeadm-workshop#deploying-the-prometheus-operator-for-monitoring-services-in-the-cluster)
- [Setting up the custom metrics adapter and sample - [Setting up the custom metrics adapter and sample
app](https://github.com/luxas/kubeadm-workshop#deploying-a-custom-metrics-api-server-and-a-sample-app) app](https://github.com/luxas/kubeadm-workshop#deploying-a-custom-metrics-api-server-and-a-sample-app)
FAQs
----
### Why do my metrics keep jumping between a normal value and a very large number?
You're probably switching between whole numbers (e.g. `10`) and milli-quantities (e.g. `10500m`).
Worry not! This is just how Kubernetes represents fractional values. See the
[Quantity Values](/docs/walkthrough.md#quantity-values) section of the walkthrough for a bit more
information.
### Why isn't my metric showing up?
First, check your configuration. Does it select your metric? You can
find the [default configuration](/deploy/manifests/custom-metrics-config-map.yaml)
in the deploy directory, and more information about configuring the
adapter in the [docs](/docs/config.md).
Next, check if the discovery information looks right. You should see the
metrics showing up as associated with the resources you expect at
`/apis/custom.metrics.k8s.io/v1beta1/` (you can use `kubectl get --raw
/apis/custom.metrics.k8s.io/v1beta1` to check, and can pipe to `jq` to
pretty-print the results, if you have it installed). If not, make sure
your series are labeled correctly. Consumers of the custom metrics API
(especially the HPA) don't do any special logic to associate a particular
resource to a particular series, so you have to make sure that the adapter
does it instead.
For example, if you want a series `foo` to be associated with deployment
`bar` in namespace `somens`, make sure there's some label that represents
deployment name, and that the adapter is configured to use it. With the
default config, that means you'd need the query
`foo{namespace="somens",deployment="bar"}` to return some results in
Prometheus.
Next, try using the `--v=6` flag on the adapter to see the exact queries
being made by the adapter. Try url-decoding the query and pasting it into
the Prometheus web console to see if the query looks wrong.
### My query contains multiple metrics, how do I make that work?
It's actually fairly straightforward, if a bit non-obvious. Simply choose one
metric to act as the "discovery" and "naming" metric, and use that to configure
the "discovery" and "naming" parts of the configuration. Then, you can write
whichever metrics you want in the `metricsQuery`! The series query can contain
whichever metrics you want, as long as they have the right set of labels.
For example, suppose you have two metrics `foo_total` and `foo_count`,
both with the label `system_name`, which represents the `node` resource.
Then, you might write
```yaml
rules:
- seriesQuery: 'foo_total'
resources: {overrides: {system_name: {resource: "node"}}}
name:
matches: 'foo_total'
as: 'foo'
metricsQuery: 'sum(foo_total{<<.LabelMatchers>>}) by (<<.GroupBy>>) / sum(foo_count{<<.LabelMatchers>>}) by (<<.GroupBy>>)'
```
### I get errors about SubjectAccessReviews/system:anonymous/TLS/Certificates/RequestHeader!
It's important to understand the role of TLS in the Kubernetes cluster. There's a high-level
overview here: https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/auth.md.
All of the above errors generally boil down to misconfigured certificates.
Specifically, you'll need to make sure your cluster's aggregation layer is
properly configured, with requestheader certificates set up properly.
Errors about SubjectAccessReviews failing for system:anonymous generally mean
that your cluster's given requestheader CA doesn't trust the proxy certificates
from the API server aggregator.
On the other hand, if you get an error from the aggregator about invalid certificates,
it's probably because the CA specified in the `caBundle` field of your APIService
object doesn't trust the serving certificates for the adapter.
If you're seeing SubjectAccessReviews failures for non-anonymous users, check your
RBAC rules -- you probably haven't given users permission to operate on resources in
the `custom.metrics.k8s.io` API group.
### My metrics appear and disappear
You probably have a Prometheus collection interval or computation interval
that's larger than your adapter's discovery interval. If the metrics
appear in discovery but occaisionally return not-found, those intervals
are probably larger than one of the rate windows used in one of your
queries. The adapter only considers metrics with datapoints in the window
`[now-discoveryInterval, now]` (in order to only capture metrics that are
still present), so make sure that your discovery interval is at least as
large as your collection interval.
### I get errors when query namespace prefixed metrics?
I have namespace prefixed metrics like `{ "name": "namespaces/node_memory_PageTables_bytes", "singularName": "", "namespaced": false, "kind": "MetricValueList", "verbs": [ "get" ] }`, but I get error `Error from server (InternalError): Internal error occurred: unable to list matching resources` when access with `kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/*/node_memory_PageTables_bytes` .
Actually namespace prefixed metrics are special, we should access them with `kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/*/metrics/node_memory_PageTables_bytes`.
## Community, discussion, contribution, and support
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
You can reach the maintainers of this project at:
- [Slack](http://slack.k8s.io/)
- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-dev)

13
RELEASE.md Normal file
View file

@ -0,0 +1,13 @@
# Release Process
prometheus-adapter is released on an as-needed basis. The process is as follows:
1. An issue is proposing a new release with a changelog since the last release
1. At least one [OWNERS](OWNERS) must LGTM this release
1. A PR that bumps version hardcoded in code is created and merged
1. An OWNER creates a draft Github release
1. An OWNER creates a release tag using `git tag -s $VERSION`, inserts the changelog and pushes the tag with `git push $VERSION`. Then waits for [prow.k8s.io](https://prow.k8s.io) to build and push new images to [gcr.io/k8s-staging-prometheus-adapter](https://gcr.io/k8s-staging-prometheus-adapter)
1. A PR in [kubernetes/k8s.io](https://github.com/kubernetes/k8s.io/blob/main/k8s.gcr.io/images/k8s-staging-prometheus-adapter/images.yaml) is created to release images to `k8s.gcr.io`
1. An OWNER publishes the GitHub release
1. An announcement email is sent to `kubernetes-sig-instrumentation@googlegroups.com` with the subject `[ANNOUNCE] prometheus-adapter $VERSION is released`
1. The release issue is closed

22
SECURITY.md Normal file
View file

@ -0,0 +1,22 @@
# Security Policy
## Security Announcements
Join the [kubernetes-security-announce] group for security and vulnerability announcements.
You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss].
## Reporting a Vulnerability
Instructions for reporting a vulnerability can be found on the
[Kubernetes Security and Disclosure Information] page.
## Supported Versions
Information about supported Kubernetes versions can be found on the
[Kubernetes version and version skew support policy] page on the Kubernetes website.
[kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce
[kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50
[Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions
[Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability

14
SECURITY_CONTACTS Normal file
View file

@ -0,0 +1,14 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Committee to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
dgrisonnet
s-urbaniak

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.12.0

11
cloudbuild.yaml Normal file
View file

@ -0,0 +1,11 @@
# See https://cloud.google.com/cloud-build/docs/build-config
timeout: 3600s
options:
substitution_option: ALLOW_LOOSE
steps:
- name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90'
entrypoint: make
env:
- TAG=$_PULL_BASE_REF
args:
- push-all

View file

@ -17,27 +17,454 @@ limitations under the License.
package main package main
import ( import (
"flag" "crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"os" "os"
"runtime" "strings"
"time"
"k8s.io/apimachinery/pkg/util/wait" corev1 "k8s.io/api/core/v1"
"k8s.io/apiserver/pkg/util/logs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/client-go/metadata"
"k8s.io/client-go/metadata/metadatainformer"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/transport"
"k8s.io/component-base/logs"
"k8s.io/klog/v2"
"github.com/directxman12/k8s-prometheus-adapter/cmd/adapter/app" customexternalmetrics "sigs.k8s.io/custom-metrics-apiserver/pkg/apiserver"
basecmd "sigs.k8s.io/custom-metrics-apiserver/pkg/cmd"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
"sigs.k8s.io/metrics-server/pkg/api"
generatedopenapi "sigs.k8s.io/prometheus-adapter/pkg/api/generated/openapi"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
mprom "sigs.k8s.io/prometheus-adapter/pkg/client/metrics"
adaptercfg "sigs.k8s.io/prometheus-adapter/pkg/config"
cmprov "sigs.k8s.io/prometheus-adapter/pkg/custom-provider"
extprov "sigs.k8s.io/prometheus-adapter/pkg/external-provider"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
resprov "sigs.k8s.io/prometheus-adapter/pkg/resourceprovider"
) )
type PrometheusAdapter struct {
basecmd.AdapterBase
// PrometheusURL is the URL describing how to connect to Prometheus. Query parameters configure connection options.
PrometheusURL string
// PrometheusAuthInCluster enables using the auth details from the in-cluster kubeconfig to connect to Prometheus
PrometheusAuthInCluster bool
// PrometheusAuthConf is the kubeconfig file that contains auth details used to connect to Prometheus
PrometheusAuthConf string
// PrometheusCAFile points to the file containing the ca-root for connecting with Prometheus
PrometheusCAFile string
// PrometheusClientTLSCertFile points to the file containing the client TLS cert for connecting with Prometheus
PrometheusClientTLSCertFile string
// PrometheusClientTLSKeyFile points to the file containing the client TLS key for connecting with Prometheus
PrometheusClientTLSKeyFile string
// PrometheusTokenFile points to the file that contains the bearer token when connecting with Prometheus
PrometheusTokenFile string
// PrometheusHeaders is a k=v list of headers to set on requests to PrometheusURL
PrometheusHeaders []string
// PrometheusVerb is a verb to set on requests to PrometheusURL
PrometheusVerb string
// AdapterConfigFile points to the file containing the metrics discovery configuration.
AdapterConfigFile string
// MetricsRelistInterval is the interval at which to relist the set of available metrics
MetricsRelistInterval time.Duration
// MetricsMaxAge is the period to query available metrics for
MetricsMaxAge time.Duration
// DisableHTTP2 indicates that http2 should not be enabled.
DisableHTTP2 bool
metricsConfig *adaptercfg.MetricsDiscoveryConfig
}
func (cmd *PrometheusAdapter) makePromClient() (prom.Client, error) {
baseURL, err := url.Parse(cmd.PrometheusURL)
if err != nil {
return nil, fmt.Errorf("invalid Prometheus URL %q: %v", baseURL, err)
}
if cmd.PrometheusVerb != http.MethodGet && cmd.PrometheusVerb != http.MethodPost {
return nil, fmt.Errorf("unsupported Prometheus HTTP verb %q; supported verbs: \"GET\" and \"POST\"", cmd.PrometheusVerb)
}
var httpClient *http.Client
if cmd.PrometheusCAFile != "" {
prometheusCAClient, err := makePrometheusCAClient(cmd.PrometheusCAFile, cmd.PrometheusClientTLSCertFile, cmd.PrometheusClientTLSKeyFile)
if err != nil {
return nil, err
}
httpClient = prometheusCAClient
klog.Info("successfully loaded ca from file")
} else {
kubeconfigHTTPClient, err := makeKubeconfigHTTPClient(cmd.PrometheusAuthInCluster, cmd.PrometheusAuthConf)
if err != nil {
return nil, err
}
httpClient = kubeconfigHTTPClient
klog.Info("successfully using in-cluster auth")
}
if cmd.PrometheusTokenFile != "" {
data, err := os.ReadFile(cmd.PrometheusTokenFile)
if err != nil {
return nil, fmt.Errorf("failed to read prometheus-token-file: %v", err)
}
wrappedTransport := http.DefaultTransport
if httpClient.Transport != nil {
wrappedTransport = httpClient.Transport
}
httpClient.Transport = transport.NewBearerAuthRoundTripper(string(data), wrappedTransport)
}
genericPromClient := prom.NewGenericAPIClient(httpClient, baseURL, parseHeaderArgs(cmd.PrometheusHeaders))
instrumentedGenericPromClient := mprom.InstrumentGenericAPIClient(genericPromClient, baseURL.String())
return prom.NewClientForAPI(instrumentedGenericPromClient, cmd.PrometheusVerb), nil
}
func (cmd *PrometheusAdapter) addFlags() {
cmd.Flags().StringVar(&cmd.PrometheusURL, "prometheus-url", cmd.PrometheusURL,
"URL for connecting to Prometheus.")
cmd.Flags().BoolVar(&cmd.PrometheusAuthInCluster, "prometheus-auth-incluster", cmd.PrometheusAuthInCluster,
"use auth details from the in-cluster kubeconfig when connecting to prometheus.")
cmd.Flags().StringVar(&cmd.PrometheusAuthConf, "prometheus-auth-config", cmd.PrometheusAuthConf,
"kubeconfig file used to configure auth when connecting to Prometheus.")
cmd.Flags().StringVar(&cmd.PrometheusCAFile, "prometheus-ca-file", cmd.PrometheusCAFile,
"Optional CA file to use when connecting with Prometheus")
cmd.Flags().StringVar(&cmd.PrometheusClientTLSCertFile, "prometheus-client-tls-cert-file", cmd.PrometheusClientTLSCertFile,
"Optional client TLS cert file to use when connecting with Prometheus, auto-renewal is not supported")
cmd.Flags().StringVar(&cmd.PrometheusClientTLSKeyFile, "prometheus-client-tls-key-file", cmd.PrometheusClientTLSKeyFile,
"Optional client TLS key file to use when connecting with Prometheus, auto-renewal is not supported")
cmd.Flags().StringVar(&cmd.PrometheusTokenFile, "prometheus-token-file", cmd.PrometheusTokenFile,
"Optional file containing the bearer token to use when connecting with Prometheus")
cmd.Flags().StringArrayVar(&cmd.PrometheusHeaders, "prometheus-header", cmd.PrometheusHeaders,
"Optional header to set on requests to prometheus-url. Can be repeated")
cmd.Flags().StringVar(&cmd.PrometheusVerb, "prometheus-verb", cmd.PrometheusVerb,
"HTTP verb to set on requests to Prometheus. Possible values: \"GET\", \"POST\"")
cmd.Flags().StringVar(&cmd.AdapterConfigFile, "config", cmd.AdapterConfigFile,
"Configuration file containing details of how to transform between Prometheus metrics "+
"and custom metrics API resources")
cmd.Flags().DurationVar(&cmd.MetricsRelistInterval, "metrics-relist-interval", cmd.MetricsRelistInterval,
"interval at which to re-list the set of all available metrics from Prometheus")
cmd.Flags().DurationVar(&cmd.MetricsMaxAge, "metrics-max-age", cmd.MetricsMaxAge,
"period for which to query the set of available metrics from Prometheus")
cmd.Flags().BoolVar(&cmd.DisableHTTP2, "disable-http2", cmd.DisableHTTP2,
"Disable HTTP/2 support")
// Add logging flags
logs.AddFlags(cmd.Flags())
}
func (cmd *PrometheusAdapter) loadConfig() error {
// load metrics discovery configuration
if cmd.AdapterConfigFile == "" {
return fmt.Errorf("no metrics discovery configuration file specified (make sure to use --config)")
}
metricsConfig, err := adaptercfg.FromFile(cmd.AdapterConfigFile)
if err != nil {
return fmt.Errorf("unable to load metrics discovery configuration: %v", err)
}
cmd.metricsConfig = metricsConfig
return nil
}
func (cmd *PrometheusAdapter) makeProvider(promClient prom.Client, stopCh <-chan struct{}) (provider.CustomMetricsProvider, error) {
if len(cmd.metricsConfig.Rules) == 0 {
return nil, nil
}
if cmd.MetricsMaxAge < cmd.MetricsRelistInterval {
return nil, fmt.Errorf("max age must not be less than relist interval")
}
// grab the mapper and dynamic client
mapper, err := cmd.RESTMapper()
if err != nil {
return nil, fmt.Errorf("unable to construct RESTMapper: %v", err)
}
dynClient, err := cmd.DynamicClient()
if err != nil {
return nil, fmt.Errorf("unable to construct Kubernetes client: %v", err)
}
// extract the namers
namers, err := naming.NamersFromConfig(cmd.metricsConfig.Rules, mapper)
if err != nil {
return nil, fmt.Errorf("unable to construct naming scheme from metrics rules: %v", err)
}
// construct the provider and start it
cmProvider, runner := cmprov.NewPrometheusProvider(mapper, dynClient, promClient, namers, cmd.MetricsRelistInterval, cmd.MetricsMaxAge)
runner.RunUntil(stopCh)
return cmProvider, nil
}
func (cmd *PrometheusAdapter) makeExternalProvider(promClient prom.Client, stopCh <-chan struct{}) (provider.ExternalMetricsProvider, error) {
if len(cmd.metricsConfig.ExternalRules) == 0 {
return nil, nil
}
// grab the mapper
mapper, err := cmd.RESTMapper()
if err != nil {
return nil, fmt.Errorf("unable to construct RESTMapper: %v", err)
}
// extract the namers
namers, err := naming.NamersFromConfig(cmd.metricsConfig.ExternalRules, mapper)
if err != nil {
return nil, fmt.Errorf("unable to construct naming scheme from metrics rules: %v", err)
}
// construct the provider and start it
emProvider, runner := extprov.NewExternalPrometheusProvider(promClient, namers, cmd.MetricsRelistInterval, cmd.MetricsMaxAge)
runner.RunUntil(stopCh)
return emProvider, nil
}
func (cmd *PrometheusAdapter) addResourceMetricsAPI(promClient prom.Client, stopCh <-chan struct{}) error {
if cmd.metricsConfig.ResourceRules == nil {
// bail if we don't have rules for setting things up
return nil
}
mapper, err := cmd.RESTMapper()
if err != nil {
return err
}
provider, err := resprov.NewProvider(promClient, mapper, cmd.metricsConfig.ResourceRules)
if err != nil {
return fmt.Errorf("unable to construct resource metrics API provider: %v", err)
}
rest, err := cmd.ClientConfig()
if err != nil {
return err
}
client, err := metadata.NewForConfig(rest)
if err != nil {
return err
}
podInformerFactory := metadatainformer.NewFilteredSharedInformerFactory(client, 0, corev1.NamespaceAll, func(options *metav1.ListOptions) {
options.FieldSelector = "status.phase=Running"
})
podInformer := podInformerFactory.ForResource(corev1.SchemeGroupVersion.WithResource("pods"))
informer, err := cmd.Informers()
if err != nil {
return err
}
config, err := cmd.Config()
if err != nil {
return err
}
config.GenericConfig.EnableMetrics = false
server, err := cmd.Server()
if err != nil {
return err
}
metricsHandler, err := mprom.MetricsHandler()
if err != nil {
return err
}
server.GenericAPIServer.Handler.NonGoRestfulMux.HandleFunc("/metrics", metricsHandler)
if err := api.Install(provider, podInformer.Lister(), informer.Core().V1().Nodes().Lister(), server.GenericAPIServer, nil); err != nil {
return err
}
go podInformer.Informer().Run(stopCh)
return nil
}
func main() { func main() {
logs.InitLogs() logs.InitLogs()
defer logs.FlushLogs() defer logs.FlushLogs()
if len(os.Getenv("GOMAXPROCS")) == 0 { // set up flags
runtime.GOMAXPROCS(runtime.NumCPU()) cmd := &PrometheusAdapter{
PrometheusURL: "https://localhost",
PrometheusVerb: http.MethodGet,
MetricsRelistInterval: 10 * time.Minute,
}
cmd.Name = "prometheus-metrics-adapter"
cmd.addFlags()
if err := cmd.Flags().Parse(os.Args); err != nil {
klog.Fatalf("unable to parse flags: %v", err)
} }
cmd := app.NewCommandStartPrometheusAdapterServer(os.Stdout, os.Stderr, wait.NeverStop) if cmd.OpenAPIConfig == nil {
cmd.Flags().AddGoFlagSet(flag.CommandLine) cmd.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(api.Scheme, customexternalmetrics.Scheme))
if err := cmd.Execute(); err != nil { cmd.OpenAPIConfig.Info.Title = "prometheus-metrics-adapter"
panic(err) cmd.OpenAPIConfig.Info.Version = "1.0.0"
}
if cmd.OpenAPIV3Config == nil {
cmd.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(generatedopenapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(api.Scheme, customexternalmetrics.Scheme))
cmd.OpenAPIV3Config.Info.Title = "prometheus-metrics-adapter"
cmd.OpenAPIV3Config.Info.Version = "1.0.0"
}
// if --metrics-max-age is not set, make it equal to --metrics-relist-interval
if cmd.MetricsMaxAge == 0*time.Second {
cmd.MetricsMaxAge = cmd.MetricsRelistInterval
}
// make the prometheus client
promClient, err := cmd.makePromClient()
if err != nil {
klog.Fatalf("unable to construct Prometheus client: %v", err)
}
// load the config
if err := cmd.loadConfig(); err != nil {
klog.Fatalf("unable to load metrics discovery config: %v", err)
}
// stop channel closed on SIGTERM and SIGINT
stopCh := genericapiserver.SetupSignalHandler()
// construct the provider
cmProvider, err := cmd.makeProvider(promClient, stopCh)
if err != nil {
klog.Fatalf("unable to construct custom metrics provider: %v", err)
}
// attach the provider to the server, if it's needed
if cmProvider != nil {
cmd.WithCustomMetrics(cmProvider)
}
// construct the external provider
emProvider, err := cmd.makeExternalProvider(promClient, stopCh)
if err != nil {
klog.Fatalf("unable to construct external metrics provider: %v", err)
}
// attach the provider to the server, if it's needed
if emProvider != nil {
cmd.WithExternalMetrics(emProvider)
}
// attach resource metrics support, if it's needed
if err := cmd.addResourceMetricsAPI(promClient, stopCh); err != nil {
klog.Fatalf("unable to install resource metrics API: %v", err)
}
// disable HTTP/2 to mitigate CVE-2023-44487 until the Go standard library
// and golang.org/x/net are fully fixed.
server, err := cmd.Server()
if err != nil {
klog.Fatalf("unable to fetch server: %v", err)
}
server.GenericAPIServer.SecureServingInfo.DisableHTTP2 = cmd.DisableHTTP2
// run the server
if err := cmd.Run(stopCh); err != nil {
klog.Fatalf("unable to run custom metrics adapter: %v", err)
} }
} }
// makeKubeconfigHTTPClient constructs an HTTP for connecting with the given auth options.
func makeKubeconfigHTTPClient(inClusterAuth bool, kubeConfigPath string) (*http.Client, error) {
// make sure we're not trying to use two different sources of auth
if inClusterAuth && kubeConfigPath != "" {
return nil, fmt.Errorf("may not use both in-cluster auth and an explicit kubeconfig at the same time")
}
// return the default client if we're using no auth
if !inClusterAuth && kubeConfigPath == "" {
return http.DefaultClient, nil
}
var authConf *rest.Config
if kubeConfigPath != "" {
var err error
loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath}
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
authConf, err = loader.ClientConfig()
if err != nil {
return nil, fmt.Errorf("unable to construct auth configuration from %q for connecting to Prometheus: %v", kubeConfigPath, err)
}
} else {
var err error
authConf, err = rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("unable to construct in-cluster auth configuration for connecting to Prometheus: %v", err)
}
}
tr, err := rest.TransportFor(authConf)
if err != nil {
return nil, fmt.Errorf("unable to construct client transport for connecting to Prometheus: %v", err)
}
return &http.Client{Transport: tr}, nil
}
func makePrometheusCAClient(caFilePath string, tlsCertFilePath string, tlsKeyFilePath string) (*http.Client, error) {
data, err := os.ReadFile(caFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read prometheus-ca-file: %v", err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(data) {
return nil, fmt.Errorf("no certs found in prometheus-ca-file")
}
if (tlsCertFilePath != "") && (tlsKeyFilePath != "") {
tlsClientCerts, err := tls.LoadX509KeyPair(tlsCertFilePath, tlsKeyFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read TLS key pair: %v", err)
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{tlsClientCerts},
MinVersion: tls.VersionTLS12,
},
},
}, nil
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
MinVersion: tls.VersionTLS12,
},
},
}, nil
}
func parseHeaderArgs(args []string) http.Header {
headers := make(http.Header, len(args))
for _, h := range args {
parts := strings.SplitN(h, "=", 2)
value := ""
if len(parts) > 1 {
value = parts[1]
}
headers.Add(parts[0], value)
}
return headers
}

211
cmd/adapter/adapter_test.go Normal file
View file

@ -0,0 +1,211 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"net/http"
"os"
"path/filepath"
"reflect"
"testing"
)
const certsDir = "testdata"
func TestMakeKubeconfigHTTPClient(t *testing.T) {
tests := []struct {
kubeconfigPath string
inClusterAuth bool
success bool
}{
{
kubeconfigPath: filepath.Join(certsDir, "kubeconfig"),
inClusterAuth: false,
success: true,
},
{
kubeconfigPath: filepath.Join(certsDir, "kubeconfig"),
inClusterAuth: true,
success: false,
},
{
kubeconfigPath: filepath.Join(certsDir, "kubeconfig-error"),
inClusterAuth: false,
success: false,
},
{
kubeconfigPath: "",
inClusterAuth: false,
success: true,
},
}
os.Setenv("KUBERNETES_SERVICE_HOST", "prometheus")
os.Setenv("KUBERNETES_SERVICE_PORT", "8080")
for _, test := range tests {
t.Logf("Running test for: inClusterAuth %v, kubeconfigPath %v", test.inClusterAuth, test.kubeconfigPath)
kubeconfigHTTPClient, err := makeKubeconfigHTTPClient(test.inClusterAuth, test.kubeconfigPath)
if test.success {
if err != nil {
t.Errorf("Error is %v, expected nil", err)
continue
}
if kubeconfigHTTPClient.Transport == nil {
if test.inClusterAuth || test.kubeconfigPath != "" {
t.Error("HTTP client Transport is nil, expected http.RoundTripper")
}
}
} else if err == nil {
t.Errorf("Error is nil, expected %v", err)
}
}
}
func TestMakePrometheusCAClient(t *testing.T) {
tests := []struct {
caFilePath string
tlsCertFilePath string
tlsKeyFilePath string
success bool
tlsUsed bool
}{
{
caFilePath: filepath.Join(certsDir, "ca.pem"),
tlsCertFilePath: filepath.Join(certsDir, "tlscert.crt"),
tlsKeyFilePath: filepath.Join(certsDir, "tlskey.key"),
success: true,
tlsUsed: true,
},
{
caFilePath: filepath.Join(certsDir, "ca-error.pem"),
tlsCertFilePath: filepath.Join(certsDir, "tlscert.crt"),
tlsKeyFilePath: filepath.Join(certsDir, "tlskey.key"),
success: false,
tlsUsed: true,
},
{
caFilePath: filepath.Join(certsDir, "ca.pem"),
tlsCertFilePath: filepath.Join(certsDir, "tlscert-error.crt"),
tlsKeyFilePath: filepath.Join(certsDir, "tlskey.key"),
success: false,
tlsUsed: true,
},
{
caFilePath: filepath.Join(certsDir, "ca.pem"),
tlsCertFilePath: "",
tlsKeyFilePath: "",
success: true,
tlsUsed: false,
},
}
for _, test := range tests {
t.Logf("Running test for: caFilePath %v, tlsCertFilePath %v, tlsKeyFilePath %v", test.caFilePath, test.tlsCertFilePath, test.tlsKeyFilePath)
prometheusCAClient, err := makePrometheusCAClient(test.caFilePath, test.tlsCertFilePath, test.tlsKeyFilePath)
if test.success {
if err != nil {
t.Errorf("Error is %v, expected nil", err)
continue
}
if prometheusCAClient.Transport.(*http.Transport).TLSClientConfig.RootCAs == nil {
t.Error("RootCAs is nil, expected *x509.CertPool")
continue
}
if test.tlsUsed {
if prometheusCAClient.Transport.(*http.Transport).TLSClientConfig.Certificates == nil {
t.Error("TLS certificates is nil, expected []tls.Certificate")
continue
}
} else {
if prometheusCAClient.Transport.(*http.Transport).TLSClientConfig.Certificates != nil {
t.Errorf("TLS certificates is %+v, expected nil", prometheusCAClient.Transport.(*http.Transport).TLSClientConfig.Certificates)
}
}
} else if err == nil {
t.Errorf("Error is nil, expected %v", err)
}
}
}
func TestParseHeaderArgs(t *testing.T) {
tests := []struct {
args []string
headers http.Header
}{
{
headers: http.Header{},
},
{
args: []string{"foo=bar"},
headers: http.Header{
"Foo": []string{"bar"},
},
},
{
args: []string{"foo"},
headers: http.Header{
"Foo": []string{""},
},
},
{
args: []string{"foo=bar", "foo=baz", "bux=baz=23"},
headers: http.Header{
"Foo": []string{"bar", "baz"},
"Bux": []string{"baz=23"},
},
},
}
for _, test := range tests {
got := parseHeaderArgs(test.args)
if !reflect.DeepEqual(got, test.headers) {
t.Errorf("Expected %#v but got %#v", test.headers, got)
}
}
}
func TestFlags(t *testing.T) {
cmd := &PrometheusAdapter{
PrometheusURL: "https://localhost",
}
cmd.addFlags()
flags := cmd.FlagSet
if flags == nil {
t.Fatalf("FlagSet should not be nil")
}
expectedFlags := []struct {
flag string
defaultValue string
}{
{flag: "v", defaultValue: "0"}, // logging flag (klog)
{flag: "prometheus-url", defaultValue: "https://localhost"}, // default is set in cmd
}
for _, e := range expectedFlags {
flag := flags.Lookup(e.flag)
if flag == nil {
t.Errorf("Flag %q expected to be present, was absent", e.flag)
continue
}
if flag.DefValue != e.defaultValue {
t.Errorf("Expected default value %q for flag %q, got %q", e.defaultValue, e.flag, flag.DefValue)
}
}
}

View file

@ -1,156 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package app
import (
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
mprom "github.com/directxman12/k8s-prometheus-adapter/pkg/client/metrics"
cmprov "github.com/directxman12/k8s-prometheus-adapter/pkg/custom-provider"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/cmd/server"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/dynamicmapper"
)
// NewCommandStartPrometheusAdapterServer provides a CLI handler for 'start master' command
func NewCommandStartPrometheusAdapterServer(out, errOut io.Writer, stopCh <-chan struct{}) *cobra.Command {
baseOpts := server.NewCustomMetricsAdapterServerOptions(out, errOut)
o := PrometheusAdapterServerOptions{
CustomMetricsAdapterServerOptions: baseOpts,
MetricsRelistInterval: 10 * time.Minute,
RateInterval: 5 * time.Minute,
PrometheusURL: "https://localhost",
DiscoveryInterval: 10 * time.Minute,
}
cmd := &cobra.Command{
Short: "Launch the custom metrics API adapter server",
Long: "Launch the custom metrics API adapter server",
RunE: func(c *cobra.Command, args []string) error {
if err := o.Complete(); err != nil {
return err
}
if err := o.Validate(args); err != nil {
return err
}
if err := o.RunCustomMetricsAdapterServer(stopCh); err != nil {
return err
}
return nil
},
}
flags := cmd.Flags()
o.SecureServing.AddFlags(flags)
o.Authentication.AddFlags(flags)
o.Authorization.AddFlags(flags)
o.Features.AddFlags(flags)
flags.StringVar(&o.RemoteKubeConfigFile, "lister-kubeconfig", o.RemoteKubeConfigFile, ""+
"kubeconfig file pointing at the 'core' kubernetes server with enough rights to list "+
"any described objets")
flags.DurationVar(&o.MetricsRelistInterval, "metrics-relist-interval", o.MetricsRelistInterval, ""+
"interval at which to re-list the set of all available metrics from Prometheus")
flags.DurationVar(&o.RateInterval, "rate-interval", o.RateInterval, ""+
"period of time used to calculate rate metrics from cumulative metrics")
flags.DurationVar(&o.DiscoveryInterval, "discovery-interval", o.DiscoveryInterval, ""+
"interval at which to refresh API discovery information")
flags.StringVar(&o.PrometheusURL, "prometheus-url", o.PrometheusURL,
"URL and configuration for connecting to Prometheus. Query parameters are used to configure the connection")
return cmd
}
func (o PrometheusAdapterServerOptions) RunCustomMetricsAdapterServer(stopCh <-chan struct{}) error {
config, err := o.Config()
if err != nil {
return err
}
config.GenericConfig.EnableMetrics = true
var clientConfig *rest.Config
if len(o.RemoteKubeConfigFile) > 0 {
loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.RemoteKubeConfigFile}
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
clientConfig, err = loader.ClientConfig()
} else {
clientConfig, err = rest.InClusterConfig()
}
if err != nil {
return fmt.Errorf("unable to construct lister client config to initialize provider: %v", err)
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(clientConfig)
if err != nil {
return fmt.Errorf("unable to construct discovery client for dynamic client: %v", err)
}
dynamicMapper, err := dynamicmapper.NewRESTMapper(discoveryClient, apimeta.InterfacesForUnstructured, o.DiscoveryInterval)
if err != nil {
return fmt.Errorf("unable to construct dynamic discovery mapper: %v", err)
}
clientPool := dynamic.NewClientPool(clientConfig, dynamicMapper, dynamic.LegacyAPIPathResolverFunc)
if err != nil {
return fmt.Errorf("unable to construct lister client to initialize provider: %v", err)
}
// TODO: actually configure this client (strip query vars, etc)
baseURL, err := url.Parse(o.PrometheusURL)
if err != nil {
return fmt.Errorf("invalid Prometheus URL %q: %v", baseURL, err)
}
genericPromClient := prom.NewGenericAPIClient(http.DefaultClient, baseURL)
instrumentedGenericPromClient := mprom.InstrumentGenericAPIClient(genericPromClient, baseURL.String())
promClient := prom.NewClientForAPI(instrumentedGenericPromClient)
cmProvider := cmprov.NewPrometheusProvider(dynamicMapper, clientPool, promClient, o.MetricsRelistInterval, o.RateInterval, stopCh)
server, err := config.Complete().New("prometheus-custom-metrics-adapter", cmProvider)
if err != nil {
return err
}
return server.GenericAPIServer.PrepareRun().Run(stopCh)
}
type PrometheusAdapterServerOptions struct {
*server.CustomMetricsAdapterServerOptions
// RemoteKubeConfigFile is the config used to list pods from the master API server
RemoteKubeConfigFile string
// MetricsRelistInterval is the interval at which to relist the set of available metrics
MetricsRelistInterval time.Duration
// RateInterval is the period of time used to calculate rate metrics
RateInterval time.Duration
// DiscoveryInterval is the interval at which discovery information is refreshed
DiscoveryInterval time.Duration
// PrometheusURL is the URL describing how to connect to Prometheus. Query parameters configure connection options.
PrometheusURL string
}

16
cmd/adapter/testdata/ca-error.pem vendored Normal file
View file

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIIDdjCCAl4CCQDdbOsYxSKoeDANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJV
UzENMAsGA1UEBwwEVGVzdDEaMBgGA1UECgwRUHJvbWV0aGV1c0FkYXB0ZXIxJDAi
BgNVBAMMG2s4cy1wcm9tZXRoZXVzLWFkYXB0ZXIudGVzdDEdMBsGCSqGSIb3DQEJ
ARYOdGVzdEB0ZXN0LnRlc3QwHhcNMjEwMjA5MTE0NzUwWhcNMjYwMjA4MTE0NzUw
WjB9MQswCQYDVQQGEwJVUzENMAsGA1UEBwwEVGVzdDEaMBgGA1UECgwRUHJvbWV0
aGV1c0FkYXB0ZXIxJDAiBgNVBAMMG2s4cy1wcm9tZXRoZXVzLWFkYXB0ZXIudGVz
dDEdMBsGCSqGSIb3DQEJARYOdGVzdEB0ZXN0LnRlc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC24TDfTWLtYZPLDXqEjF7yn4K7oBOltX5Nngsk7LNd
AQELBQADggEBAD/bbeAZuyvtuEwdJ+4wkhBsHYXQ4OPxff1f3t4buIQFtnilWTXE
S60K3SEaQS8rOw8V9eHmzCsh3mPuVCoM7WsgKhp2mVhbGVZoBWBZ8kPQXqtsw+v4
tqTuJXnFPiF4clXb6Wp96Rc7nxzRAfn/6uVbSWds4JwRToUVszVOxe+yu0I84vuB
SHrRa077b1V+UT8otm+C5tC3jBZ0/IPRNWoT/rVcSoVLouX0fkbtxNF7c9v+PYg6
849A9T8cGKWKpKPGNEwBL9HYwtK6W0tTJr8A8pnAJ/UlniHA6u7SMHN+NoqBfi6M
bqq9lQ4QhjFrN2B1z9r3ak+EzQX1711TQ8w=
-----END CERTIFICATE-----

20
cmd/adapter/testdata/ca.pem vendored Normal file
View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDLjCCAhYCCQDlnNCOw7JHFDANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJV
UzENMAsGA1UECgwEVGVzdDEbMBkGA1UEAwwScHJvbWV0aGV1cy1hZGFwdGVyMR0w
GwYJKoZIhvcNAQkBFg50ZXN0QHRlc3QudGVzdDAgFw0yMTAyMjIyMDMxNTBaGA80
NzU5MDEyMDIwMzE1MFowWDELMAkGA1UEBhMCVVMxDTALBgNVBAoMBFRlc3QxGzAZ
BgNVBAMMEnByb21ldGhldXMtYWRhcHRlcjEdMBsGCSqGSIb3DQEJARYOdGVzdEB0
ZXN0LnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvn9HQlfhw
qDH77+eEFU+N+ztqCtat54neVez+sFa4dfYxuvVYK+nc+oh4E7SS4u+17eKV+QFb
ZhRhrOTNI+fmuO+xDPKyU1MuYUDfwasRfMDcUpssea2fO/SrsxHmX9oOam0kgefJ
8aSwI9TYw4N4kIpG+EGatogDlR2KXhrqsRfx5PUB4npFaCrdoglyvvAQig83Iq5L
+bCknSe6NUMiqtL9CcuLzzRKB3DMOrvbB0tJdb4uv/gS26sx/Hp/1ri73/tv4I9z
GLLoUUoff7vfvxrhiGR9i+qBOda7THbbmYBD54y+SR0dBa2uuDDX0JbgNNfXtjiG
52hvAnc1/wv7AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACCysIzT9NKaniEvXtnx
Yx/jRxpiEEUGl8kg83a95X4f13jdPpUSwcn3/iK5SAE/7ntGVM+ajtlXrHGxwjB7
ER0w4WC6Ozypzoh/yI/VXs+DRJTJu8CBJOBRQEpzkK4r64HU8iN2c9lPp1+6b3Vy
jfbf3yfnRUbJztSjOFDUeA2t3FThVddhqif/oxj65s5R8p9HEurcwhA3Q6lE53yx
jgee8qV9HXAqa4V0qQQJ0tjcpajhQahDTtThRr+Z2H4TzQuwHa3dM7IIF6EPWsCo
DtbUXEPL7zT3EBH7THOdvNsFlD/SFmT2RwiQ5606bRAHwAzzxjxjxFTMl7r4tX5W
Ldc=
-----END CERTIFICATE-----

17
cmd/adapter/testdata/kubeconfig vendored Normal file
View file

@ -0,0 +1,17 @@
apiVersion: v1
kind: Config
clusters:
- name: test
cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRRGxuTkNPdzdKSEZEQU5CZ2txaGtpRzl3MEJBUXNGQURCWU1Rc3dDUVlEVlFRR0V3SlYKVXpFTk1Bc0dBMVVFQ2d3RVZHVnpkREViTUJrR0ExVUVBd3dTY0hKdmJXVjBhR1YxY3kxaFpHRndkR1Z5TVIwdwpHd1lKS29aSWh2Y05BUWtCRmc1MFpYTjBRSFJsYzNRdWRHVnpkREFnRncweU1UQXlNakl5TURNeE5UQmFHQTgwCk56VTVNREV5TURJd016RTFNRm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhEVEFMQmdOVkJBb01CRlJsYzNReEd6QVoKQmdOVkJBTU1FbkJ5YjIxbGRHaGxkWE10WVdSaGNIUmxjakVkTUJzR0NTcUdTSWIzRFFFSkFSWU9kR1Z6ZEVCMApaWE4wTG5SbGMzUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDdm45SFFsZmh3CnFESDc3K2VFRlUrTit6dHFDdGF0NTRuZVZleitzRmE0ZGZZeHV2VllLK25jK29oNEU3U1M0dSsxN2VLVitRRmIKWmhSaHJPVE5JK2ZtdU8reERQS3lVMU11WVVEZndhc1JmTURjVXBzc2VhMmZPL1Nyc3hIbVg5b09hbTBrZ2VmSgo4YVN3STlUWXc0TjRrSXBHK0VHYXRvZ0RsUjJLWGhycXNSZng1UFVCNG5wRmFDcmRvZ2x5dnZBUWlnODNJcTVMCitiQ2tuU2U2TlVNaXF0TDlDY3VMenpSS0IzRE1PcnZiQjB0SmRiNHV2L2dTMjZzeC9IcC8xcmk3My90djRJOXoKR0xMb1VVb2ZmN3ZmdnhyaGlHUjlpK3FCT2RhN1RIYmJtWUJENTR5K1NSMGRCYTJ1dUREWDBKYmdOTmZYdGppRwo1Mmh2QW5jMS93djdBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDQ3lzSXpUOU5LYW5pRXZYdG54Cll4L2pSeHBpRUVVR2w4a2c4M2E5NVg0ZjEzamRQcFVTd2NuMy9pSzVTQUUvN250R1ZNK2FqdGxYckhHeHdqQjcKRVIwdzRXQzZPenlwem9oL3lJL1ZYcytEUkpUSnU4Q0JKT0JSUUVwemtLNHI2NEhVOGlOMmM5bFBwMSs2YjNWeQpqZmJmM3lmblJVYkp6dFNqT0ZEVWVBMnQzRlRoVmRkaHFpZi9veGo2NXM1UjhwOUhFdXJjd2hBM1E2bEU1M3l4CmpnZWU4cVY5SFhBcWE0VjBxUVFKMHRqY3BhamhRYWhEVHRUaFJyK1oySDRUelF1d0hhM2RNN0lJRjZFUFdzQ28KRHRiVVhFUEw3elQzRUJIN1RIT2R2TnNGbEQvU0ZtVDJSd2lRNTYwNmJSQUh3QXp6eGp4anhGVE1sN3I0dFg1VwpMZGM9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: test.test
contexts:
- name: test
context:
cluster: test
user: test-user
current-context: test
users:
- name: test-user
user:
token: abcde12345

18
cmd/adapter/testdata/kubeconfig-error vendored Normal file
View file

@ -0,0 +1,18 @@
apiVersion: v1
kind: Config
clusters:
- name: test
cluster:
certificate-authority-data: abcde12345
server: test.test
contexts:
- name: test
context:
cluster: test
user: test-user
current-context: test
users:
- name: test-user
user:
token: abcde12345

24
cmd/adapter/testdata/tlscert-error.crt vendored Normal file
View file

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIFdjCCA14CCQC+svUhDVv51DANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJV
UzENMAsGA1UEBwwEVGVzdDEaMBgGA1UECgwRUHJvbWV0aGV1c0FkYXB0ZXIxJDAi
BgNVBAMMG2s4cy1wcm9tZXRoZXVzLWFkYXB0ZXIudGVzdDEdMBsGCSqGSIb3DQEJ
ARYOdGVzdEB0ZXN0LnRlc3QwHhcNMjEwMjA5MTE0NDMyWhcNMjIwMjA5MTE0NDMy
WjB9MQswCQYDVQQGEwJVUzENMAsGA1UEBwwEVGVzdDEaMBgGA1UECgwRUHJvbWV0
aGV1c0FkYXB0ZXIxJDAiBgNVBAMMG2s4cy1wcm9tZXRoZXVzLWFkYXB0ZXIudGVz
dDEdMBsGCSqGSIb3DQEJARYOdGVzdEB0ZXN0LnRlc3QwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDtLqKuIJqRETLOUSMDBtUDmIBaD2pG9Qv+cOBhQbVS
apZRWk8uKZKxqBOxgQ3UxY1szeVkx1Dphe3RN6ndmofiRc23ns1qncbDllgbtflk
GFvLKGcVBa6Z/lZ6FCZDWn6K6mJb0a7jtkOMG6+J/5eJHfZ23u/GYL1RKxH+qPPc
AwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQC0oE4/Yel5GHeuKJ5+U9KEBsLCrBjj
jUW8or7SIZ1Gl4J2ak3p3WabhZjqOe10rsIXCNTaC2rEMvkNiP5Om0aeE6bx14jV
e9FIfJ7ytAL/PISaZXINgml05m4Su3bUaxCQpajBgqneNp4w57jfeFhcPt35j0H4
bxGk/hnIY1MmRULSOFBstmxNZSDNsGlTcZoN3+0KtuqLg6vTNuuJIyx1zd9/QT8t
RJ4fgrffJcPJscvq6hEdWmtcJhaDLWOEblsbFfN0J+zK07hHhqRavQrnwaBZgFWa
OIqAo6NfZONhCFy9mWFxLvQky1NXr60y220+N1GkEiLRQES7+p1pcKgn0v+f2EfW
uN6+LCppWX7FqtkB3OhZkHM6nbE/9GP5T76Kj30Fed/nHkTJ3QORRMQUTs4J6LNk
BD1i14MZMCn3UBZh8wX+d63xJHtfMvfac7L655GwLEnWW8JM8h8DDfRYM7JuEURG
pSbvoaygyvddT0FKRLcFGhfI7aBSWGcJH5rHdEcUQ+mnloD1RioQqTC+kxUSddJI
QNjgYivl9kwW9cJV1jzmKd8GQfg+j1X+jR9icNT5cacvclwnL0Mim0w/ZLfWQYmJ
q2ud+GS9+5RtPzWwHR60+Qs3dr8oQGh5wO12qUJ8d5MI+4YGWRjKRyYdio6g1Bhi
9WInD4va9cC7fw==
-----END CERTIFICATE-----

30
cmd/adapter/testdata/tlscert.crt vendored Normal file
View file

@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFLjCCAxYCCQDMlabDYYlDKzANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJV
UzENMAsGA1UECgwEVGVzdDEbMBkGA1UEAwwScHJvbWV0aGV1cy1hZGFwdGVyMR0w
GwYJKoZIhvcNAQkBFg50ZXN0QHRlc3QudGVzdDAgFw0yMTAyMjIyMDMwMTNaGA80
NzU5MDEyMDIwMzAxM1owWDELMAkGA1UEBhMCVVMxDTALBgNVBAoMBFRlc3QxGzAZ
BgNVBAMMEnByb21ldGhldXMtYWRhcHRlcjEdMBsGCSqGSIb3DQEJARYOdGVzdEB0
ZXN0LnRlc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDIJOO8apS3
84bssvnTVHp1VAiPg1tX+E6wPjayVPx+S4LgMKA4QM2kNKoPLQtvGV+lyqfhYp0H
cdGzCPVQ6aBlbHuOhInusJaXjgOlNTalThgigzky0t1jFxqaNjFtXqv6ME1Zcb9H
VrGMreEfNj8/Ijp7cfsBe7Jv2rBc06aidx9j66+oPTC98XcNnURUGO95UF8SjTQt
oi10m17uA7z/JUUSBvDJAg5Z62myZPU2stz38cuthROyEyXRBWimHh7bD17rwqhc
WRCfkFORWvwz9GMV5KfFCfWm2D2pm1f3ZWm5/FQbQrlxgxUagwDMoma+F6hQp84a
/sYPqqkWDRUK0NGZzWwxjfra8r8H2xFab+5ZFVr9+FhMgy6eelZ1JJc860s35Qpk
ZrSRH8RNMqLRG1cnDwHn9Md6joCZgLJhEW9L5xjpCVWkLXK59yA9ry5Jau9/2zDs
wlRzYI4TNazbVa84KliEjt3nZ6DgQh3PRtxHDrqJIQSSYu1MtUmPArLtEDDP/BqD
fGWCayc/SdxSWW9qU/aOq+D4KMKQXV44qc22f6rd/LKt/fcvDpbfcexXbeeNABQg
x1rAnhA8L/rYc4WTTbTrb8jwhaUoqJve6XOsHPVbk/L4CS9ReP1UwvhMM1C+Ast6
rr/a2bZkoMK+jmkA2QTwUsjvt/8G4dtVOwIDAQABMA0GCSqGSIb3DQEBCwUAA4IC
AQAvrWslCR21l8XRGI+l4z6bpZ7+089KQRemYcHWOKZ/nDTYcydDWQMdMtDZS43d
B2Wuu4UfOnK27YwuP4Ojf2hAzeaDBgv7xOcKRZ1K+zOCm+VqbtWV49c/Ow0Rc5KU
N7rApohdpXeJBp5TB1qQJsKcBv3gveLAivCFTeD0LiLMVdxjRRl9ZbMXtD3PABDC
KKFtE/n2MV/0wroMD9hHs9LckcNjHSrIFaQEy9cESn8q3kngFf3wvc2oM77yCOZ1
5y0AN+9ZXyMHHlMjye7GuW0Mpiwo1O4tW2brC0boqSmvSFNW9KRogKvu6Oij9Pm6
jJpuUsM0KOnID8m9jJ+Xb+DGC9cgLGHRJc+zw74X2KMQnH4/pZDNbIGG7d8xEoPn
RS/EbCoALmUbI2kqflVN88kN4ZUchsoHly5gIdidfo9yjeOihTF0xEEou/tzGW+K
AYxwy9uIYhz4lmH894H5nqJWPY/aLxD4M9nFW0yxczCQ8tpjwVYmP3/dCKp1IUXy
0h9TjyBRPv9O3JrTLTYBPLisLNqiU+YOZM6wgqmZTPtTCxKMmNlhGWKa8prAhMdb
GRxwkO6ylyL/j3J3HgcDHEC22/685L21HVFv8z/DMuj/eba4yyn1FBVXOU9hgLWS
LVLoVFFp7RaGSIECcqTyXldoZZpZrA89XDVuqSvHDiCOrg==
-----END CERTIFICATE-----

52
cmd/adapter/testdata/tlskey.key vendored Normal file
View file

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDIJOO8apS384bs
svnTVHp1VAiPg1tX+E6wPjayVPx+S4LgMKA4QM2kNKoPLQtvGV+lyqfhYp0HcdGz
CPVQ6aBlbHuOhInusJaXjgOlNTalThgigzky0t1jFxqaNjFtXqv6ME1Zcb9HVrGM
reEfNj8/Ijp7cfsBe7Jv2rBc06aidx9j66+oPTC98XcNnURUGO95UF8SjTQtoi10
m17uA7z/JUUSBvDJAg5Z62myZPU2stz38cuthROyEyXRBWimHh7bD17rwqhcWRCf
kFORWvwz9GMV5KfFCfWm2D2pm1f3ZWm5/FQbQrlxgxUagwDMoma+F6hQp84a/sYP
qqkWDRUK0NGZzWwxjfra8r8H2xFab+5ZFVr9+FhMgy6eelZ1JJc860s35QpkZrSR
H8RNMqLRG1cnDwHn9Md6joCZgLJhEW9L5xjpCVWkLXK59yA9ry5Jau9/2zDswlRz
YI4TNazbVa84KliEjt3nZ6DgQh3PRtxHDrqJIQSSYu1MtUmPArLtEDDP/BqDfGWC
ayc/SdxSWW9qU/aOq+D4KMKQXV44qc22f6rd/LKt/fcvDpbfcexXbeeNABQgx1rA
nhA8L/rYc4WTTbTrb8jwhaUoqJve6XOsHPVbk/L4CS9ReP1UwvhMM1C+Ast6rr/a
2bZkoMK+jmkA2QTwUsjvt/8G4dtVOwIDAQABAoICAQCFd9RG+exjH4uCnXfsbhGb
3IY47igj6frPnS1sjzAyKLkGOGcgHFcGgfhGVouhcxJNxW9e5hxBsq1c70RoyOOl
v0pGKCyzeB90wce8jFf8tK9zlH64XdY1FlsvK6Sagt+84Ck01J3yPOX6IppV7h8P
Qwws9j2lJ5A+919VB++/uCC+yZVCZEv03um9snq2ekp4ZBiCjpeVNumJMXOE1glb
PMdq1iYMZcqcPFkoFhtQdsbUsfJZrL0Nq6c0VJ8M6Fk7TGzIW+9aZiqnvd98t2go
XXkWSH148MNYmCvGx0lKOd7foF2WMFDqWbfhDiuiS0qoya3822qepff+ypgnlGHK
vr+9pLsWT7TG8pBfbXj47a7TwYAXkRMi+78vFQwoaeiKdehJM1YXZg9vBVS8BV3r
+0wYNE4WpdxUvX3aAnJO6ntRU6KCz3/D1+fxUT/w1rKX2Z1uTH5x2UxB6UUGDSF9
HiJfDp6RRtXHbQMR6uowM6UYBn0dl9Aso21oc2K4Gpx5QlsZaPi9M6BBMbPUhFcx
QH+w7fLmccwneJVGxjHkYOcLVLF7nuH5C2DsffrMubrgwuhSw2b8zy7ZpZ0eJ83D
CjJN9EgqwbmH0Or5N91YyVdR0Zm4EtODAo615O1kEMCKasKjpolOx/t9cgtbdkiq
pbLruOS+8jEG1erA7nYkQQKCAQEA4yba38hSkfIMUzfrlgF7AkXHbU4iINgKpHti
A9QrvEL9W4VHRiA5UTezzblyfMck9w/Hhx74pQjjGLj76L+8ZssCFI8ingNo3ItL
/AX3MN68NT4heiy8EvKRwRNWV05WEehZg9tTUKexIDRcDSr/9E+qG/cW5KOIQpYl
RIsKW2RUNFd3TVCQVUIzwe/0n6LuO2b7Btow+nfJ7U3mWQmHGYu7+SYjVlvIoQ68
jFGviGRineu/J7EiPND7qQzj78AtnXkULf+mjK2JdapRcn2EBNL34QepVCyjfXZf
QWm/ykI9nVOKRy1F38OhRHKrBICfWhN2Bgyvw3PPhGcb8EdknwKCAQEA4Y/2bpiz
S0H8LPUUsOLZWCadpp8yzvTerB/vuigkQiHM8w4QUwEpL2iXSF36MD8yV/c4ilVN
8m1p5prp1YtasTxsOWv7FDEs4oZfum1w3QsAvlrFRhctsACsZ1i4i3mvxQWJ955q
zZxs5vhO5CL24rVoQYGVQj/uCSHlyK7ko9AA8XkejTlZMJ5h0Mip+oWNxz3M/VTa
sJlYkQrbP0cWxCjKJLEmtVlVSCMeHoILGZzLcol6RVPbaAb57i27SRwY9YIFt1A+
OMpHFs4fgDa4A1IlobBwhhd1dAw3CL5QJN+ylDnBYsm1bwBRHx/AKUjpRv+7ZXQb
H9ngSivFHrXN5QKCAQBAqzUw9LUdO83qe0ck47MDiJ4oLlBlDVyqSz4yXNs+s8ux
nJYYDuCCkNstvJgtkfyiIenqPBUJ1yfgR/nf34ZhtXYYKE/wsIPQFhBB5ejkDuWC
OvgI8mdw9YItd7XjEThLzNx/P5fOpI823fE/BnjsMyn44DWyTiRi4KAnjXYbYsre
Q/CBIGiW/UwC8K+yKw6r9ruMzd2X0Ta5yq3Dt4Sw7ylK22LAGU1bHPjs8eyJZhr1
XsKDKFjY+55KGJNkFFBoPqpSFjByaI1z5FNfxwAo528Or8GzZyn8dBDWbKbfjFBC
VCBP90GnXOiytfqeQ4gaeuPlAQOhH3168mfv1kN9AoIBABOZzgFYVaRBjKdfeLfS
Tq7BVEvJY8HmN39fmxZjLJtukn/AhhygajLLdPH98KLGqxpHymsC9K4PYfd/GLjM
zkm+hW0L/BqKF2tr39+0aO1camkgPCpWE0tLE7A7XnYIUgTd8VpKMt/BKxl7FGfw
veF/gBrJJu5F3ep/PpeM0yOFDL/vFX+SLzTxXnClL1gsyOA6d5jACez0tmSMO/co
t0q+fKpploKFy8pj+tcN1+cW3/sJBU4G9nb4vDk9UhwNTAHxlYuTdoS61yidKtGa
b60iM1D0oyKT4Un/Ubz5xL8fjUYiKrLp8lE+Bs6clLdBtbvMtz0etMi0xy/K0+tS
Qx0CggEBALfe2TUfAt9aMqpcidaFwgNFTr61wgOeoLWLt559OeRIeZWKAEB81bnz
EJLxDF51Y2tLc/pEXrc0zJzzrFIfk/drYe0uD5RnJjRxE3+spwin6D32ZOZW3KSX
1zReW1On80o/LJU6nyDJrNJvay2eL9PyWi47nBdO7MRZi53im72BmmwxaAKXf40l
StykjloyFdI+eyGyQUqcs4nFHd3WWmV+lLIDhGDlF5EBUgueCJz1xO54oPj1PKGl
vDs7JXdJiS3HDf20GREGwvL1y1kewX+KqdO7aBZhLN3Rx/fZnS/UFC3xCtbikuG4
LeU1NmvuCRmWmrgEkqiKs3jgjbEPVQI=
-----END PRIVATE KEY-----

44
cmd/config-gen/main.go Normal file
View file

@ -0,0 +1,44 @@
package main
import (
"fmt"
"os"
"time"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v2"
"sigs.k8s.io/prometheus-adapter/cmd/config-gen/utils"
)
func main() {
var labelPrefix string
var rateInterval time.Duration
cmd := &cobra.Command{
Short: "Generate a config matching the legacy discovery rules",
Long: `Generate a config that produces the same functionality
as the legacy discovery rules. This includes discovering metrics and associating
resources according to the Kubernetes instrumention conventions and the cAdvisor
conventions, and auto-converting cumulative metrics into rate metrics.`,
RunE: func(c *cobra.Command, args []string) error {
cfg := utils.DefaultConfig(rateInterval, labelPrefix)
enc := yaml.NewEncoder(os.Stdout)
if err := enc.Encode(cfg); err != nil {
return err
}
return enc.Close()
},
}
cmd.Flags().StringVar(&labelPrefix, "label-prefix", "",
"Prefix to expect on labels referring to pod resources. For example, if the prefix is "+
"'kube_', any series with the 'kube_pod' label would be considered a pod metric")
cmd.Flags().DurationVar(&rateInterval, "rate-interval", 5*time.Minute,
"Period of time used to calculate rate metrics from cumulative metrics")
if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Unable to generate config: %v\n", err)
os.Exit(1)
}
}

View file

@ -0,0 +1,122 @@
package utils
import (
"fmt"
"time"
pmodel "github.com/prometheus/common/model"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/config"
)
// DefaultConfig returns a configuration equivalent to the former
// pre-advanced-config settings. This means that "normal" series labels
// will be of the form `<prefix><<.Resource>>`, cadvisor series will be
// of the form `container_`, and have the label `pod`. Any series ending
// in total will be treated as a rate metric.
func DefaultConfig(rateInterval time.Duration, labelPrefix string) *config.MetricsDiscoveryConfig {
return &config.MetricsDiscoveryConfig{
Rules: []config.DiscoveryRule{
// container seconds rate metrics
{
SeriesQuery: string(prom.MatchSeries("", prom.NameMatches("^container_.*"), prom.LabelNeq("container", "POD"), prom.LabelNeq("namespace", ""), prom.LabelNeq("pod", ""))),
Resources: config.ResourceMapping{
Overrides: map[string]config.GroupResource{
"namespace": {Resource: "namespace"},
"pod": {Resource: "pod"},
},
},
Name: config.NameMapping{Matches: "^container_(.*)_seconds_total$"},
MetricsQuery: fmt.Sprintf(`sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[%s])) by (<<.GroupBy>>)`, pmodel.Duration(rateInterval).String()),
},
// container rate metrics
{
SeriesQuery: string(prom.MatchSeries("", prom.NameMatches("^container_.*"), prom.LabelNeq("container", "POD"), prom.LabelNeq("namespace", ""), prom.LabelNeq("pod", ""))),
SeriesFilters: []config.RegexFilter{{IsNot: "^container_.*_seconds_total$"}},
Resources: config.ResourceMapping{
Overrides: map[string]config.GroupResource{
"namespace": {Resource: "namespace"},
"pod": {Resource: "pod"},
},
},
Name: config.NameMapping{Matches: "^container_(.*)_total$"},
MetricsQuery: fmt.Sprintf(`sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[%s])) by (<<.GroupBy>>)`, pmodel.Duration(rateInterval).String()),
},
// container non-cumulative metrics
{
SeriesQuery: string(prom.MatchSeries("", prom.NameMatches("^container_.*"), prom.LabelNeq("container", "POD"), prom.LabelNeq("namespace", ""), prom.LabelNeq("pod", ""))),
SeriesFilters: []config.RegexFilter{{IsNot: "^container_.*_total$"}},
Resources: config.ResourceMapping{
Overrides: map[string]config.GroupResource{
"namespace": {Resource: "namespace"},
"pod": {Resource: "pod"},
},
},
Name: config.NameMapping{Matches: "^container_(.*)$"},
MetricsQuery: `sum(<<.Series>>{<<.LabelMatchers>>,container!="POD"}) by (<<.GroupBy>>)`,
},
// normal non-cumulative metrics
{
SeriesQuery: string(prom.MatchSeries("", prom.LabelNeq(fmt.Sprintf("%snamespace", labelPrefix), ""), prom.NameNotMatches("^container_.*"))),
SeriesFilters: []config.RegexFilter{{IsNot: ".*_total$"}},
Resources: config.ResourceMapping{
Template: fmt.Sprintf("%s<<.Resource>>", labelPrefix),
},
MetricsQuery: "sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)",
},
// normal rate metrics
{
SeriesQuery: string(prom.MatchSeries("", prom.LabelNeq(fmt.Sprintf("%snamespace", labelPrefix), ""), prom.NameNotMatches("^container_.*"))),
SeriesFilters: []config.RegexFilter{{IsNot: ".*_seconds_total"}},
Name: config.NameMapping{Matches: "^(.*)_total$"},
Resources: config.ResourceMapping{
Template: fmt.Sprintf("%s<<.Resource>>", labelPrefix),
},
MetricsQuery: fmt.Sprintf("sum(rate(<<.Series>>{<<.LabelMatchers>>}[%s])) by (<<.GroupBy>>)", pmodel.Duration(rateInterval).String()),
},
// seconds rate metrics
{
SeriesQuery: string(prom.MatchSeries("", prom.LabelNeq(fmt.Sprintf("%snamespace", labelPrefix), ""), prom.NameNotMatches("^container_.*"))),
Name: config.NameMapping{Matches: "^(.*)_seconds_total$"},
Resources: config.ResourceMapping{
Template: fmt.Sprintf("%s<<.Resource>>", labelPrefix),
},
MetricsQuery: fmt.Sprintf("sum(rate(<<.Series>>{<<.LabelMatchers>>}[%s])) by (<<.GroupBy>>)", pmodel.Duration(rateInterval).String()),
},
},
ResourceRules: &config.ResourceRules{
CPU: config.ResourceRule{
ContainerQuery: fmt.Sprintf("sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>}[%s])) by (<<.GroupBy>>)", pmodel.Duration(rateInterval).String()),
NodeQuery: fmt.Sprintf("sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>, id='/'}[%s])) by (<<.GroupBy>>)", pmodel.Duration(rateInterval).String()),
Resources: config.ResourceMapping{
Overrides: map[string]config.GroupResource{
"namespace": {Resource: "namespace"},
"pod": {Resource: "pod"},
"instance": {Resource: "node"},
},
},
ContainerLabel: fmt.Sprintf("%scontainer", labelPrefix),
},
Memory: config.ResourceRule{
ContainerQuery: "sum(container_memory_working_set_bytes{<<.LabelMatchers>>}) by (<<.GroupBy>>)",
NodeQuery: "sum(container_memory_working_set_bytes{<<.LabelMatchers>>,id='/'}) by (<<.GroupBy>>)",
Resources: config.ResourceMapping{
Overrides: map[string]config.GroupResource{
"namespace": {Resource: "namespace"},
"pod": {Resource: "pod"},
"instance": {Resource: "node"},
},
},
ContainerLabel: fmt.Sprintf("%scontainer", labelPrefix),
},
Window: pmodel.Duration(rateInterval),
},
}
}

3
code-of-conduct.md Normal file
View file

@ -0,0 +1,3 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View file

@ -1,4 +0,0 @@
FROM BASEIMAGE
COPY adapter /
USER 1001:1001
ENTRYPOINT ["/adapter"]

View file

@ -1,13 +1,11 @@
Example Deployment Example Deployment
================== ==================
1. Make sure you've built the included Dockerfile with `make docker-build`. The image should be tagged as `directxman12/k8s-prometheus-adapter:latest`. 1. Make sure you've built the included Dockerfile with `TAG=latest make container`. The image should be tagged as `registry.k8s.io/prometheus-adapter/staging-prometheus-adapter:latest`.
2. Create a secret called `cm-adapter-serving-certs` with two values:
`serving.crt` and `serving.key`. For more information on how to
generate these certificates, see the [auth concepts
documentation](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/auth.md)
in the apiserver-builder repository.
3. `kubectl create -f example-deployment.yaml`, modifying as necessary to 2. `kubectl create namespace monitoring` to ensure that the namespace that we're installing
point to your prometheus server. the custom metrics adapter in exists.
3. `kubectl create -f manifests/`, modifying the Deployment as necessary to
point to your Prometheus server, and the ConfigMap to contain your desired
metrics discovery configuration.

View file

@ -1,156 +0,0 @@
kind: Namespace
apiVersion: v1
metadata:
name: custom-metrics
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: custom-metrics-apiserver
namespace: custom-metrics
---
apiVersion: rbac.authorization.k8s.io/v1beta1
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: custom-metrics-apiserver
namespace: custom-metrics
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: custom-metrics-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: custom-metrics-apiserver
namespace: custom-metrics
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: custom-metrics-resource-reader
rules:
- apiGroups:
- ""
resources:
- namespaces
- pods
- services
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: custom-metrics-resource-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: custom-metrics-resource-reader
subjects:
- kind: ServiceAccount
name: custom-metrics-apiserver
namespace: custom-metrics
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: custom-metrics-apiserver
name: custom-metrics-apiserver
spec:
replicas: 1
selector:
matchLabels:
app: custom-metrics-apiserver
template:
metadata:
labels:
app: custom-metrics-apiserver
name: custom-metrics-apiserver
spec:
serviceAccountName: custom-metrics-apiserver
containers:
- name: custom-metrics-apiserver
image: directxman12/k8s-prometheus-adapter
args:
- /adapter
- --secure-port=6443
- --tls-cert-file=/var/run/serving-cert/serving.crt
- --tls-private-key-file=/var/run/serving-cert/serving.key
- --logtostderr=true
- --prometheus-url=http://prometheus.prom.svc:9090/
- --metrics-relist-interval=30s
- --rate-interval=30s
- --v=10
ports:
- containerPort: 6443
volumeMounts:
- mountPath: /var/run/serving-cert
name: volume-serving-cert
readOnly: true
volumes:
- name: volume-serving-cert
secret:
secretName: cm-adapter-serving-certs
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: custom-metrics
spec:
ports:
- port: 443
targetPort: 6443
selector:
app: custom-metrics-apiserver
---
apiVersion: apiregistration.k8s.io/v1beta1
kind: APIService
metadata:
name: v1alpha1.custom-metrics.metrics.k8s.io
spec:
insecureSkipTLSVerify: true
group: custom-metrics.metrics.k8s.io
priority: 150
service:
name: api
namespace: custom-metrics
version: v1alpha1
---
# Make a ClusterRole so that the HPA controller is able to read the custom metrics this adapter provides
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: custom-metrics-server-resources
rules:
- apiGroups:
- custom-metrics.metrics.k8s.io
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: hpa-controller-custom-metrics
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: custom-metrics-server-resources
subjects:
- kind: ServiceAccount
name: horizontal-pod-autoscaler
namespace: kube-system

View file

@ -0,0 +1,17 @@
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: prometheus-adapter
namespace: monitoring
version: v1beta1
versionPriority: 100

View file

@ -0,0 +1,22 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
name: system:aggregated-metrics-reader
namespace: monitoring
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch

View file

@ -0,0 +1,17 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: resource-metrics:system:auth-delegator
namespace: monitoring
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: prometheus-adapter
namespace: monitoring

View file

@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: hpa-controller-custom-metrics
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: custom-metrics-server-resources
subjects:
- kind: ServiceAccount
name: horizontal-pod-autoscaler
namespace: kube-system

View file

@ -0,0 +1,17 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: prometheus-adapter
namespace: monitoring
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus-adapter
subjects:
- kind: ServiceAccount
name: prometheus-adapter
namespace: monitoring

View file

@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: resource-metrics-server-resources
rules:
- apiGroups:
- metrics.k8s.io
resources:
- '*'
verbs:
- '*'

View file

@ -0,0 +1,20 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: prometheus-adapter
rules:
- apiGroups:
- ""
resources:
- nodes
- namespaces
- pods
- services
verbs:
- get
- list
- watch

View file

@ -0,0 +1,53 @@
apiVersion: v1
data:
config.yaml: |-
"resourceRules":
"cpu":
"containerLabel": "container"
"containerQuery": |
sum by (<<.GroupBy>>) (
irate (
container_cpu_usage_seconds_total{<<.LabelMatchers>>,container!="",pod!=""}[4m]
)
)
"nodeQuery": |
sum by (<<.GroupBy>>) (
irate(
node_cpu_usage_seconds_total{<<.LabelMatchers>>}[4m]
)
)
"resources":
"overrides":
"namespace":
"resource": "namespace"
"node":
"resource": "node"
"pod":
"resource": "pod"
"memory":
"containerLabel": "container"
"containerQuery": |
sum by (<<.GroupBy>>) (
container_memory_working_set_bytes{<<.LabelMatchers>>,container!="",pod!=""}
)
"nodeQuery": |
sum by (<<.GroupBy>>) (
node_memory_working_set_bytes{<<.LabelMatchers>>}
)
"resources":
"overrides":
"node":
"resource": "node"
"namespace":
"resource": "namespace"
"pod":
"resource": "pod"
"window": "5m"
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: adapter-config
namespace: monitoring

View file

@ -0,0 +1,89 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: prometheus-adapter
namespace: monitoring
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
spec:
automountServiceAccountToken: true
containers:
- args:
- --cert-dir=/var/run/serving-cert
- --config=/etc/adapter/config.yaml
- --metrics-relist-interval=1m
- --prometheus-url=https://prometheus.monitoring.svc:9090/
- --secure-port=6443
- --tls-cipher-suites=TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA
image: registry.k8s.io/prometheus-adapter/prometheus-adapter:v0.12.0
livenessProbe:
failureThreshold: 5
httpGet:
path: /livez
port: https
scheme: HTTPS
initialDelaySeconds: 30
periodSeconds: 5
name: prometheus-adapter
ports:
- containerPort: 6443
name: https
readinessProbe:
failureThreshold: 5
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 30
periodSeconds: 5
resources:
requests:
cpu: 102m
memory: 180Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- mountPath: /tmp
name: tmpfs
readOnly: false
- mountPath: /var/run/serving-cert
name: volume-serving-cert
readOnly: false
- mountPath: /etc/adapter
name: config
readOnly: false
nodeSelector:
kubernetes.io/os: linux
securityContext: {}
serviceAccountName: prometheus-adapter
volumes:
- emptyDir: {}
name: tmpfs
- emptyDir: {}
name: volume-serving-cert
- configMap:
name: adapter-config
name: config

View file

@ -0,0 +1,21 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: prometheus-adapter
namespace: monitoring
spec:
egress:
- {}
ingress:
- {}
podSelector:
matchLabels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
policyTypes:
- Egress
- Ingress

View file

@ -0,0 +1,15 @@
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: prometheus-adapter
namespace: monitoring
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter

View file

@ -0,0 +1,17 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: resource-metrics-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: prometheus-adapter
namespace: monitoring

View file

@ -0,0 +1,10 @@
apiVersion: v1
automountServiceAccountToken: false
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: prometheus-adapter
namespace: monitoring

View file

@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter
app.kubernetes.io/version: 0.12.0
name: prometheus-adapter
namespace: monitoring
spec:
ports:
- name: https
port: 443
targetPort: 6443
selector:
app.kubernetes.io/component: metrics-adapter
app.kubernetes.io/name: prometheus-adapter

230
docs/config-walkthrough.md Normal file
View file

@ -0,0 +1,230 @@
Configuration Walkthroughs
==========================
*If you're looking for reference documentation on configuration, please
read the [configuration reference](/docs/config.md)*
Per-pod HTTP Requests
---------------------
### Background
*The [full walkthrough](/docs/walkthrough.md) sets up the background for
something like this*
Suppose we have some frontend webserver, and we're trying to write a
configuration for the Prometheus adapter so that we can autoscale it based
on the HTTP requests per second that it receives.
Before starting, we've gone and instrumented our frontend server with
a metric, `http_requests_total`. It is exposed with a single label,
`method`, breaking down the requests by HTTP verb.
We've configured our Prometheus to collect the metric, and it
adds the `kubernetes_namespace` and `kubernetes_pod_name` labels,
representing namespace and pod, respectively.
If we query Prometheus, we see series that look like
```
http_requests_total{method="GET",kubernetes_namespace="production",kubernetes_pod_name="frontend-server-abcd-0123"}
```
### Configuring the adapter
The adapter considers metrics in the following ways:
1. First, it discovers the metrics available (*Discovery*)
2. Then, it figures out which Kubernetes resources each metric is
associated with (*Association*)
3. Then, it figures out how it should expose them to the custom metrics
API (*Naming*)
4. Finally, it figures out how it should query Prometheus to get the
actual numbers (*Querying*)
We need to inform the adapter how it should perform each of these steps
for our metric, `http_requests_total`, so we'll need to add a new
***rule***. Each rule in the adapter encodes these steps. Let's add a new
one to our configuration:
```yaml
rules:
- {}
```
If we want to find all `http_requests_total` series ourselves in the
Prometheus dashboard, we'd write
`http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}` to
find all `http_requests_total` series that were associated with
a namespace and pod.
We can add this to our rule in the `seriesQuery` field, to tell the
adapter how *discover* the right series itself:
```yaml
rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
```
Next, we'll need to tell the adapter how to figure out which Kubernetes
resources are associated with the metric. We've already said that
`kubernetes_namespace` represents the namespace name, and
`kubernetes_pod_name` represents the pod name. Since these names don't
quite follow a consistent pattern, we use the `overrides` section of the
`resources` field in our rule:
```yaml
rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
resources:
overrides:
kubernetes_namespace: {resource: "namespace"}
kubernetes_pod_name: {resource: "pod"}
```
This says that each label represents its corresponding resource. Since the
resources are in the "core" kubernetes API, we don't need to specify
a group. The adapter will automatically take care of pluralization, so we
can specify either `pod` or `pods`, just the same way as in `kubectl get`.
The resources can be any resource available in your kubernetes cluster, as
long as you've got a corresponding label.
If our labels followed a consistent pattern, like `kubernetes_<resource>`,
we could specify `resources: {template: "kubernetes_<<.Resource>>"}`
instead of specifying an override for each resource. If you want to see
all resources currently available in your cluster, you can use the
`kubectl api-resources` command (but the list of available resources can
change as you add or remove CRDs or aggregated API servers). For more
information on resources, see [Kinds, Resources, and
Scopes](https://github.com/kubernetes-sigs/custom-metrics-apiserver/blob/master/docs/getting-started.md#kinds-resources-and-scopes)
in the custom-metrics-apiserver boilerplate guide.
Now, cumulative metrics (like those that end in `_total`) aren't
particularly useful for autoscaling, so we want to convert them to rate
metrics in the API. We'll call the rate version of our metric
`http_requests_per_second`. We can use the `name` field to tell the
adapter about that:
```yaml
rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
resources:
overrides:
kubernetes_namespace: {resource: "namespace"}
kubernetes_pod_name: {resource: "pod"}
name:
matches: "^(.*)_total"
as: "${1}_per_second"
```
Here, we've said that we should take the name matching
`<something>_total`, and turning it into `<something>_per_second`.
Finally, we need to tell the adapter how to actually query Prometheus to
get some numbers. Since we want a rate, we might write:
`sum(rate(http_requests_total{kubernetes_namespace="production",kubernetes_pod_name=~"frontend-server-abcd-0123|fronted-server-abcd-4567"}) by (kubernetes_pod_name)`,
which would get us the total requests per second for each pod, summed across verbs.
We can write something similar in the adapter, using the `metricsQuery`
field:
```yaml
rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
resources:
overrides:
kubernetes_namespace: {resource: "namespace"}
kubernetes_pod_name: {resource: "pod"}
name:
matches: "^(.*)_total"
as: "${1}_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
```
The adapter will automatically fill in the right series name, label
matchers, and group-by clause, depending on what we put into the API.
Since we're only working with a single metric anyway, we could replace
`<<.Series>>` with `http_requests_total`.
Now, if we run an instance of the Prometheus adapter with this
configuration, we should see discovery information at
`$KUBERNETES/apis/custom.metrics.k8s.io/v1beta1/` of
```json
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "custom.metrics.k8s.io/v1beta1",
"resources": [
{
"name": "pods/http_requests_total",
"singularName": "",
"namespaced": true,
"kind": "MetricValueList",
"verbs": ["get"]
},
{
"name": "namespaces/http_requests_total",
"singularName": "",
"namespaced": false,
"kind": "MetricValueList",
"verbs": ["get"]
}
]
}
```
Notice that we get an entry for both "pods" and "namespaces" -- the
adapter exposes the metric on each resource that we've associated the
metric with (and all namespaced resources must be associated with
a namespace), and will fill in the `<<.GroupBy>>` section with the
appropriate label depending on which we ask for.
We can now connect to
`$KUBERNETES/apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/http_requests_per_second`,
and we should see
```json
{
"kind": "MetricValueList",
"apiVersion": "custom.metrics.k8s.io/v1beta1",
"metadata": {
"selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/http_requests_per_second",
},
"items": [
{
"describedObject": {
"kind": "Pod",
"name": "frontend-server-abcd-0123",
"apiVersion": "/__internal",
},
"metricName": "http_requests_per_second",
"timestamp": "2018-08-07T17:45:22Z",
"value": "16m"
},
{
"describedObject": {
"kind": "Pod",
"name": "frontend-server-abcd-4567",
"apiVersion": "/__internal",
},
"metricName": "http_requests_per_second",
"timestamp": "2018-08-07T17:45:22Z",
"value": "22m"
}
]
}
```
This says that our server pods are receiving 16 and 22 milli-requests per
second (depending on the pod), which is 0.016 and 0.022 requests per
second, written out as a decimal. That's about what we'd expect with
little-to-no traffic except for the Prometheus scrape.
If we added some traffic to our pods, we might see `1` or `20` instead of
`16m`, which would be `1` or `20` requests per second. We might also see
`20500m`, which would mean 20500 milli-requests per second, or 20.5
requests per second in decimal form.

215
docs/config.md Normal file
View file

@ -0,0 +1,215 @@
Metrics Discovery and Presentation Configuration
================================================
*If you want a full walkthrough of configuring the adapter for a sample
metric, please read the [configuration
walkthrough](/docs/config-walkthrough.md)*
The adapter determines which metrics to expose, and how to expose them,
through a set of "discovery" rules. Each rule is executed independently
(so make sure that your rules are mutually exclusive), and specifies each
of the steps the adapter needs to take to expose a metric in the API.
Each rule can be broken down into roughly four parts:
- *Discovery*, which specifies how the adapter should find all Prometheus
metrics for this rule.
- *Association*, which specifies how the adapter should determine which
Kubernetes resources a particular metric is associated with.
- *Naming*, which specifies how the adapter should expose the metric in
the custom metrics API.
- *Querying*, which specifies how a request for a particular metric on one
or more Kubernetes objects should be turned into a query to Prometheus.
A more comprehensive configuration file can be found in
[sample-config.yaml](sample-config.yaml), but a basic config with one rule
might look like:
```yaml
rules:
# this rule matches cumulative cAdvisor metrics measured in seconds
- seriesQuery: '{__name__=~"^container_.*",container!="POD",namespace!="",pod!=""}'
resources:
# skip specifying generic resource<->label mappings, and just
# attach only pod and namespace resources by mapping label names to group-resources
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
# specify that the `container_` and `_seconds_total` suffixes should be removed.
# this also introduces an implicit filter on metric family names
name:
# we use the value of the capture group implicitly as the API name
# we could also explicitly write `as: "$1"`
matches: "^container_(.*)_seconds_total$"
# specify how to construct a query to fetch samples for a given series
# This is a Go template where the `.Series` and `.LabelMatchers` string values
# are available, and the delimiters are `<<` and `>>` to avoid conflicts with
# the prometheus query language
metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[2m])) by (<<.GroupBy>>)"
```
Discovery
---------
Discovery governs the process of finding the metrics that you want to
expose in the custom metrics API. There are two fields that factor into
discovery: `seriesQuery` and `seriesFilters`.
`seriesQuery` specifies Prometheus series query (as passed to the
`/api/v1/series` endpoint in Prometheus) to use to find some set of
Prometheus series. The adapter will strip the label values from this
series, and then use the resulting metric-name-label-names combinations
later on.
In many cases, `seriesQuery` will be sufficient to narrow down the list of
Prometheus series. However, sometimes (especially if two rules might
otherwise overlap), it's useful to do additional filtering on metric
names. In this case, `seriesFilters` can be used. After the list of
series is returned from `seriesQuery`, each series has its metric name
filtered through any specified filters.
Filters may be either:
- `is: <regex>`, which matches any series whose name matches the specified
regex.
- `isNot: <regex>`, which matches any series whose name does not match the
specified regex.
For example:
```yaml
# match all cAdvisor metrics that aren't measured in seconds
seriesQuery: '{__name__=~"^container_.*_total",container!="POD",namespace!="",pod!=""}'
seriesFilters:
- isNot: "^container_.*_seconds_total"
```
Association
-----------
Association governs the process of figuring out which Kubernetes resources
a particular metric could be attached to. The `resources` field controls
this process.
There are two ways to associate resources with a particular metric. In
both cases, the value of the label becomes the name of the particular
object.
One way is to specify that any label name that matches some particular
pattern refers to some group-resource based on the label name. This can
be done using the `template` field. The pattern is specified as a Go
template, with the `Group` and `Resource` fields representing group and
resource. You don't necessarily have to use the `Group` field (in which
case the group is guessed by the system). For instance:
```yaml
# any label `kube_<group>_<resource>` becomes <group>.<resource> in Kubernetes
resources:
template: "kube_<<.Group>>_<<.Resource>>"
```
The other way is to specify that some particular label represents some
particular Kubernetes resource. This can be done using the `overrides`
field. Each override maps a Prometheus label to a Kubernetes
group-resource. For instance:
```yaml
# the microservice label corresponds to the apps.deployment resource
resources:
overrides:
microservice: {group: "apps", resource: "deployment"}
```
These two can be combined, so you can specify both a template and some
individual overrides.
The resources mentioned can be any resource available in your kubernetes
cluster, as long as you've got a corresponding label.
Naming
------
Naming governs the process of converting a Prometheus metric name into
a metric in the custom metrics API, and vice versa. It's controlled by
the `name` field.
Naming is controlled by specifying a pattern to extract an API name from
a Prometheus name, and potentially a transformation on that extracted
value.
The pattern is specified in the `matches` field, and is just a regular
expression. If not specified, it defaults to `.*`.
The transformation is specified by the `as` field. You can use any
capture groups defined in the `matches` field. If the `matches` field
doesn't contain capture groups, the `as` field defaults to `$0`. If it
contains a single capture group, the `as` field defautls to `$1`.
Otherwise, it's an error not to specify the as field.
For example:
```yaml
# match turn any name <name>_total to <name>_per_second
# e.g. http_requests_total becomes http_requests_per_second
name:
matches: "^(.*)_total$"
as: "${1}_per_second"
```
Querying
--------
Querying governs the process of actually fetching values for a particular
metric. It's controlled by the `metricsQuery` field.
The `metricsQuery` field is a Go template that gets turned into
a Prometheus query, using input from a particular call to the custom
metrics API. A given call to the custom metrics API is distilled down to
a metric name, a group-resource, and one or more objects of that
group-resource. These get turned into the following fields in the
template:
- `Series`: the metric name
- `LabelMatchers`: a comma-separated list of label matchers matching the
given objects. Currently, this is the label for the particular
group-resource, plus the label for namespace, if the group-resource is
namespaced.
- `GroupBy`: a comma-separated list of labels to group by. Currently,
this contains the group-resource label used in `LabelMatchers`.
For instance, suppose we had a series `http_requests_total` (exposed as
`http_requests_per_second` in the API) with labels `service`, `pod`,
`ingress`, `namespace`, and `verb`. The first four correspond to
Kubernetes resources. Then, if someone requested the metric
`pods/http_request_per_second` for the pods `pod1` and `pod2` in the
`somens` namespace, we'd have:
- `Series: "http_requests_total"`
- `LabelMatchers: "pod=~\"pod1|pod2",namespace="somens"`
- `GroupBy`: `pod`
Additionally, there are two advanced fields that are "raw" forms of other
fields:
- `LabelValuesByName`: a map mapping the labels and values from the
`LabelMatchers` field. The values are pre-joined by `|`
(for used with the `=~` matcher in Prometheus).
- `GroupBySlice`: the slice form of `GroupBy`.
In general, you'll probably want to use the `Series`, `LabelMatchers`, and
`GroupBy` fields. The other two are for advanced usage.
The query is expected to return one value for each object requested. The
adapter will use the labels on the returned series to associate a given
series back to its corresponding object.
For example:
```yaml
# convert cumulative cAdvisor metrics into rates calculated over 2 minutes
metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[2m])) by (<<.GroupBy>>)"
```

84
docs/externalmetrics.md Normal file
View file

@ -0,0 +1,84 @@
External Metrics
===========
It's possible to configure [Autoscaling on metrics not related to Kubernetes objects](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-metrics-not-related-to-kubernetes-objects) in Kubernetes. This is done with a special `External Metrics` system. Using external metrics in Kubernetes with the adapter requires you to configure special `external` rules in the configuration.
The configuration for `external` metrics rules is almost identical to the normal `rules`:
```yaml
externalRules:
- seriesQuery: '{__name__="queue_consumer_lag",name!=""}'
metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (name)
resources:
overrides: { namespace: {resource: "namespace"} }
```
Namespacing
-----------
All Kubernetes Horizontal Pod Autoscaler (HPA) resources are namespaced. And when you create an HPA that
references an external metric the adapter will automatically add a `namespace` label to the `seriesQuery` you have configured.
This is done because the External Merics API Specification *requires* a namespace component in the URL:
```shell
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/queue_consumer_lag"
```
Cross-Namespace or No Namespace Queries
---------------------------------------
A semi-common scenario is to have a `workload` in one namespace that needs to scale based on a metric from a different namespace. This is normally not
possible with `external` rules because the `namespace` label is set to match that of the source `workload`.
However, you can explicitly disable the automatic add of the HPA namepace to the query, and instead opt to not set a namespace at all, or to target a different namespace.
This is done by setting `namespaced: false` in the `resources` section of the `external` rule:
```yaml
# rules: ...
externalRules:
- seriesQuery: '{__name__="queue_depth",name!=""}'
metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (name)
resources:
namespaced: false
```
Given the `external` rules defined above any `External` metric query for `queue_depth` will simply ignore the source `namespace` of the HPA. This allows you to explicilty not put a namespace into an external query, or to set the namespace to one that might be different from that of the HPA.
```yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: external-queue-scaler
# the HPA and scaleTargetRef must exist in a namespace
namespace: default
annotations:
# The "External" metric below targets a metricName that has namespaced=false
# and this allows the metric to explicitly query a different
# namespace than that of the HPA and scaleTargetRef
autoscaling.alpha.kubernetes.io/metrics: |
[
{
"type": "External",
"external": {
"metricName": "queue_depth",
"metricSelector": {
"matchLabels": {
"namespace": "queue",
"name": "my-sample-queue"
}
},
"targetAverageValue": "50"
}
}
]
spec:
maxReplicas: 5
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
```

View file

@ -1,44 +0,0 @@
Metrics Format and Presentation
===============================
The adapter gathers the names of available metrics from Prometheus at the
specified interval. Only metrics of the following forms are considered:
- "container" metrics (cAdvisor container metrics): series with a name
starting with `container_`, as well as non-empty `namespace` and
`pod_name` labels.
- "namespaced" metrics (metrics describing namespaced Kubernetes objects):
series with non-empty namespace labels (which don't start with
`container_`).
*Note*: Currently, metrics on non-namespaced objects (besides namespaces
themselves) are not supported.
Metrics in Prometheus are converted in the custom-metrics-API metrics as
follows:
1. The metric name and type are decided:
- For container metrics, the `container_` prefix is removed
- If the metric has the `_total` suffix, it is marked as a counter
metric, and the suffix is removed
- If the metric has the `_seconds_total` suffix, it is marked as
a seconds counter metric, and the suffix is removed.
- If the metric has none of the above suffixes, is is marked as a gauge
metric, and the metric name is used as-is
2. Relevant resources are associated with the metric:
- container metrics are associated with pods only
- for non-container metrics, each label on the series is considered. If
that label represents a resource (without the group) available on the
server, the metric is associated with that resource. A metric may be
associated with multiple resources.
When retrieving counter and seconds-counter metrics, the adapter requests
the metrics as a rate over the configured amount of time. For metrics
with multiple associated resources, the adapter requests the metric
aggregated over all non-requested metrics.
The adapter does not consider resources consumed by the "POD" container,
which exists as part of all Kubernetes pods running in Docker simply
supports the existance of the pod's shared network namespace.

83
docs/sample-config.yaml Normal file
View file

@ -0,0 +1,83 @@
rules:
# Each rule represents a some naming and discovery logic.
# Each rule is executed independently of the others, so
# take care to avoid overlap. As an optimization, rules
# with the same `seriesQuery` but different
# `name` or `seriesFilters` will use only one query to
# Prometheus for discovery.
# some of these rules are taken from the "default" configuration, which
# can be found in pkg/config/default.go
# this rule matches cumulative cAdvisor metrics measured in seconds
- seriesQuery: '{__name__=~"^container_.*",container!="POD",namespace!="",pod!=""}'
resources:
# skip specifying generic resource<->label mappings, and just
# attach only pod and namespace resources by mapping label names to group-resources
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
# specify that the `container_` and `_seconds_total` suffixes should be removed.
# this also introduces an implicit filter on metric family names
name:
# we use the value of the capture group implicitly as the API name
# we could also explicitly write `as: "$1"`
matches: "^container_(.*)_seconds_total$"
# specify how to construct a query to fetch samples for a given series
# This is a Go template where the `.Series` and `.LabelMatchers` string values
# are available, and the delimiters are `<<` and `>>` to avoid conflicts with
# the prometheus query language
metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[2m])) by (<<.GroupBy>>)"
# this rule matches cumulative cAdvisor metrics not measured in seconds
- seriesQuery: '{__name__=~"^container_.*_total",container!="POD",namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
seriesFilters:
# since this is a superset of the query above, we introduce an additional filter here
- isNot: "^container_.*_seconds_total$"
name: {matches: "^container_(.*)_total$"}
metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[2m])) by (<<.GroupBy>>)"
# this rule matches cumulative non-cAdvisor metrics
- seriesQuery: '{namespace!="",__name__!="^container_.*"}'
name: {matches: "^(.*)_total$"}
resources:
# specify an a generic mapping between resources and labels. This
# is a template, like the `metricsQuery` template, except with the `.Group`
# and `.Resource` strings available. It will also be used to match labels,
# so avoid using template functions which truncate the group or resource.
# Group will be converted to a form acceptible for use as a label automatically.
template: "<<.Resource>>"
# if we wanted to, we could also specify overrides here
metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[2m])) by (<<.GroupBy>>)"
# this rule matches only a single metric, explicitly naming it something else
# It's series query *must* return only a single metric family
- seriesQuery: 'cheddar{sharp="true"}'
# this metric will appear as "cheesy_goodness" in the custom metrics API
name: {as: "cheesy_goodness"}
resources:
overrides:
# this should still resolve in our cluster
brand: {group: "cheese.io", resource: "brand"}
metricsQuery: 'count(cheddar{sharp="true"})'
# external rules are not tied to a Kubernetes resource and can reference any metric
# https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-metrics-not-related-to-kubernetes-objects
externalRules:
- seriesQuery: '{__name__="queue_consumer_lag",name!=""}'
metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (name)
- seriesQuery: '{__name__="queue_depth",topic!=""}'
metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (name)
# Kubernetes metric queries include a namespace in the query by default
# but you can explicitly disable namespaces if needed with "namespaced: false"
# this is useful if you have an HPA with an external metric in namespace A
# but want to query for metrics from namespace B
resources:
namespaced: false
# TODO: should we be able to map to a constant instance of a resource
# (e.g. `resources: {constant: [{resource: "namespace", name: "kube-system"}}]`)?

View file

@ -8,7 +8,7 @@ metrics sourced from the adapter.
Prerequisites Prerequisites
------------- -------------
### Cluster Configuration ### ### Cluster Configuration
Before getting started, ensure that the main components of your Before getting started, ensure that the main components of your
cluster are configured for autoscaling on custom metrics. As of cluster are configured for autoscaling on custom metrics. As of
@ -20,361 +20,64 @@ Detailed instructions can be found in the Kubernetes documentation under
[Horizontal Pod [Horizontal Pod
Autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-custom-metrics). Autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-custom-metrics).
Make sure that you've properly configured Heapster with the `--api-server` Make sure that you've properly configured metrics-server (as default in
flag, otherwise enabling custom metrics autoscaling support with disable Kubernetes 1.9+), or enabling custom metrics autoscaling support will
CPU autoscaling support. disable CPU autoscaling support.
### Binaries and Images ### Note that most of the API versions in this walkthrough target Kubernetes
1.9+. Note that current versions of the adapter *only* work with
Kubernetes 1.8+. Version 0.1.0 works with Kubernetes 1.7, but is
significantly different.
### Binaries and Images
In order to follow this walkthrough, you'll need container images for In order to follow this walkthrough, you'll need container images for
Prometheus and the custom metrics adapter. Prometheus and the custom metrics adapter.
Both can be found on Dockerhub under `prom/prometheus` and The [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator),
`directxman12/k8s-prometheus-adapter`, respectively. makes it easy to get up and running with Prometheus. This walkthrough
will assume you're planning on doing that -- if you've deployed it by hand
instead, you'll need to make a few adjustments to the way you expose
metrics to Prometheus.
If you're feeling adventurous, you can build the latest version of the The adapter has different images for each arch, which can be found at
custom metrics adapter by running `make docker-build`. `gcr.io/k8s-staging-prometheus-adapter/prometheus-adapter-${ARCH}`. For
instance, if you're on an x86_64 machine, use
`gcr.io/k8s-staging-prometheus-adapter/prometheus-adapter-amd64` image.
Launching Prometheus and the Adapter There is also an official multi arch image available at
------------------------------------ `registry.k8s.io/prometheus-adapter/prometheus-adapter:${VERSION}`.
In this walkthrough, it's assumed that you're deploying Prometheus into
its own namespace called `prom`. Most of the sample commands and files
are namespace-agnostic, but there are a few commands that rely on
namespace. If you're using a different namespace, simply substitute that
in for `prom` when it appears.
### Prometheus Configuration ### If you're feeling adventurous, you can build the latest version of
prometheus-adapter by running `make container` or get the latest image from the
staging registry `gcr.io/k8s-staging-prometheus-adapter/prometheus-adapter`.
If you've never deployed Prometheus before, you'll need an appropriate Special thanks to [@luxas](https://github.com/luxas) for providing the
Prometheus configuration. There's a extensive sample Prometheus demo application for this walkthrough.
configuration in the Prometheus repository
[here](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml).
Be sure to also read the Prometheus documentation on [configuring
Prometheus](https://prometheus.io/docs/operating/configuration/).
For the purposes of this walkthrough, you'll need the following The Scenario
configuration options to be set: ------------
<details> Suppose that you've written some new web application, and you know it's
the next best thing since sliced bread. It's ready to unveil to the
world... except you're not sure that just one instance will handle all the
traffic once it goes viral. Thankfully, you've got Kubernetes.
<summary>prom-cfg.yaml</summary> Deploy your app into your cluster, exposed via a service so that you can
send traffic to it and fetch metrics from it:
```yaml
# a short scrape interval means you can respond to changes in
# metrics more quickly
global:
scrape_interval: 15s
# you need a scrape configuration for scraping from pods
scrape_configs:
- job_name: 'kubernetes-pods'
# if you want to use metrics on jobs, set the below field to
# true to prevent Prometheus from setting the `job` label
# automatically.
honor_labels: false
kubernetes_sd_configs:
- role: pod
# skip verification so you can do HTTPS to pods
tls_config:
insecure_skip_verify: true
# make sure your labels are in order
relabel_configs:
# these labels tell Prometheus to automatically attach source
# pod and namespace information to each collected sample, so
# that they'll be exposed in the custom metrics API automatically.
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: pod
# these labels tell Prometheus to look for
# prometheus.io/{scrape,path,port} annotations to configure
# how to scrape
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (.+)
```
</details>
Place your full configuration (it might just be the above) in a file (call
it `prom-cfg.yaml`, for example), and then create a ConfigMap containing
it:
```shell
$ kubectl -n prom create configmap prometheus --from-file=prometheus.yml=prom-cfg.yaml
```
You'll be using this later when you launch Prometheus.
### Launching the Deployment ###
It's generally easiest to launch the adapter and Prometheus as two
containers in the same pod. You can use a deployment to manage your
adapter and Prometheus instance.
First, we'll create a ServiceAccount for the deployment to run as:
```yaml
$ kubectl -n prom create serviceaccount prom-cm-adapter
```
Start out with a fairly straightforward Prometheus deployment using the
ConfigMap from above, and proceed from there:
<details>
<summary>prom-adapter.deployment.yaml [Prometheus only]</summary>
```yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: prometheus
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
serviceAccountName: prom-cm-adapter
containers:
- image: prom/prometheus:v1.6.1
name: prometheus
args:
- -storage.local.retention=6h
- -storage.local.memory-chunks=500000
# point prometheus at the configuration that you mount in below
- -config.file=/etc/prometheus/prometheus.yml
ports:
# this port exposes the dashboard and the HTTP API
- containerPort: 9090
name: prom-web
protocol: TCP
volumeMounts:
# you'll mount your ConfigMap volume at /etc/prometheus
- mountPath: /etc/prometheus
name: prom-config
volumes:
# make your configmap available as a volume, so that you can
# mount in the Prometheus config from earlier
- name: config-volume
configMap:
name: prometheus
```
</details>
Save this file (for example, as `prom-adapter.deployment.yaml`). Now,
you'll need to modify the deployment to include the adapter.
The adapter has several options, most of which it shares with any other
standard Kubernetes addon API server. This means that you'll need proper
API server certificates for it to function properly. To learn more about
which certificates are needed, and what they mean, see [Concepts: Auth and
Certificates](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/auth.md).
Once you've generated the necessary certificates, you'll need to place
them in a secret. This walkthrough assumes that you've set up your
cluster to automatically inject client certificates and CA certificates
(see the above concepts documentation). Make sure you've granted the
default service account for your namespace permission to fetch the
authentication CA ConfigMap:
```shell
$ kubectl create rolebinding prom-ext-auth-reader --role="extension-apiserver-authentication-reader" --serviceaccount=prom:prom-cm-adapter
```
Then, store your serving certificates in a secret:
```shell
$ kubectl -n prom create secret tls serving-cm-adapter --cert=/path/to/cm-adapter/serving.crt --key=/path/to/cm-adapter/serving.key
```
Next, you'll need to make sure that the service account used to launch the
Deployment has permission to list resources in the cluster:
```shell
$ kubectl create clusterrole resource-lister --verb=list --resource="*"
$ kubectl create clusterrolebinding cm-adapter-resource-lister --clusterrole=resource-lister -- serviceaccount=prom:prom-cm-adapter
```
Finally, ensure the deployment has all the necessary permissions to
delegate authentication and authorization decisions to the main API
server. See [Concepts: Auth and
Certificates](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/auth.md)
for more information.
Next, amend the file above to run the adapter as well. You may need to
modify this part if you wish to inject the needed certificates a different
way.
<details>
<summary>prom-adapter.deployment.yaml [Adapter & Prometheus]</summary>
```yaml
...
spec:
containers:
...
- image: directxman12/k8s-prometheus-adapter
name: cm-adapter
args:
- --secure-port=6443
- --logtostderr=true
# use your serving certs
- --tls-cert-file=/var/run/serving-cert/tls.crt
- --tls-private-key-file=/var/run/serving-cert/tls.key
# Prometheus is running in the same pod, so you can say your Prometheus
# is at `localhost`
- --prometheus-url=http://localhost:9090
# relist available Prometheus metrics every 1m
- --metrics-relist-interval=1m
# calculate rates for cumulative metrics over 30s periods. This should be *at least*
# as much as your collection interval for Prometheus.
- --rate-interval=30s
# re-discover new available resource names every 10m. You probably
# won't need to have this be particularly often, but if you add
# additional addons to your cluster, the adapter will discover there
# existance at this interval
- --discovery-interval=10m
- --v=4
ports:
# this port exposes the custom metrics API
- containerPort: 6443
name: https
protocol: TCP
volumeMounts:
- mountPath: /var/run/serving-certs
name: serving-certs
readOnly: true
volumes:
...
- name: serving-certs
secret:
secretName: serving-cm-adapter
```
</details>
Next, create the deployment and expose it as a service, mapping port 443
to your pod's port 443, on which you've exposed the custom metrics API:
```shell
$ kubectl -n prom create -f prom-adapter.deployment.yaml
$ kubectl -n prom create service clusterip prometheus --tcp=443:6443
```
### Registering the API ###
Now that you have a running deployment of Prometheus and the adapter,
you'll need to register it as providing the
`custom-metrics.metrics.k8s.io/v1alpha` API.
For more information on how this works, see [Concepts:
Aggregation](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/aggregation.md).
You'll need to create an API registration record for the
`custom-metrics.metrics.k8s.io/v1alpha1` API. In order to do this, you'll
need the base64 encoded version of the CA certificate used to sign the
serving certificates you created above. If the CA certificate is stored
in `/tmp/ca.crt`, you can get the base64-encoded form like this:
```shell
$ base64 --w 0 < /tmp/ca.crt
```
Take the resulting value, and place it into the following file:
<details>
<summary>cm-registration.yaml</summary>
```yaml
apiVersion: apiregistration.k8s.io/v1beta1
kind: APIService
metadata:
name: v1alpha1.custom-metrics.metrics.k8s.io
spec:
# this tells the aggregator how to verify that your API server is
# actually who it claims to be
caBundle: <base-64-value-from-above>
# these specify which group and version you're registering the API
# server for
group: custom-metrics.metrics.k8s.io
version: v1alpha1
# these control how the aggregator prioritizes your registration.
# it's not particularly relevant in this case.
groupPriorityMinimum: 1000
versionPriority: 10
# finally, this points the aggregator at the service for your
# API server that you created
service:
name: prometheus
namespace: prom
```
</details>
Register that registration object with the aggregator:
```shell
$ kubectl -f cm-registration.yaml
```
### Double-Checking Your Work ###
With that all set, your custom metrics API should show up in discovery.
Try fetching the discovery information for it:
```shell
$ kubectl get --raw /apis/custom-metrics.metrics.k8s.io/v1alpha1
```
Since you don't have any metrics collected yet, you shouldn't see any
available resources, but the request should return successfully. Keep
this command in mind -- you'll want to use it later once you have a pod
producing custom metrics.
Collecting Application Metrics
------------------------------
Now that you have a working pipeline for ingesting application metrics,
you'll need an application that produces some metrics. Any application
which produces Prometheus-formatted metrics will do. For the purposes of
this walkthrough, try out [@luxas](https://github.com/luxas)'s simple HTTP
counter in the `luxas/autoscale-demo` image on Dockerhub:
<details> <details>
<summary>sample-app.deploy.yaml</summary> <summary>sample-app.deploy.yaml</summary>
```yaml ```yaml
apiVersion: apps/v1beta1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: sample-app name: sample-app
labels:
app: sample-app
spec: spec:
replicas: 1 replicas: 1
selector: selector:
@ -384,81 +87,73 @@ spec:
metadata: metadata:
labels: labels:
app: sample-app app: sample-app
annotations:
# based on your Prometheus config above, this tells prometheus
# to scrape this pod for metrics on port 8080 at "/metrics"
prometheus.io/scrape: true
prometheus.io/port: 8080
prometheus.io/path: "/metrics"
spec: spec:
containers: containers:
- image: luxas/autoscale-demo - image: luxas/autoscale-demo:v0.1.2
name: metrics-provider name: metrics-provider
ports: ports:
- name: http - name: http
port: 8080 containerPort: 8080
``` ```
</details> </details>
Create this deployment, and expose it so that you can easily trigger <details>
increases in metrics:
<summary>sample-app.service.yaml</summary>
```yaml ```yaml
$ kubectl create -f sample-app.deploy.yaml apiVersion: v1
$ kubectl create service clusterip sample-app --tcp=80:8080 kind: Service
metadata:
labels:
app: sample-app
name: sample-app
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
selector:
app: sample-app
type: ClusterIP
``` ```
This sample application provides some metrics on the number of HTTP </details>
requests it receives. Consider the metric `http_requests_total`. First,
check that it appears in discovery using the command from [Double-Checking
Yor Work](#double-checking-your-work). The cumulative Prometheus metric
`http_requests_total` should have become the custom-metrics-API rate
metric `pods/http_requests`. Check out its value:
```shell ```shell
$ kubectl get --raw "/apis/custom-metrics.metrics.k8s.io/v1alpha1/namespaces/default/pods/*/http_requests?selector=app%3Dsample-app" $ kubectl create -f sample-app.deploy.yaml
$ kubectl create -f sample-app.service.yaml
``` ```
It should be zero, since you're not currently accessing it. Now, create Now, check your app, which exposes metrics and counts the number of
a few requests with curl: accesses to the metrics page via the `http_requests_total` metric:
```shell ```shell
$ curl http://$(kubectl get service sample-app -o jsonpath='{ .spec.clusterIP }')/metrics $ curl http://$(kubectl get service sample-app -o jsonpath='{ .spec.clusterIP }')/metrics
``` ```
Try fetching the metrics again. You should see an increase in the rate Notice that each time you access the page, the counter goes up.
after the collection interval specified in your Prometheus configuration
has elapsed. If you leave it for a bit, the rate will go back down again.
Autoscaling Now, you'll want to make sure you can autoscale your application on that
----------- metric, so that you're ready for your launch. You can use
a HorizontalPodAutoscaler like this to accomplish the autoscaling:
Now that you have an application which produces custom metrics, you'll be
able to autoscale on it. As noted in the [HorizontalPodAutoscaler
walkthrough](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics),
there are three different types of metrics that the
HorizontalPodAutoscaler can handle.
In this walkthrough, you've exposed some metrics that can be consumed
using the `Pods` metric type.
Create a description for the HorizontalPodAutoscaler (HPA):
<details> <details>
<summary>sample-app-hpa.yaml</summary> <summary>sample-app.hpa.yaml</summary>
```yaml ```yaml
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2alpha1 apiVersion: autoscaling/v2
metadata: metadata:
name: sample-app name: sample-app
spec: spec:
scaleTargetRef: scaleTargetRef:
# point the HPA at the sample application # point the HPA at the sample application
# you created above # you created above
apiVersion: apps/v1beta1 apiVersion: apps/v1
kind: Deployment kind: Deployment
name: sample-app name: sample-app
# autoscale between 1 and 10 replicas # autoscale between 1 and 10 replicas
@ -470,33 +165,290 @@ spec:
- type: Pods - type: Pods
pods: pods:
# use the metric that you used above: pods/http_requests # use the metric that you used above: pods/http_requests
metricName: http_requests metric:
name: http_requests
# target 500 milli-requests per second, # target 500 milli-requests per second,
# which is 1 request every two seconds # which is 1 request every two seconds
targetAverageValue: 500m target:
type: Value
averageValue: 500m
``` ```
</details> </details>
Create the HorizontalPodAutoscaler with If you try creating that now (and take a look at your controller-manager
logs), you'll see that the that the HorizontalPodAutoscaler controller is
attempting to fetch metrics from
`/apis/custom.metrics.k8s.io/v1beta2/namespaces/default/pods/*/http_requests?selector=app%3Dsample-app`,
but right now, nothing's serving that API.
``` Before you can autoscale your application, you'll need to make sure that
$ kubectl create -f sample-app-hpa.yaml Kubernetes can read the metrics that your application exposes.
Launching Prometheus and the Adapter
------------------------------------
In order to expose metrics beyond CPU and memory to Kubernetes for
autoscaling, you'll need an "adapter" that serves the custom metrics API.
Since you've got Prometheus metrics, it makes sense to use the
Prometheus adapter to serve metrics out of Prometheus.
### Launching Prometheus
First, you'll need to deploy the Prometheus Operator. Check out the
[quick start
guide](https://github.com/prometheus-operator/prometheus-operator#quickstart)
for the Operator to deploy a copy of Prometheus.
This walkthrough assumes that Prometheus is deployed in the `monitoring`
namespace. Most of the sample commands and files are namespace-agnostic,
but there are a few commands or pieces of configuration that rely on that
namespace. If you're using a different namespace, simply substitute that
in for `monitoring` when it appears.
### Monitoring Your Application
In order to monitor your application, you'll need to set up
a ServiceMonitor pointing at the application. Assuming you've set up your
Prometheus instance to use ServiceMonitors with the `app: sample-app`
label, create a ServiceMonitor to monitor the app's metrics via the
service:
<details>
<summary>sample-app.monitor.yaml</summary>
```yaml
kind: ServiceMonitor
apiVersion: monitoring.coreos.com/v1
metadata:
name: sample-app
labels:
app: sample-app
spec:
selector:
matchLabels:
app: sample-app
endpoints:
- port: http
``` ```
Then, like before, make some requests to the sample app's service. If you </details>
describe the HPA, after the HPA sync interval has elapsed, you should see
the number of pods increase proportionally to the ratio between the actual
requests per second and your target of 1 request every 2 seconds.
You can examine the HPA with ```shell
$ kubectl create -f sample-app.monitor.yaml
```
Now, you should see your metrics (`http_requests_total`) appear in your Prometheus instance. Look
them up via the dashboard, and make sure they have the `namespace` and
`pod` labels. If not, check the labels on the service monitor match the ones on the Prometheus CRD.
### Launching the Adapter
Now that you've got a running copy of Prometheus that's monitoring your
application, you'll need to deploy the adapter, which knows how to
communicate with both Kubernetes and Prometheus, acting as a translator
between the two.
The [deploy/manifests](/deploy/manifests) directory contains the
appropriate files for creating the Kubernetes objects to deploy the
adapter.
See the [deployment README](/deploy/README.md) for more information about
the steps to deploy the adapter. Note that if you're deploying on
a non-x86_64 (amd64) platform, you'll need to change the `image` field in
the Deployment to be the appropriate image for your platform.
However an update to the adapter config is necessary in order to
expose custom metrics.
<details>
<summary>prom-adapter.config.yaml</summary>
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: adapter-config
namespace: monitoring
data:
config.yaml: |-
"rules":
- "seriesQuery": |
{namespace!="",__name__!~"^container_.*"}
"resources":
"template": "<<.Resource>>"
"name":
"matches": "^(.*)_total"
"as": ""
"metricsQuery": |
sum by (<<.GroupBy>>) (
irate (
<<.Series>>{<<.LabelMatchers>>}[1m]
)
)
```
</details>
```shell
$ kubectl apply -f prom-adapter.config.yaml
# Restart prom-adapter pods
$ kubectl rollout restart deployment prometheus-adapter -n monitoring
```
This adapter configuration should work for this walkthrough together with
a standard Prometheus Operator configuration, but if you've got custom
relabelling rules, or your labels above weren't exactly `namespace` and
`pod`, you may need to edit the configuration in the ConfigMap. The
[configuration walkthrough](/docs/config-walkthrough.md) provides an
overview of how configuration works.
### The Registered API
We also need to register the custom metrics API with the API aggregator (part of
the main Kubernetes API server). For that we need to create an APIService resource
<details>
<summary>api-service.yaml</summary>
```yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1beta2.custom.metrics.k8s.io
spec:
group: custom.metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: prometheus-adapter
namespace: monitoring
version: v1beta2
versionPriority: 100
```
</details>
```shell
$ kubectl create -f api-service.yaml
```
The API is registered as `custom.metrics.k8s.io/v1beta2`, and you can find
more information about aggregation at [Concepts:
Aggregation](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/aggregation.md).
### Double-Checking Your Work
With that all set, your custom metrics API should show up in discovery.
Try fetching the discovery information for it:
```shell
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta2
```
Since you've set up Prometheus to collect your app's metrics, you should
see a `pods/http_request` resource show up. This represents the
`http_requests_total` metric, converted into a rate, aggregated to have
one datapoint per pod. Notice that this translates to the same API that
our HorizontalPodAutoscaler was trying to use above.
You can check the value of the metric using `kubectl get --raw`, which
sends a raw GET request to the Kubernetes API server, automatically
injecting auth information:
```shell
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/default/pods/*/http_requests?selector=app%3Dsample-app"
```
Because of the adapter's configuration, the cumulative metric
`http_requests_total` has been converted into a rate metric,
`pods/http_requests`, which measures requests per second over a 1 minute
interval. The value should currently be close to zero, since there's no
traffic to your app, except for the regular metrics collection from
Prometheus.
Try generating some traffic using cURL a few times, like before:
```shell
$ curl http://$(kubectl get service sample-app -o jsonpath='{ .spec.clusterIP }')/metrics
```
Now, if you fetch the metrics again, you should see an increase in the
value. If you leave it alone for a bit, the value should go back down
again.
### Quantity Values
Notice that the API uses Kubernetes-style quantities to describe metric
values. These quantities use SI suffixes instead of decimal points. The
most common to see in the metrics API is the `m` suffix, which means
milli-units, or 1000ths of a unit. If your metric is exactly a whole
number of units on the nose, you might not see a suffix. Otherwise, you'll
probably see an `m` suffix to represent fractions of a unit.
For example, here, `500m` would be half a request per second, `10` would
be 10 requests per second, and `10500m` would be `10.5` requests per
second.
### Troubleshooting Missing Metrics
If the metric does not appear, or is not registered with the right
resources, you might need to modify your [adapter
configuration](/docs/config.md), as mentioned above. Check your labels
via the Prometheus dashboard, and then modify the configuration
appropriately.
As noted in the main [README](/README.md), you'll need to also make sure
your metrics relist interval is at least your Prometheus scrape interval.
If it's less that that, you'll see metrics periodically appear and
disappear from the adapter.
Autoscaling
-----------
Now that you finally have the metrics API set up, your
HorizontalPodAutoscaler should be able to fetch the appropriate metric,
and make decisions based on it.
If you didn't create the HorizontalPodAutoscaler above, create it now:
```shell
$ kubectl create -f sample-app.hpa.yaml
```
Wait a little bit, and then examine the HPA:
```shell ```shell
$ kubectl describe hpa sample-app $ kubectl describe hpa sample-app
``` ```
You should see the HPA's last observed metric value, which should roughly You should see that it succesfully fetched the metric, but it hasn't tried
correspond to the rate of requests that you made. to scale, since there's not traffic.
Since your app is going to need to scale in response to traffic, generate
some via cURL like above:
```shell
$ curl http://$(kubectl get service sample-app -o jsonpath='{ .spec.clusterIP }')/metrics
```
Recall from the configuration at the start that you configured your HPA to
have each replica handle 500 milli-requests, or 1 request every two
seconds (ok, so *maybe* you still have some performance issues to work out
before your beta period ends). Thus, if you generate a few requests, you
should see the HPA scale up your app relatively quickly.
If you describe the HPA again, you should see that the last observed
metric value roughly corresponds to your rate of requests, and that the
HPA has recently scaled your app.
Now that you've got your app autoscaling on the HTTP requests, you're all
ready to launch! If you leave the app alone for a while, the HPA should
scale it back down, so you can save precious budget for the launch party.
Next Steps Next Steps
---------- ----------
@ -510,4 +462,5 @@ scaling on application on a metric provided by another application by
setting different labels or using the `Object` metric source type. setting different labels or using the `Object` metric source type.
For more information on how metrics are exposed by the Prometheus adapter, For more information on how metrics are exposed by the Prometheus adapter,
see the [format documentation](./format.md). see [config documentation](/docs/config.md), and check the [default
configuration](/deploy/manifests/config-map.yaml).

571
glide.lock generated
View file

@ -1,571 +0,0 @@
hash: 1d2ad16816cec54a2561278dfd05ae9725247ea9b72db5c7d330f0074d069fad
updated: 2017-09-28T15:22:28.593829441-04:00
imports:
- name: bitbucket.org/ww/goautoneg
version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675
- name: github.com/beorn7/perks
version: 3ac7bf7a47d159a033b107610db8a1b6575507a4
subpackages:
- quantile
- name: github.com/coreos/etcd
version: 0520cb9304cb2385f7e72b8bc02d6e4d3257158a
subpackages:
- alarm
- auth
- auth/authpb
- client
- clientv3
- compactor
- discovery
- error
- etcdserver
- etcdserver/api
- etcdserver/api/v2http
- etcdserver/api/v2http/httptypes
- etcdserver/api/v3rpc
- etcdserver/api/v3rpc/rpctypes
- etcdserver/auth
- etcdserver/etcdserverpb
- etcdserver/membership
- etcdserver/stats
- integration
- lease
- lease/leasehttp
- lease/leasepb
- mvcc
- mvcc/backend
- mvcc/mvccpb
- pkg/adt
- pkg/contention
- pkg/cpuutil
- pkg/crc
- pkg/fileutil
- pkg/httputil
- pkg/idutil
- pkg/ioutil
- pkg/logutil
- pkg/monotime
- pkg/netutil
- pkg/pathutil
- pkg/pbutil
- pkg/runtime
- pkg/schedule
- pkg/testutil
- pkg/tlsutil
- pkg/transport
- pkg/types
- pkg/wait
- proxy/grpcproxy
- proxy/grpcproxy/cache
- raft
- raft/raftpb
- rafthttp
- snap
- snap/snappb
- store
- version
- wal
- wal/walpb
- name: github.com/coreos/go-systemd
version: 48702e0da86bd25e76cfef347e2adeb434a0d0a6
subpackages:
- daemon
- journal
- name: github.com/coreos/pkg
version: fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8
subpackages:
- capnslog
- health
- httputil
- timeutil
- name: github.com/davecgh/go-spew
version: 782f4967f2dc4564575ca782fe2d04090b5faca8
subpackages:
- spew
- name: github.com/elazarl/go-bindata-assetfs
version: 3dcc96556217539f50599357fb481ac0dc7439b9
- name: github.com/emicklei/go-restful
version: ff4f55a206334ef123e4f79bbf348980da81ca46
subpackages:
- log
- name: github.com/emicklei/go-restful-swagger12
version: dcef7f55730566d41eae5db10e7d6981829720f6
- name: github.com/evanphx/json-patch
version: 944e07253867aacae43c04b2e6a239005443f33a
- name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-openapi/jsonpointer
version: 46af16f9f7b149af66e5d1bd010e3574dc06de98
- name: github.com/go-openapi/jsonreference
version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272
- name: github.com/go-openapi/spec
version: 6aced65f8501fe1217321abf0749d354824ba2ff
- name: github.com/go-openapi/swag
version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72
- name: github.com/gogo/protobuf
version: c0656edd0d9eab7c66d1eb0c568f9039345796f7
subpackages:
- proto
- sortkeys
- name: github.com/golang/glog
version: 44145f04b68cf362d9c4df2182967c2275eaefed
- name: github.com/golang/protobuf
version: 4bd1920723d7b7c925de087aa32e2187708897f7
subpackages:
- jsonpb
- proto
- ptypes
- ptypes/any
- ptypes/duration
- ptypes/timestamp
- name: github.com/google/btree
version: 7d79101e329e5a3adf994758c578dab82b90c017
- name: github.com/google/gofuzz
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
- name: github.com/googleapis/gnostic
version: 0c5108395e2debce0d731cf0287ddf7242066aba
subpackages:
- OpenAPIv2
- compiler
- extensions
- name: github.com/gregjones/httpcache
version: 787624de3eb7bd915c329cba748687a3b22666a6
subpackages:
- diskcache
- name: github.com/grpc-ecosystem/go-grpc-prometheus
version: 2500245aa6110c562d17020fb31a2c133d737799
- name: github.com/grpc-ecosystem/grpc-gateway
version: 84398b94e188ee336f307779b57b3aa91af7063c
subpackages:
- runtime
- runtime/internal
- utilities
- name: github.com/hashicorp/golang-lru
version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4
subpackages:
- simplelru
- name: github.com/howeyc/gopass
version: bf9dde6d0d2c004a008c27aaee91170c786f6db8
- name: github.com/imdario/mergo
version: 6633656539c1639d9d78127b7d47c622b5d7b6dc
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/json-iterator/go
version: 36b14963da70d11297d313183d7e6388c8510e1e
- name: github.com/juju/ratelimit
version: 5b9ff866471762aa2ab2dced63c9fb6f53921342
- name: github.com/kubernetes-incubator/custom-metrics-apiserver
version: fae01650d93f5de6151a024e36323344e14427aa
subpackages:
- pkg/apiserver
- pkg/apiserver/installer
- pkg/cmd/server
- pkg/dynamicmapper
- pkg/provider
- pkg/registry/custom_metrics
- name: github.com/mailru/easyjson
version: d5b7844b561a7bc640052f1b935f7b800330d7e0
subpackages:
- buffer
- jlexer
- jwriter
- name: github.com/matttproud/golang_protobuf_extensions
version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a
subpackages:
- pbutil
- name: github.com/mxk/go-flowrate
version: cca7078d478f8520f85629ad7c68962d31ed7682
subpackages:
- flowrate
- name: github.com/NYTimes/gziphandler
version: 56545f4a5d46df9a6648819d1664c3a03a13ffdb
- name: github.com/pborman/uuid
version: ca53cad383cad2479bbba7f7a1a05797ec1386e4
- name: github.com/peterbourgon/diskv
version: 5f041e8faa004a95c88a202771f4cc3e991971e6
- name: github.com/pkg/errors
version: a22138067af1c4942683050411a841ade67fe1eb
- name: github.com/prometheus/client_golang
version: e7e903064f5e9eb5da98208bae10b475d4db0f8c
subpackages:
- prometheus
- name: github.com/prometheus/client_model
version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6
subpackages:
- go
- name: github.com/prometheus/common
version: 13ba4ddd0caa9c28ca7b7bffe1dfa9ed8d5ef207
subpackages:
- expfmt
- internal/bitbucket.org/ww/goautoneg
- model
- name: github.com/prometheus/procfs
version: 65c1f6f8f0fc1e2185eb9863a3bc751496404259
subpackages:
- xfs
- name: github.com/PuerkitoBio/purell
version: 8a290539e2e8629dbc4e6bad948158f790ec31f4
- name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/spf13/cobra
version: f62e98d28ab7ad31d707ba837a966378465c7b57
- name: github.com/spf13/pflag
version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7
- name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert
- require
- name: github.com/ugorji/go
version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74
subpackages:
- codec
- name: golang.org/x/crypto
version: 81e90905daefcd6fd217b62423c0908922eadb30
subpackages:
- bcrypt
- blowfish
- nacl/secretbox
- poly1305
- salsa20/salsa
- ssh/terminal
- name: golang.org/x/net
version: 1c05540f6879653db88113bc4a2b70aec4bd491f
subpackages:
- context
- html
- html/atom
- http2
- http2/hpack
- idna
- internal/timeseries
- lex/httplex
- trace
- websocket
- name: golang.org/x/sys
version: 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce
subpackages:
- unix
- windows
- name: golang.org/x/text
version: b19bf474d317b857955b12035d2c5acb57ce8b01
subpackages:
- cases
- internal
- internal/tag
- language
- runes
- secure/bidirule
- secure/precis
- transform
- unicode/bidi
- unicode/norm
- width
- name: google.golang.org/genproto
version: 09f6ed296fc66555a25fe4ce95173148778dfa85
subpackages:
- googleapis/rpc/status
- name: google.golang.org/grpc
version: d2e1b51f33ff8c5e4a15560ff049d200e83726c5
subpackages:
- codes
- credentials
- grpclb/grpc_lb_v1
- grpclog
- internal
- keepalive
- metadata
- naming
- peer
- stats
- status
- tap
- transport
- name: gopkg.in/inf.v0
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/natefinch/lumberjack.v2
version: 20b71e5b60d756d3d2f80def009790325acc2b23
- name: gopkg.in/yaml.v2
version: 53feefa2559fb8dfa8d81baad31be332c97d6c77
- name: k8s.io/api
version: cadaf100c0a3dd6b254f320d6d651df079ec8e0a
subpackages:
- admissionregistration/v1alpha1
- apps/v1beta1
- apps/v1beta2
- authentication/v1
- authentication/v1beta1
- authorization/v1
- authorization/v1beta1
- autoscaling/v1
- autoscaling/v2beta1
- batch/v1
- batch/v1beta1
- batch/v2alpha1
- certificates/v1beta1
- core/v1
- extensions/v1beta1
- networking/v1
- policy/v1beta1
- rbac/v1
- rbac/v1alpha1
- rbac/v1beta1
- scheduling/v1alpha1
- settings/v1alpha1
- storage/v1
- storage/v1beta1
- name: k8s.io/apimachinery
version: 3b05bbfa0a45413bfa184edbf9af617e277962fb
subpackages:
- pkg/api/equality
- pkg/api/errors
- pkg/api/meta
- pkg/api/resource
- pkg/api/validation
- pkg/api/validation/path
- pkg/apimachinery
- pkg/apimachinery/announced
- pkg/apimachinery/registered
- pkg/apis/meta/internalversion
- pkg/apis/meta/v1
- pkg/apis/meta/v1/unstructured
- pkg/apis/meta/v1/validation
- pkg/apis/meta/v1alpha1
- pkg/conversion
- pkg/conversion/queryparams
- pkg/conversion/unstructured
- pkg/fields
- pkg/labels
- pkg/runtime
- pkg/runtime/schema
- pkg/runtime/serializer
- pkg/runtime/serializer/json
- pkg/runtime/serializer/protobuf
- pkg/runtime/serializer/recognizer
- pkg/runtime/serializer/streaming
- pkg/runtime/serializer/versioning
- pkg/selection
- pkg/types
- pkg/util/cache
- pkg/util/clock
- pkg/util/diff
- pkg/util/errors
- pkg/util/framer
- pkg/util/httpstream
- pkg/util/intstr
- pkg/util/json
- pkg/util/mergepatch
- pkg/util/net
- pkg/util/proxy
- pkg/util/rand
- pkg/util/runtime
- pkg/util/sets
- pkg/util/strategicpatch
- pkg/util/uuid
- pkg/util/validation
- pkg/util/validation/field
- pkg/util/wait
- pkg/util/yaml
- pkg/version
- pkg/watch
- third_party/forked/golang/json
- third_party/forked/golang/netutil
- third_party/forked/golang/reflect
- name: k8s.io/apiserver
version: c1e53d745d0fe45bf7d5d44697e6eface25fceca
subpackages:
- pkg/admission
- pkg/admission/initializer
- pkg/admission/plugin/namespace/lifecycle
- pkg/apis/apiserver
- pkg/apis/apiserver/install
- pkg/apis/apiserver/v1alpha1
- pkg/apis/audit
- pkg/apis/audit/install
- pkg/apis/audit/v1alpha1
- pkg/apis/audit/v1beta1
- pkg/apis/audit/validation
- pkg/audit
- pkg/audit/policy
- pkg/authentication/authenticator
- pkg/authentication/authenticatorfactory
- pkg/authentication/group
- pkg/authentication/request/anonymous
- pkg/authentication/request/bearertoken
- pkg/authentication/request/headerrequest
- pkg/authentication/request/union
- pkg/authentication/request/websocket
- pkg/authentication/request/x509
- pkg/authentication/serviceaccount
- pkg/authentication/token/tokenfile
- pkg/authentication/user
- pkg/authorization/authorizer
- pkg/authorization/authorizerfactory
- pkg/authorization/union
- pkg/endpoints
- pkg/endpoints/discovery
- pkg/endpoints/filters
- pkg/endpoints/handlers
- pkg/endpoints/handlers/negotiation
- pkg/endpoints/handlers/responsewriters
- pkg/endpoints/metrics
- pkg/endpoints/openapi
- pkg/endpoints/request
- pkg/features
- pkg/registry/generic
- pkg/registry/generic/registry
- pkg/registry/rest
- pkg/server
- pkg/server/filters
- pkg/server/healthz
- pkg/server/httplog
- pkg/server/mux
- pkg/server/options
- pkg/server/routes
- pkg/server/routes/data/swagger
- pkg/server/storage
- pkg/storage
- pkg/storage/errors
- pkg/storage/etcd
- pkg/storage/etcd/metrics
- pkg/storage/etcd/util
- pkg/storage/etcd3
- pkg/storage/etcd3/preflight
- pkg/storage/names
- pkg/storage/storagebackend
- pkg/storage/storagebackend/factory
- pkg/storage/value
- pkg/util/feature
- pkg/util/flag
- pkg/util/flushwriter
- pkg/util/logs
- pkg/util/trace
- pkg/util/webhook
- pkg/util/wsstream
- plugin/pkg/audit/log
- plugin/pkg/audit/webhook
- plugin/pkg/authenticator/token/webhook
- plugin/pkg/authorizer/webhook
- name: k8s.io/client-go
version: 82aa063804cf055e16e8911250f888bc216e8b61
subpackages:
- discovery
- dynamic
- dynamic/fake
- informers
- informers/admissionregistration
- informers/admissionregistration/v1alpha1
- informers/apps
- informers/apps/v1beta1
- informers/apps/v1beta2
- informers/autoscaling
- informers/autoscaling/v1
- informers/autoscaling/v2beta1
- informers/batch
- informers/batch/v1
- informers/batch/v1beta1
- informers/batch/v2alpha1
- informers/certificates
- informers/certificates/v1beta1
- informers/core
- informers/core/v1
- informers/extensions
- informers/extensions/v1beta1
- informers/internalinterfaces
- informers/networking
- informers/networking/v1
- informers/policy
- informers/policy/v1beta1
- informers/rbac
- informers/rbac/v1
- informers/rbac/v1alpha1
- informers/rbac/v1beta1
- informers/scheduling
- informers/scheduling/v1alpha1
- informers/settings
- informers/settings/v1alpha1
- informers/storage
- informers/storage/v1
- informers/storage/v1beta1
- kubernetes
- kubernetes/scheme
- kubernetes/typed/admissionregistration/v1alpha1
- kubernetes/typed/apps/v1beta1
- kubernetes/typed/apps/v1beta2
- kubernetes/typed/authentication/v1
- kubernetes/typed/authentication/v1beta1
- kubernetes/typed/authorization/v1
- kubernetes/typed/authorization/v1beta1
- kubernetes/typed/autoscaling/v1
- kubernetes/typed/autoscaling/v2beta1
- kubernetes/typed/batch/v1
- kubernetes/typed/batch/v1beta1
- kubernetes/typed/batch/v2alpha1
- kubernetes/typed/certificates/v1beta1
- kubernetes/typed/core/v1
- kubernetes/typed/extensions/v1beta1
- kubernetes/typed/networking/v1
- kubernetes/typed/policy/v1beta1
- kubernetes/typed/rbac/v1
- kubernetes/typed/rbac/v1alpha1
- kubernetes/typed/rbac/v1beta1
- kubernetes/typed/scheduling/v1alpha1
- kubernetes/typed/settings/v1alpha1
- kubernetes/typed/storage/v1
- kubernetes/typed/storage/v1beta1
- listers/admissionregistration/v1alpha1
- listers/apps/v1beta1
- listers/apps/v1beta2
- listers/autoscaling/v1
- listers/autoscaling/v2beta1
- listers/batch/v1
- listers/batch/v1beta1
- listers/batch/v2alpha1
- listers/certificates/v1beta1
- listers/core/v1
- listers/extensions/v1beta1
- listers/networking/v1
- listers/policy/v1beta1
- listers/rbac/v1
- listers/rbac/v1alpha1
- listers/rbac/v1beta1
- listers/scheduling/v1alpha1
- listers/settings/v1alpha1
- listers/storage/v1
- listers/storage/v1beta1
- pkg/version
- rest
- rest/watch
- testing
- tools/auth
- tools/cache
- tools/clientcmd
- tools/clientcmd/api
- tools/clientcmd/api/latest
- tools/clientcmd/api/v1
- tools/metrics
- tools/pager
- tools/reference
- transport
- util/cert
- util/flowcontrol
- util/homedir
- util/integer
- name: k8s.io/kube-openapi
version: 868f2f29720b192240e18284659231b440f9cda5
subpackages:
- pkg/builder
- pkg/common
- pkg/handler
- pkg/util
- name: k8s.io/metrics
version: 4c7ac522b9daf7beeb53f6766722ba78b7e5712d
subpackages:
- pkg/apis/custom_metrics
- pkg/apis/custom_metrics/install
- pkg/apis/custom_metrics/v1beta1
testImports:
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib

View file

@ -1,23 +0,0 @@
package: github.com/directxman12/k8s-prometheus-adapter
import:
- package: github.com/spf13/cobra
- package: k8s.io/apimachinery
subpackages:
- pkg/util/wait
- package: k8s.io/apiserver
subpackages:
- pkg/util/logs
- package: k8s.io/client-go
subpackages:
- kubernetes/typed/core/v1
- rest
- tools/clientcmd
- package: github.com/kubernetes-incubator/custom-metrics-apiserver
subpackages:
- pkg/cmd/server
- pkg/provider
- package: github.com/stretchr/testify
version: ^1.1.4
subpackages:
- assert
- require

118
go.mod Normal file
View file

@ -0,0 +1,118 @@
module sigs.k8s.io/prometheus-adapter
go 1.22.1
toolchain go1.22.2
require (
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.33.1
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.73.2
github.com/prometheus-operator/prometheus-operator/pkg/client v0.73.2
github.com/prometheus/client_golang v1.18.0
github.com/prometheus/common v0.46.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.30.0
k8s.io/apimachinery v0.30.0
k8s.io/apiserver v0.30.0
k8s.io/client-go v0.30.0
k8s.io/component-base v0.30.0
k8s.io/klog/v2 v2.120.1
k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f
k8s.io/metrics v0.30.0
sigs.k8s.io/custom-metrics-apiserver v1.30.0
sigs.k8s.io/metrics-server v0.7.1
)
require (
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/cel-go v0.17.8 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.11 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect
go.etcd.io/etcd/client/v3 v3.5.11 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/sdk v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
google.golang.org/grpc v1.60.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.29.3 // indirect
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect
k8s.io/kms v0.30.0 // indirect
k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect
sigs.k8s.io/controller-runtime v0.17.2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

383
go.sum Normal file
View file

@ -0,0 +1,383 @@
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto=
github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.73.2 h1:GwlGJPK6vf1UIohpc72KJVkKYlzki1UgE3xC4bWbf20=
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.73.2/go.mod h1:yJ3CawR/A5qEYFEeCOUVYLTwYxmacfHQhJS+b/2QiaM=
github.com/prometheus-operator/prometheus-operator/pkg/client v0.73.2 h1:cKSYjDjk9Rn/VIFKCh+CCd771ip7VTJzA3fAuKTxY2Q=
github.com/prometheus-operator/prometheus-operator/pkg/client v0.73.2/go.mod h1:mkLwGPvmexoEm6j3bk8gkWNIIFzN2uCs9tRFU2Vsu/I=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/etcd/api/v3 v3.5.11 h1:B54KwXbWDHyD3XYAwprxNzTe7vlhR69LuBgZnMVvS7E=
go.etcd.io/etcd/api/v3 v3.5.11/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
go.etcd.io/etcd/client/pkg/v3 v3.5.11 h1:bT2xVspdiCj2910T0V+/KHcVKjkUrCZVtk8J2JF2z1A=
go.etcd.io/etcd/client/pkg/v3 v3.5.11/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4=
go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4=
go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA=
go.etcd.io/etcd/client/v3 v3.5.11 h1:ajWtgoNSZJ1gmS8k+icvPtqsqEav+iUorF7b0qozgUU=
go.etcd.io/etcd/client/v3 v3.5.11/go.mod h1:a6xQUEqFJ8vztO1agJh/KQKOMfFI8og52ZconzcDJwE=
go.etcd.io/etcd/pkg/v3 v3.5.10 h1:WPR8K0e9kWl1gAhB5A7gEa5ZBTNkT9NdNWrR8Qpo1CM=
go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs=
go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA=
go.etcd.io/etcd/raft/v3 v3.5.10/go.mod h1:odD6kr8XQXTy9oQnyMPBOr0TVe+gT0neQhElQ6jbGRc=
go.etcd.io/etcd/server/v3 v3.5.10 h1:4NOGyOwD5sUZ22PiWYKmfxqoeh72z6EhYjNosKGLmZg=
go.etcd.io/etcd/server/v3 v3.5.10/go.mod h1:gBplPHfs6YI0L+RpGkTQO7buDbHv5HJGG/Bst0/zIPo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI=
k8s.io/apiextensions-apiserver v0.29.3/go.mod h1:po0XiY5scnpJfFizNGo6puNU6Fq6D70UJY2Cb2KwAVc=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M=
k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY=
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o=
k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ=
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo=
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kms v0.30.0 h1:ZlnD/ei5lpvUlPw6eLfVvH7d8i9qZ6HwUQgydNVks8g=
k8s.io/kms v0.30.0/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4=
k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f h1:0LQagt0gDpKqvIkAMPaRGcXawNMouPECM1+F9BVxEaM=
k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f/go.mod h1:S9tOR0FxgyusSNR+MboCuiDpVWkAifZvaYI1Q2ubgro=
k8s.io/metrics v0.30.0 h1:tqB+T0GJY288KahaO3Eb41HaDVeLR18gBmyPo0R417s=
k8s.io/metrics v0.30.0/go.mod h1:nSDA8V19WHhCTBhRYuyzJT9yPJBxSpqbyrGCCQ4jPj4=
k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 h1:ao5hUqGhsqdm+bYbjH/pRkCs0unBGe9UyDahzs9zQzQ=
k8s.io/utils v0.0.0-20240423183400-0849a56e8f22/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4=
sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0=
sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=
sigs.k8s.io/custom-metrics-apiserver v1.30.0 h1:BCgg2QfInoWXvoJgPK8TxrSS9r5wR4NNvr7M+9sUOYo=
sigs.k8s.io/custom-metrics-apiserver v1.30.0/go.mod h1:QXOKIL83M545uITzoZn4OC1C7nr0WhLh70A38pbzUpk=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/metrics-server v0.7.1 h1:LhdCzkaI7VI7/N7pR4hDauTuWyc9Pxr+ihjTDuS9GIo=
sigs.k8s.io/metrics-server v0.7.1/go.mod h1:vt+pIEbw5tpmyRR46WJb3pRm1JEzf/HxRN+VClTKuqI=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

15
hack/boilerplate.go.txt Normal file
View file

@ -0,0 +1,15 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

View file

@ -1,41 +0,0 @@
#!/bin/bash
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
verify=0
if [[ ${1:-} = "--verify" || ${1:-} = "-v" ]]; then
verify=1
fi
find_files() {
find . -not \( \( \
-wholename './_output' \
-o -wholename './vendor' \
\) -prune \) -name '*.go'
}
if [[ $verify -eq 1 ]]; then
diff=$(find_files | xargs gofmt -s -d 2>&1)
if [[ -n "${diff}" ]]; then
echo "gofmt -s -w $(echo "${diff}" | awk '/^diff / { print $2 }' | tr '\n' ' ')"
exit 1
fi
else
find_files | xargs gofmt -s -w
fi

24
hack/tools.go Normal file
View file

@ -0,0 +1,24 @@
// Copyright 2021 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build tools
// +build tools
// Package tools tracks dependencies for tools that used in the build process.
// See https://github.com/golang/go/wiki/Modules
package tools
import (
_ "k8s.io/kube-openapi/cmd/openapi-gen"
)

File diff suppressed because it is too large Load diff

26
pkg/api/openapi-gen.go Normal file
View file

@ -0,0 +1,26 @@
//go:build codegen
// +build codegen
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package is only a stub to ensure k8s.io/kube-openapi/cmd/openapi-gen is vendored
// so the same version of kube-openapi is used to generate and render the openapi spec
package main
import (
_ "k8s.io/kube-openapi/cmd/openapi-gen"
)

View file

@ -21,14 +21,14 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strings"
"time" "time"
"github.com/golang/glog"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"k8s.io/klog/v2"
) )
// APIClient is a raw client to the Prometheus Query API. // APIClient is a raw client to the Prometheus Query API.
@ -47,17 +47,31 @@ type GenericAPIClient interface {
type httpAPIClient struct { type httpAPIClient struct {
client *http.Client client *http.Client
baseURL *url.URL baseURL *url.URL
headers http.Header
} }
func (c *httpAPIClient) Do(ctx context.Context, verb, endpoint string, query url.Values) (APIResponse, error) { func (c *httpAPIClient) Do(ctx context.Context, verb, endpoint string, query url.Values) (APIResponse, error) {
u := *c.baseURL u := *c.baseURL
u.Path = path.Join(c.baseURL.Path, endpoint) u.Path = path.Join(c.baseURL.Path, endpoint)
u.RawQuery = query.Encode() var reqBody io.Reader
req, err := http.NewRequest(verb, u.String(), nil) if verb == http.MethodGet {
u.RawQuery = query.Encode()
} else if verb == http.MethodPost {
reqBody = strings.NewReader(query.Encode())
}
req, err := http.NewRequestWithContext(ctx, verb, u.String(), reqBody)
if err != nil { if err != nil {
return APIResponse{}, fmt.Errorf("error constructing HTTP request to Prometheus: %v", err) return APIResponse{}, fmt.Errorf("error constructing HTTP request to Prometheus: %v", err)
} }
req.WithContext(ctx) for key, values := range c.headers {
for _, value := range values {
req.Header.Add(key, value)
}
}
if verb == http.MethodPost {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
defer func() { defer func() {
@ -70,8 +84,8 @@ func (c *httpAPIClient) Do(ctx context.Context, verb, endpoint string, query url
return APIResponse{}, err return APIResponse{}, err
} }
if glog.V(6) { if klog.V(6).Enabled() {
glog.Infof("%s %s %s", verb, u.String(), resp.Status) klog.Infof("%s %s %s", verb, u.String(), resp.Status)
} }
code := resp.StatusCode code := resp.StatusCode
@ -85,12 +99,12 @@ func (c *httpAPIClient) Do(ctx context.Context, verb, endpoint string, query url
} }
var body io.Reader = resp.Body var body io.Reader = resp.Body
if glog.V(8) { if klog.V(8).Enabled() {
data, err := ioutil.ReadAll(body) data, err := io.ReadAll(body)
if err != nil { if err != nil {
return APIResponse{}, fmt.Errorf("unable to log response body: %v", err) return APIResponse{}, fmt.Errorf("unable to log response body: %v", err)
} }
glog.Infof("Response Body: %s", string(data)) klog.Infof("Response Body: %s", string(data))
body = bytes.NewReader(data) body = bytes.NewReader(data)
} }
@ -113,10 +127,11 @@ func (c *httpAPIClient) Do(ctx context.Context, verb, endpoint string, query url
} }
// NewGenericAPIClient builds a new generic Prometheus API client for the given base URL and HTTP Client. // NewGenericAPIClient builds a new generic Prometheus API client for the given base URL and HTTP Client.
func NewGenericAPIClient(client *http.Client, baseURL *url.URL) GenericAPIClient { func NewGenericAPIClient(client *http.Client, baseURL *url.URL, headers http.Header) GenericAPIClient {
return &httpAPIClient{ return &httpAPIClient{
client: client, client: client,
baseURL: baseURL, baseURL: baseURL,
headers: headers,
} }
} }
@ -128,20 +143,22 @@ const (
// queryClient is a Client that connects to the Prometheus HTTP API. // queryClient is a Client that connects to the Prometheus HTTP API.
type queryClient struct { type queryClient struct {
api GenericAPIClient api GenericAPIClient
verb string
} }
// NewClientForAPI creates a Client for the given generic Prometheus API client. // NewClientForAPI creates a Client for the given generic Prometheus API client.
func NewClientForAPI(client GenericAPIClient) Client { func NewClientForAPI(client GenericAPIClient, verb string) Client {
return &queryClient{ return &queryClient{
api: client, api: client,
verb: verb,
} }
} }
// NewClient creates a Client for the given HTTP client and base URL (the location of the Prometheus server). // NewClient creates a Client for the given HTTP client and base URL (the location of the Prometheus server).
func NewClient(client *http.Client, baseURL *url.URL) Client { func NewClient(client *http.Client, baseURL *url.URL, headers http.Header, verb string) Client {
genericClient := NewGenericAPIClient(client, baseURL) genericClient := NewGenericAPIClient(client, baseURL, headers)
return NewClientForAPI(genericClient) return NewClientForAPI(genericClient, verb)
} }
func (h *queryClient) Series(ctx context.Context, interval model.Interval, selectors ...Selector) ([]Series, error) { func (h *queryClient) Series(ctx context.Context, interval model.Interval, selectors ...Selector) ([]Series, error) {
@ -157,7 +174,7 @@ func (h *queryClient) Series(ctx context.Context, interval model.Interval, selec
vals.Add("match[]", string(selector)) vals.Add("match[]", string(selector))
} }
res, err := h.api.Do(ctx, "GET", seriesURL, vals) res, err := h.api.Do(ctx, h.verb, seriesURL, vals)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -177,7 +194,7 @@ func (h *queryClient) Query(ctx context.Context, t model.Time, query Selector) (
vals.Set("timeout", model.Duration(timeout).String()) vals.Set("timeout", model.Duration(timeout).String())
} }
res, err := h.api.Do(ctx, "GET", queryURL, vals) res, err := h.api.Do(ctx, h.verb, queryURL, vals)
if err != nil { if err != nil {
return QueryResult{}, err return QueryResult{}, err
} }
@ -204,7 +221,7 @@ func (h *queryClient) QueryRange(ctx context.Context, r Range, query Selector) (
vals.Set("timeout", model.Duration(timeout).String()) vals.Set("timeout", model.Duration(timeout).String())
} }
res, err := h.api.Do(ctx, "GET", queryRangeURL, vals) res, err := h.api.Do(ctx, h.verb, queryRangeURL, vals)
if err != nil { if err != nil {
return QueryResult{}, err return QueryResult{}, err
} }
@ -218,7 +235,7 @@ func (h *queryClient) QueryRange(ctx context.Context, r Range, query Selector) (
// when present // when present
func timeoutFromContext(ctx context.Context) (time.Duration, bool) { func timeoutFromContext(ctx context.Context) (time.Duration, bool) {
if deadline, hasDeadline := ctx.Deadline(); hasDeadline { if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
return time.Now().Sub(deadline), true return time.Since(deadline), true
} }
return time.Duration(0), false return time.Duration(0), false

78
pkg/client/fake/client.go Normal file
View file

@ -0,0 +1,78 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fake
import (
"context"
"fmt"
pmodel "github.com/prometheus/common/model"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
)
// FakePrometheusClient is a fake instance of prom.Client
type FakePrometheusClient struct {
// AcceptableInterval is the interval in which to return queries
AcceptableInterval pmodel.Interval
// ErrQueries are queries that result in an error (whether from Query or Series)
ErrQueries map[prom.Selector]error
// Series are non-error responses to partial Series calls
SeriesResults map[prom.Selector][]prom.Series
// QueryResults are non-error responses to Query
QueryResults map[prom.Selector]prom.QueryResult
}
func (c *FakePrometheusClient) Series(_ context.Context, interval pmodel.Interval, selectors ...prom.Selector) ([]prom.Series, error) {
if (interval.Start != 0 && interval.Start < c.AcceptableInterval.Start) || (interval.End != 0 && interval.End > c.AcceptableInterval.End) {
return nil, fmt.Errorf("interval [%v, %v] for query is outside range [%v, %v]", interval.Start, interval.End, c.AcceptableInterval.Start, c.AcceptableInterval.End)
}
res := []prom.Series{}
for _, sel := range selectors {
if err, found := c.ErrQueries[sel]; found {
return nil, err
}
if series, found := c.SeriesResults[sel]; found {
res = append(res, series...)
}
}
return res, nil
}
func (c *FakePrometheusClient) Query(_ context.Context, t pmodel.Time, query prom.Selector) (prom.QueryResult, error) {
if t < c.AcceptableInterval.Start || t > c.AcceptableInterval.End {
return prom.QueryResult{}, fmt.Errorf("time %v for query is outside range [%v, %v]", t, c.AcceptableInterval.Start, c.AcceptableInterval.End)
}
if err, found := c.ErrQueries[query]; found {
return prom.QueryResult{}, err
}
if res, found := c.QueryResults[query]; found {
return res, nil
}
return prom.QueryResult{
Type: pmodel.ValVector,
Vector: &pmodel.Vector{},
}, nil
}
func (c *FakePrometheusClient) QueryRange(_ context.Context, r prom.Range, query prom.Selector) (prom.QueryResult, error) {
return prom.QueryResult{}, nil
}

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package client package client
import ( import (
@ -22,14 +23,14 @@ import (
// LabelNeq produces a not-equal label selector expression. // LabelNeq produces a not-equal label selector expression.
// Label is passed verbatim, and value is double-quote escaped // Label is passed verbatim, and value is double-quote escaped
// using Go's escaping is used on value (as per the PromQL rules). // using Go's escaping (as per the PromQL rules).
func LabelNeq(label string, value string) string { func LabelNeq(label string, value string) string {
return fmt.Sprintf("%s!=%q", label, value) return fmt.Sprintf("%s!=%q", label, value)
} }
// LabelEq produces a equal label selector expression. // LabelEq produces a equal label selector expression.
// Label is passed verbatim, and value is double-quote escaped // Label is passed verbatim, and value is double-quote escaped
// using Go's escaping is used on value (as per the PromQL rules). // using Go's escaping (as per the PromQL rules).
func LabelEq(label string, value string) string { func LabelEq(label string, value string) string {
return fmt.Sprintf("%s=%q", label, value) return fmt.Sprintf("%s=%q", label, value)
} }
@ -52,7 +53,7 @@ func NameMatches(expr string) string {
} }
// NameNotMatches produces a label selector expression that checks that the series name doesn't matches the given expression. // NameNotMatches produces a label selector expression that checks that the series name doesn't matches the given expression.
// It's a convinience wrapper around LabelNotMatches. // It's a convenience wrapper around LabelNotMatches.
func NameNotMatches(expr string) string { func NameNotMatches(expr string) string {
return LabelNotMatches("__name__", expr) return LabelNotMatches("__name__", expr)
} }

View file

@ -13,12 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package client package client
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -26,7 +28,7 @@ import (
// NB: the official prometheus API client at https://github.com/prometheus/client_golang // NB: the official prometheus API client at https://github.com/prometheus/client_golang
// is rather lackluster -- as of the time of writing of this file, it lacked support // is rather lackluster -- as of the time of writing of this file, it lacked support
// for querying the series metadata, which we need for the adapter. Instead, we use // for querying the series metadata, which we need for the adapter. Instead, we use
// this client. // this client.
// Selector represents a series selector // Selector represents a series selector
@ -121,3 +123,11 @@ func (s *Series) UnmarshalJSON(data []byte) error {
return nil return nil
} }
func (s *Series) String() string {
lblStrings := make([]string, 0, len(s.Labels))
for k, v := range s.Labels {
lblStrings = append(lblStrings, fmt.Sprintf("%s=%q", k, v))
}
return fmt.Sprintf("%s{%s}", s.Name, strings.Join(lblStrings, ","))
}

View file

@ -13,34 +13,51 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package metrics package metrics
import ( import (
"context" "context"
"net/http"
"net/url" "net/url"
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/directxman12/k8s-prometheus-adapter/pkg/client" apimetrics "k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
"sigs.k8s.io/prometheus-adapter/pkg/client"
) )
var ( var (
// queryLatency is the total latency of any query going through the // queryLatency is the total latency of any query going through the
// various endpoints (query, range-query, series). It includes some deserialization // various endpoints (query, range-query, series). It includes some deserialization
// overhead and HTTP overhead. // overhead and HTTP overhead.
queryLatency = prometheus.NewHistogramVec( queryLatency = metrics.NewHistogramVec(
prometheus.HistogramOpts{ &metrics.HistogramOpts{
Name: "cmgateway_prometheus_query_latency_seconds", Namespace: "prometheus_adapter",
Help: "Prometheus client query latency in seconds. Broken down by target prometheus endpoint and target server", Subsystem: "prometheus_client",
Buckets: prometheus.ExponentialBuckets(0.0001, 2, 10), Name: "request_duration_seconds",
Help: "Prometheus client query latency in seconds. Broken down by target prometheus endpoint and target server",
Buckets: prometheus.DefBuckets,
}, },
[]string{"endpoint", "server"}, []string{"path", "server"},
) )
) )
func init() { func MetricsHandler() (http.HandlerFunc, error) {
prometheus.MustRegister(queryLatency) registry := metrics.NewKubeRegistry()
err := registry.Register(queryLatency)
if err != nil {
return nil, err
}
apimetrics.Register()
return func(w http.ResponseWriter, req *http.Request) {
legacyregistry.Handler().ServeHTTP(w, req)
metrics.HandlerFor(registry, metrics.HandlerOpts{}).ServeHTTP(w, req)
}, nil
} }
// instrumentedClient is a client.GenericAPIClient which instruments calls to Do, // instrumentedClient is a client.GenericAPIClient which instruments calls to Do,
@ -62,7 +79,7 @@ func (c *instrumentedGenericClient) Do(ctx context.Context, verb, endpoint strin
return return
} }
} }
queryLatency.With(prometheus.Labels{"endpoint": endpoint, "server": c.serverName}).Observe(endTime.Sub(startTime).Seconds()) queryLatency.With(prometheus.Labels{"path": endpoint, "server": c.serverName}).Observe(endTime.Sub(startTime).Seconds())
}() }()
var resp client.APIResponse var resp client.APIResponse

View file

@ -25,10 +25,10 @@ type ErrorType string
const ( const (
ErrBadData ErrorType = "bad_data" ErrBadData ErrorType = "bad_data"
ErrTimeout = "timeout" ErrTimeout ErrorType = "timeout"
ErrCanceled = "canceled" ErrCanceled ErrorType = "canceled"
ErrExec = "execution" ErrExec ErrorType = "execution"
ErrBadResponse = "bad_response" ErrBadResponse ErrorType = "bad_response"
) )
// Error is an error returned by the API. // Error is an error returned by the API.
@ -46,7 +46,7 @@ type ResponseStatus string
const ( const (
ResponseSucceeded ResponseStatus = "succeeded" ResponseSucceeded ResponseStatus = "succeeded"
ResponseError = "error" ResponseError ResponseStatus = "error"
) )
// APIResponse represents the raw response returned by the API. // APIResponse represents the raw response returned by the API.

111
pkg/config/config.go Normal file
View file

@ -0,0 +1,111 @@
package config
import (
pmodel "github.com/prometheus/common/model"
)
type MetricsDiscoveryConfig struct {
// Rules specifies how to discover and map Prometheus metrics to
// custom metrics API resources. The rules are applied independently,
// and thus must be mutually exclusive. Rules with the same SeriesQuery
// will make only a single API call.
Rules []DiscoveryRule `json:"rules" yaml:"rules"`
ResourceRules *ResourceRules `json:"resourceRules,omitempty" yaml:"resourceRules,omitempty"`
ExternalRules []DiscoveryRule `json:"externalRules,omitempty" yaml:"externalRules,omitempty"`
}
// DiscoveryRule describes a set of rules for transforming Prometheus metrics to/from
// custom metrics API resources.
type DiscoveryRule struct {
// SeriesQuery specifies which metrics this rule should consider via a Prometheus query
// series selector query.
SeriesQuery string `json:"seriesQuery" yaml:"seriesQuery"`
// SeriesFilters specifies additional regular expressions to be applied on
// the series names returned from the query. This is useful for constraints
// that can't be represented in the SeriesQuery (e.g. series matching `container_.+`
// not matching `container_.+_total`. A filter will be automatically appended to
// match the form specified in Name.
SeriesFilters []RegexFilter `json:"seriesFilters" yaml:"seriesFilters"`
// Resources specifies how associated Kubernetes resources should be discovered for
// the given metrics.
Resources ResourceMapping `json:"resources" yaml:"resources"`
// Name specifies how the metric name should be transformed between custom metric
// API resources, and Prometheus metric names.
Name NameMapping `json:"name" yaml:"name"`
// MetricsQuery specifies modifications to the metrics query, such as converting
// cumulative metrics to rate metrics. It is a template where `.LabelMatchers` is
// a the comma-separated base label matchers and `.Series` is the series name, and
// `.GroupBy` is the comma-separated expected group-by label names. The delimeters
// are `<<` and `>>`.
MetricsQuery string `json:"metricsQuery,omitempty" yaml:"metricsQuery,omitempty"`
}
// RegexFilter is a filter that matches positively or negatively against a regex.
// Only one field may be set at a time.
type RegexFilter struct {
Is string `json:"is,omitempty" yaml:"is,omitempty"`
IsNot string `json:"isNot,omitempty" yaml:"isNot,omitempty"`
}
// ResourceMapping specifies how to map Kubernetes resources to Prometheus labels
type ResourceMapping struct {
// Template specifies a golang string template for converting a Kubernetes
// group-resource to a Prometheus label. The template object contains
// the `.Group` and `.Resource` fields. The `.Group` field will have
// dots replaced with underscores, and the `.Resource` field will be
// singularized. The delimiters are `<<` and `>>`.
Template string `json:"template,omitempty" yaml:"template,omitempty"`
// Overrides specifies exceptions to the above template, mapping label names
// to group-resources
Overrides map[string]GroupResource `json:"overrides,omitempty" yaml:"overrides,omitempty"`
// Namespaced ignores the source namespace of the requester and requires one in the query
Namespaced *bool `json:"namespaced,omitempty" yaml:"namespaced,omitempty"`
}
// GroupResource represents a Kubernetes group-resource.
type GroupResource struct {
Group string `json:"group,omitempty" yaml:"group,omitempty"`
Resource string `json:"resource" yaml:"resource"`
}
// NameMapping specifies how to convert Prometheus metrics
// to/from custom metrics API resources.
type NameMapping struct {
// Matches is a regular expression that is used to match
// Prometheus series names. It may be left blank, in which
// case it is equivalent to `.*`.
Matches string `json:"matches" yaml:"matches"`
// As is the name used in the API. Captures from Matches
// are available for use here. If not specified, it defaults
// to $0 if no capture groups are present in Matches, or $1
// if only one is present, and will error if multiple are.
As string `json:"as" yaml:"as"`
}
// ResourceRules describe the rules for querying resource metrics
// API results. It's assumed that the same metrics can be used
// to aggregate across different resources.
type ResourceRules struct {
CPU ResourceRule `json:"cpu" yaml:"cpu"`
Memory ResourceRule `json:"memory" yaml:"memory"`
// Window is the window size reported by the resource metrics API. It should match the value used
// in your containerQuery and nodeQuery if you use a `rate` function.
Window pmodel.Duration `json:"window" yaml:"window"`
}
// ResourceRule describes how to query metrics for some particular
// system resource metric.
type ResourceRule struct {
// Container is the query used to fetch the metrics for containers.
ContainerQuery string `json:"containerQuery" yaml:"containerQuery"`
// NodeQuery is the query used to fetch the metrics for nodes
// (for instance, simply aggregating by node label is insufficient for
// cadvisor metrics -- you need to select the `/` container).
NodeQuery string `json:"nodeQuery" yaml:"nodeQuery"`
// Resources specifies how associated Kubernetes resources should be discovered for
// the given metrics.
Resources ResourceMapping `json:"resources" yaml:"resources"`
// ContainerLabel indicates the name of the Prometheus label containing the container name
// (since "container" is not a resource, this can't go in the `resources` block, but is similar).
ContainerLabel string `json:"containerLabel" yaml:"containerLabel"`
}

32
pkg/config/loader.go Normal file
View file

@ -0,0 +1,32 @@
package config
import (
"fmt"
"io"
"os"
yaml "gopkg.in/yaml.v2"
)
// FromFile loads the configuration from a particular file.
func FromFile(filename string) (*MetricsDiscoveryConfig, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("unable to load metrics discovery config file: %v", err)
}
defer file.Close()
contents, err := io.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("unable to load metrics discovery config file: %v", err)
}
return FromYAML(contents)
}
// FromYAML loads the configuration from a blob of YAML.
func FromYAML(contents []byte) (*MetricsDiscoveryConfig, error) {
var cfg MetricsDiscoveryConfig
if err := yaml.UnmarshalStrict(contents, &cfg); err != nil {
return nil, fmt.Errorf("unable to parse metrics discovery config: %v", err)
}
return &cfg, nil
}

View file

@ -1,343 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"fmt"
"strings"
"sync"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
"github.com/golang/glog"
pmodel "github.com/prometheus/common/model"
)
// NB: container metrics sourced from cAdvisor don't consistently follow naming conventions,
// so we need to whitelist them and handle them on a case-by-case basis. Metrics ending in `_total`
// *should* be counters, but may actually be guages in this case.
// SeriesType represents the kind of series backing a metric.
type SeriesType int
const (
CounterSeries SeriesType = iota
SecondsCounterSeries
GaugeSeries
)
// SeriesRegistry provides conversions between Prometheus series and MetricInfo
type SeriesRegistry interface {
// SetSeries replaces the known series in this registry
SetSeries(series []prom.Series) error
// ListAllMetrics lists all metrics known to this registry
ListAllMetrics() []provider.MetricInfo
// SeriesForMetric looks up the minimum required series information to make a query for the given metric
// against the given resource (namespace may be empty for non-namespaced resources)
QueryForMetric(info provider.MetricInfo, namespace string, resourceNames ...string) (kind SeriesType, query prom.Selector, groupBy string, found bool)
// MatchValuesToNames matches result values to resource names for the given metric and value set
MatchValuesToNames(metricInfo provider.MetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool)
}
type seriesInfo struct {
// baseSeries represents the minimum information to access a particular series
baseSeries prom.Series
// kind is the type of this series
kind SeriesType
// isContainer indicates if the series is a cAdvisor container_ metric, and thus needs special handling
isContainer bool
}
// overridableSeriesRegistry is a basic SeriesRegistry
type basicSeriesRegistry struct {
mu sync.RWMutex
// info maps metric info to information about the corresponding series
info map[provider.MetricInfo]seriesInfo
// metrics is the list of all known metrics
metrics []provider.MetricInfo
// namer is the metricNamer responsible for converting series to metric names and information
namer metricNamer
}
func (r *basicSeriesRegistry) SetSeries(newSeries []prom.Series) error {
newInfo := make(map[provider.MetricInfo]seriesInfo)
for _, series := range newSeries {
if strings.HasPrefix(series.Name, "container_") {
r.namer.processContainerSeries(series, newInfo)
} else if namespaceLabel, hasNamespaceLabel := series.Labels["namespace"]; hasNamespaceLabel && namespaceLabel != "" {
// we also handle namespaced metrics here as part of the resource-association logic
if err := r.namer.processNamespacedSeries(series, newInfo); err != nil {
glog.Errorf("Unable to process namespaced series %q: %v", series.Name, err)
continue
}
} else {
if err := r.namer.processRootScopedSeries(series, newInfo); err != nil {
glog.Errorf("Unable to process root-scoped series %q: %v", series.Name, err)
continue
}
}
}
newMetrics := make([]provider.MetricInfo, 0, len(newInfo))
for info := range newInfo {
newMetrics = append(newMetrics, info)
}
r.mu.Lock()
defer r.mu.Unlock()
r.info = newInfo
r.metrics = newMetrics
return nil
}
func (r *basicSeriesRegistry) ListAllMetrics() []provider.MetricInfo {
r.mu.RLock()
defer r.mu.RUnlock()
return r.metrics
}
func (r *basicSeriesRegistry) QueryForMetric(metricInfo provider.MetricInfo, namespace string, resourceNames ...string) (kind SeriesType, query prom.Selector, groupBy string, found bool) {
r.mu.RLock()
defer r.mu.RUnlock()
if len(resourceNames) == 0 {
glog.Errorf("no resource names requested while producing a query for metric %s", metricInfo.String())
return 0, "", "", false
}
metricInfo, singularResource, err := metricInfo.Normalized(r.namer.mapper)
if err != nil {
glog.Errorf("unable to normalize group resource while producing a query: %v", err)
return 0, "", "", false
}
// TODO: support container metrics
if info, found := r.info[metricInfo]; found {
targetValue := resourceNames[0]
matcher := prom.LabelEq
if len(resourceNames) > 1 {
targetValue = strings.Join(resourceNames, "|")
matcher = prom.LabelMatches
}
var expressions []string
if info.isContainer {
expressions = []string{matcher("pod_name", targetValue), prom.LabelNeq("container_name", "POD")}
groupBy = "pod_name"
} else {
// TODO: copy base series labels?
expressions = []string{matcher(singularResource, targetValue)}
groupBy = singularResource
}
if metricInfo.Namespaced {
expressions = append(expressions, prom.LabelEq("namespace", namespace))
}
return info.kind, prom.MatchSeries(info.baseSeries.Name, expressions...), groupBy, true
}
glog.V(10).Infof("metric %v not registered", metricInfo)
return 0, "", "", false
}
func (r *basicSeriesRegistry) MatchValuesToNames(metricInfo provider.MetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool) {
r.mu.RLock()
defer r.mu.RUnlock()
metricInfo, singularResource, err := metricInfo.Normalized(r.namer.mapper)
if err != nil {
glog.Errorf("unable to normalize group resource while matching values to names: %v", err)
return nil, false
}
if info, found := r.info[metricInfo]; found {
res := make(map[string]pmodel.SampleValue, len(values))
for _, val := range values {
if val == nil {
// skip empty values
continue
}
labelName := pmodel.LabelName(singularResource)
if info.isContainer {
labelName = pmodel.LabelName("pod_name")
}
res[string(val.Metric[labelName])] = val.Value
}
return res, true
}
return nil, false
}
// metricNamer knows how to construct MetricInfo out of raw prometheus series descriptions.
type metricNamer struct {
// overrides contains the list of container metrics whose naming we want to override.
// This is used to properly convert certain cAdvisor container metrics.
overrides map[string]seriesSpec
mapper apimeta.RESTMapper
}
// seriesSpec specifies how to produce metric info for a particular prometheus series source
type seriesSpec struct {
// metricName is the desired output API metric name
metricName string
// kind indicates whether or not this metric is cumulative,
// and thus has to be calculated as a rate when returning it
kind SeriesType
}
// processContainerSeries performs special work to extract metric definitions
// from cAdvisor-sourced container metrics, which don't particularly follow any useful conventions consistently.
func (n *metricNamer) processContainerSeries(series prom.Series, infos map[provider.MetricInfo]seriesInfo) {
originalName := series.Name
var name string
metricKind := GaugeSeries
if override, hasOverride := n.overrides[series.Name]; hasOverride {
name = override.metricName
metricKind = override.kind
} else {
// chop of the "container_" prefix
series.Name = series.Name[10:]
name, metricKind = n.metricNameFromSeries(series)
}
info := provider.MetricInfo{
GroupResource: schema.GroupResource{Resource: "pods"},
Namespaced: true,
Metric: name,
}
infos[info] = seriesInfo{
kind: metricKind,
baseSeries: prom.Series{Name: originalName},
isContainer: true,
}
}
// processNamespacedSeries adds the metric info for the given generic namespaced series to
// the map of metric info.
func (n *metricNamer) processNamespacedSeries(series prom.Series, infos map[provider.MetricInfo]seriesInfo) error {
// NB: all errors must occur *before* we save the series info
name, metricKind := n.metricNameFromSeries(series)
resources, err := n.groupResourcesFromSeries(series)
if err != nil {
return fmt.Errorf("unable to process prometheus series %s: %v", series.Name, err)
}
// we add one metric for each resource that this could describe
for _, resource := range resources {
info := provider.MetricInfo{
GroupResource: resource,
Namespaced: true,
Metric: name,
}
// metrics describing namespaces aren't considered to be namespaced
if resource == (schema.GroupResource{Resource: "namespaces"}) {
info.Namespaced = false
}
infos[info] = seriesInfo{
kind: metricKind,
baseSeries: prom.Series{Name: series.Name},
}
}
return nil
}
// processesRootScopedSeries adds the metric info for the given generic namespaced series to
// the map of metric info.
func (n *metricNamer) processRootScopedSeries(series prom.Series, infos map[provider.MetricInfo]seriesInfo) error {
// NB: all errors must occur *before* we save the series info
name, metricKind := n.metricNameFromSeries(series)
resources, err := n.groupResourcesFromSeries(series)
if err != nil {
return fmt.Errorf("unable to process prometheus series %s: %v", series.Name, err)
}
// we add one metric for each resource that this could describe
for _, resource := range resources {
info := provider.MetricInfo{
GroupResource: resource,
Namespaced: false,
Metric: name,
}
infos[info] = seriesInfo{
kind: metricKind,
baseSeries: prom.Series{Name: series.Name},
}
}
return nil
}
// groupResourceFromSeries collects the possible group-resources that this series could describe by
// going through each label, checking to see if it corresponds to a known resource. For instance,
// a series `ingress_http_hits_total{pod="foo",service="bar",ingress="baz",namespace="ns"}`
// would return three GroupResources: "pods", "services", and "ingresses".
// Returned MetricInfo is equilavent to the "normalized" info produced by metricInfo.Normalized.
func (n *metricNamer) groupResourcesFromSeries(series prom.Series) ([]schema.GroupResource, error) {
var res []schema.GroupResource
for label := range series.Labels {
// TODO: figure out a way to let people specify a fully-qualified name in label-form
gvr, err := n.mapper.ResourceFor(schema.GroupVersionResource{Resource: string(label)})
if err != nil {
if apimeta.IsNoMatchError(err) {
continue
}
return nil, err
}
res = append(res, gvr.GroupResource())
}
return res, nil
}
// metricNameFromSeries extracts a metric name from a series name, and indicates
// whether or not that series was a counter. It also has special logic to deal with time-based
// counters, which general get converted to milli-unit rate metrics.
func (n *metricNamer) metricNameFromSeries(series prom.Series) (name string, kind SeriesType) {
kind = GaugeSeries
name = series.Name
if strings.HasSuffix(name, "_total") {
kind = CounterSeries
name = name[:len(name)-6]
if strings.HasSuffix(name, "_seconds") {
kind = SecondsCounterSeries
name = name[:len(name)-8]
}
}
return
}

View file

@ -1,409 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"sort"
"testing"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider"
pmodel "github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
coreapi "k8s.io/api/core/v1"
extapi "k8s.io/api/extensions/v1beta1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
)
// restMapper creates a RESTMapper with just the types we need for
// these tests.
func restMapper() apimeta.RESTMapper {
mapper := apimeta.NewDefaultRESTMapper([]schema.GroupVersion{coreapi.SchemeGroupVersion}, apimeta.InterfacesForUnstructured)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Pod"), apimeta.RESTScopeNamespace)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Service"), apimeta.RESTScopeNamespace)
mapper.Add(extapi.SchemeGroupVersion.WithKind("Ingress"), apimeta.RESTScopeNamespace)
mapper.Add(extapi.SchemeGroupVersion.WithKind("Deployment"), apimeta.RESTScopeNamespace)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Node"), apimeta.RESTScopeRoot)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("PersistentVolume"), apimeta.RESTScopeRoot)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Namespace"), apimeta.RESTScopeRoot)
return mapper
}
func setupMetricNamer(t *testing.T) *metricNamer {
return &metricNamer{
overrides: map[string]seriesSpec{
"container_actually_gauge_seconds_total": {
metricName: "actually_gauge",
kind: GaugeSeries,
},
},
mapper: restMapper(),
}
}
func TestMetricNamerContainerSeries(t *testing.T) {
testCases := []struct {
input prom.Series
outputMetricName string
outputInfo seriesInfo
}{
{
input: prom.Series{
Name: "container_actually_gauge_seconds_total",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
outputMetricName: "actually_gauge",
outputInfo: seriesInfo{
baseSeries: prom.Series{Name: "container_actually_gauge_seconds_total"},
kind: GaugeSeries,
isContainer: true,
},
},
{
input: prom.Series{
Name: "container_some_usage",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
outputMetricName: "some_usage",
outputInfo: seriesInfo{
baseSeries: prom.Series{Name: "container_some_usage"},
kind: GaugeSeries,
isContainer: true,
},
},
{
input: prom.Series{
Name: "container_some_count_total",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
outputMetricName: "some_count",
outputInfo: seriesInfo{
baseSeries: prom.Series{Name: "container_some_count_total"},
kind: CounterSeries,
isContainer: true,
},
},
{
input: prom.Series{
Name: "container_some_time_seconds_total",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
outputMetricName: "some_time",
outputInfo: seriesInfo{
baseSeries: prom.Series{Name: "container_some_time_seconds_total"},
kind: SecondsCounterSeries,
isContainer: true,
},
},
}
assert := assert.New(t)
namer := setupMetricNamer(t)
resMap := map[provider.MetricInfo]seriesInfo{}
for _, test := range testCases {
namer.processContainerSeries(test.input, resMap)
metric := provider.MetricInfo{
Metric: test.outputMetricName,
GroupResource: schema.GroupResource{Resource: "pods"},
Namespaced: true,
}
if assert.Contains(resMap, metric) {
assert.Equal(test.outputInfo, resMap[metric])
}
}
}
func TestSeriesRegistry(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
namer := setupMetricNamer(t)
registry := &basicSeriesRegistry{
namer: *namer,
}
inputSeries := []prom.Series{
// container series
{
Name: "container_actually_gauge_seconds_total",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
{
Name: "container_some_usage",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
{
Name: "container_some_count_total",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
{
Name: "container_some_time_seconds_total",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
// namespaced series
// a series that should turn into multiple metrics
{
Name: "ingress_hits_total",
Labels: pmodel.LabelSet{"ingress": "someingress", "service": "somesvc", "pod": "backend1", "namespace": "somens"},
},
{
Name: "ingress_hits_total",
Labels: pmodel.LabelSet{"ingress": "someingress", "service": "somesvc", "pod": "backend2", "namespace": "somens"},
},
{
Name: "service_proxy_packets",
Labels: pmodel.LabelSet{"service": "somesvc", "namespace": "somens"},
},
{
Name: "work_queue_wait_seconds_total",
Labels: pmodel.LabelSet{"deployment": "somedep", "namespace": "somens"},
},
// non-namespaced series
{
Name: "node_gigawatts",
Labels: pmodel.LabelSet{"node": "somenode"},
},
{
Name: "volume_claims_total",
Labels: pmodel.LabelSet{"persistentvolume": "somepv"},
},
{
Name: "node_fan_seconds_total",
Labels: pmodel.LabelSet{"node": "somenode"},
},
// unrelated series
{
Name: "admin_coffee_liters_total",
Labels: pmodel.LabelSet{"admin": "some-admin"},
},
{
Name: "admin_unread_emails",
Labels: pmodel.LabelSet{"admin": "some-admin"},
},
{
Name: "admin_reddit_seconds_total",
Labels: pmodel.LabelSet{"admin": "some-admin"},
},
}
// set up the registry
require.NoError(registry.SetSeries(inputSeries))
// make sure each metric got registered and can form queries
testCases := []struct {
title string
info provider.MetricInfo
namespace string
resourceNames []string
expectedKind SeriesType
expectedQuery string
expectedGroupBy string
}{
// container metrics
{
title: "container metrics overrides / single resource name",
info: provider.MetricInfo{schema.GroupResource{Resource: "pods"}, true, "actually_gauge"},
namespace: "somens",
resourceNames: []string{"somepod"},
expectedKind: GaugeSeries,
expectedQuery: "container_actually_gauge_seconds_total{pod_name=\"somepod\",container_name!=\"POD\",namespace=\"somens\"}",
expectedGroupBy: "pod_name",
},
{
title: "container metrics gauge / multiple resource names",
info: provider.MetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_usage"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
expectedKind: GaugeSeries,
expectedQuery: "container_some_usage{pod_name=~\"somepod1|somepod2\",container_name!=\"POD\",namespace=\"somens\"}",
expectedGroupBy: "pod_name",
},
{
title: "container metrics counter",
info: provider.MetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_count"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
expectedKind: CounterSeries,
expectedQuery: "container_some_count_total{pod_name=~\"somepod1|somepod2\",container_name!=\"POD\",namespace=\"somens\"}",
expectedGroupBy: "pod_name",
},
{
title: "container metrics seconds counter",
info: provider.MetricInfo{schema.GroupResource{Resource: "pods"}, true, "some_time"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
expectedKind: SecondsCounterSeries,
expectedQuery: "container_some_time_seconds_total{pod_name=~\"somepod1|somepod2\",container_name!=\"POD\",namespace=\"somens\"}",
expectedGroupBy: "pod_name",
},
// namespaced metrics
{
title: "namespaced metrics counter / multidimensional (service)",
info: provider.MetricInfo{schema.GroupResource{Resource: "service"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somesvc"},
expectedKind: CounterSeries,
expectedQuery: "ingress_hits_total{service=\"somesvc\",namespace=\"somens\"}",
},
{
title: "namespaced metrics counter / multidimensional (ingress)",
info: provider.MetricInfo{schema.GroupResource{Group: "extensions", Resource: "ingress"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"someingress"},
expectedKind: CounterSeries,
expectedQuery: "ingress_hits_total{ingress=\"someingress\",namespace=\"somens\"}",
},
{
title: "namespaced metrics counter / multidimensional (pod)",
info: provider.MetricInfo{schema.GroupResource{Resource: "pod"}, true, "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somepod"},
expectedKind: CounterSeries,
expectedQuery: "ingress_hits_total{pod=\"somepod\",namespace=\"somens\"}",
},
{
title: "namespaced metrics gauge",
info: provider.MetricInfo{schema.GroupResource{Resource: "service"}, true, "service_proxy_packets"},
namespace: "somens",
resourceNames: []string{"somesvc"},
expectedKind: GaugeSeries,
expectedQuery: "service_proxy_packets{service=\"somesvc\",namespace=\"somens\"}",
},
{
title: "namespaced metrics seconds counter",
info: provider.MetricInfo{schema.GroupResource{Group: "extensions", Resource: "deployment"}, true, "work_queue_wait"},
namespace: "somens",
resourceNames: []string{"somedep"},
expectedKind: SecondsCounterSeries,
expectedQuery: "work_queue_wait_seconds_total{deployment=\"somedep\",namespace=\"somens\"}",
},
// non-namespaced series
{
title: "root scoped metrics gauge",
info: provider.MetricInfo{schema.GroupResource{Resource: "node"}, false, "node_gigawatts"},
resourceNames: []string{"somenode"},
expectedKind: GaugeSeries,
expectedQuery: "node_gigawatts{node=\"somenode\"}",
},
{
title: "root scoped metrics counter",
info: provider.MetricInfo{schema.GroupResource{Resource: "persistentvolume"}, false, "volume_claims"},
resourceNames: []string{"somepv"},
expectedKind: CounterSeries,
expectedQuery: "volume_claims_total{persistentvolume=\"somepv\"}",
},
{
title: "root scoped metrics seconds counter",
info: provider.MetricInfo{schema.GroupResource{Resource: "node"}, false, "node_fan"},
resourceNames: []string{"somenode"},
expectedKind: SecondsCounterSeries,
expectedQuery: "node_fan_seconds_total{node=\"somenode\"}",
},
}
for _, testCase := range testCases {
outputKind, outputQuery, groupBy, found := registry.QueryForMetric(testCase.info, testCase.namespace, testCase.resourceNames...)
if !assert.True(found, "%s: metric %v should available", testCase.title, testCase.info) {
continue
}
assert.Equal(testCase.expectedKind, outputKind, "%s: metric %v should have had the right series type", testCase.title, testCase.info)
assert.Equal(prom.Selector(testCase.expectedQuery), outputQuery, "%s: metric %v should have produced the correct query for %v in namespace %s", testCase.title, testCase.info, testCase.resourceNames, testCase.namespace)
expectedGroupBy := testCase.expectedGroupBy
if expectedGroupBy == "" {
expectedGroupBy = testCase.info.GroupResource.Resource
}
assert.Equal(expectedGroupBy, groupBy, "%s: metric %v should have produced the correct groupBy clause", testCase.title)
}
allMetrics := registry.ListAllMetrics()
expectedMetrics := []provider.MetricInfo{
{schema.GroupResource{Resource: "pods"}, true, "actually_gauge"},
{schema.GroupResource{Resource: "pods"}, true, "some_usage"},
{schema.GroupResource{Resource: "pods"}, true, "some_count"},
{schema.GroupResource{Resource: "pods"}, true, "some_time"},
{schema.GroupResource{Resource: "services"}, true, "ingress_hits"},
{schema.GroupResource{Group: "extensions", Resource: "ingresses"}, true, "ingress_hits"},
{schema.GroupResource{Resource: "pods"}, true, "ingress_hits"},
{schema.GroupResource{Resource: "namespaces"}, false, "ingress_hits"},
{schema.GroupResource{Resource: "services"}, true, "service_proxy_packets"},
{schema.GroupResource{Resource: "namespaces"}, false, "service_proxy_packets"},
{schema.GroupResource{Group: "extensions", Resource: "deployments"}, true, "work_queue_wait"},
{schema.GroupResource{Resource: "namespaces"}, false, "work_queue_wait"},
{schema.GroupResource{Resource: "nodes"}, false, "node_gigawatts"},
{schema.GroupResource{Resource: "persistentvolumes"}, false, "volume_claims"},
{schema.GroupResource{Resource: "nodes"}, false, "node_fan"},
}
// sort both for easy comparison
sort.Sort(metricInfoSorter(allMetrics))
sort.Sort(metricInfoSorter(expectedMetrics))
assert.Equal(expectedMetrics, allMetrics, "should have listed all expected metrics")
}
// metricInfoSorter is a sort.Interface for sorting provider.MetricInfos
type metricInfoSorter []provider.MetricInfo
func (s metricInfoSorter) Len() int {
return len(s)
}
func (s metricInfoSorter) Less(i, j int) bool {
infoI := s[i]
infoJ := s[j]
if infoI.Metric == infoJ.Metric {
if infoI.GroupResource == infoJ.GroupResource {
return infoI.Namespaced
}
if infoI.GroupResource.Group == infoJ.GroupResource.Group {
return infoI.GroupResource.Resource < infoJ.GroupResource.Resource
}
return infoI.GroupResource.Group < infoJ.GroupResource.Group
}
return infoI.Metric < infoJ.Metric
}
func (s metricInfoSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

View file

@ -19,110 +19,118 @@ package provider
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/golang/glog" "math"
"time" "time"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider"
pmodel "github.com/prometheus/common/model" pmodel "github.com/prometheus/common/model"
apierr "k8s.io/apimachinery/pkg/api/errors" apierr "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta" apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/klog/v2"
"k8s.io/metrics/pkg/apis/custom_metrics" "k8s.io/metrics/pkg/apis/custom_metrics"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" "sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/helpers"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
) )
// Runnable represents something that can be run until told to stop.
type Runnable interface {
// Run runs the runnable forever.
Run()
// RunUntil runs the runnable until the given channel is closed.
RunUntil(stopChan <-chan struct{})
}
type prometheusProvider struct { type prometheusProvider struct {
mapper apimeta.RESTMapper mapper apimeta.RESTMapper
kubeClient dynamic.ClientPool kubeClient dynamic.Interface
promClient prom.Client promClient prom.Client
SeriesRegistry SeriesRegistry
rateInterval time.Duration
} }
func NewPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.ClientPool, promClient prom.Client, updateInterval time.Duration, rateInterval time.Duration, stopChan <-chan struct{}) provider.CustomMetricsProvider { func NewPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.Interface, promClient prom.Client, namers []naming.MetricNamer, updateInterval time.Duration, maxAge time.Duration) (provider.CustomMetricsProvider, Runnable) {
lister := &cachingMetricsLister{ lister := &cachingMetricsLister{
updateInterval: updateInterval, updateInterval: updateInterval,
maxAge: maxAge,
promClient: promClient, promClient: promClient,
namers: namers,
SeriesRegistry: &basicSeriesRegistry{ SeriesRegistry: &basicSeriesRegistry{
namer: metricNamer{ mapper: mapper,
// TODO: populate the overrides list
overrides: nil,
mapper: mapper,
},
}, },
} }
lister.RunUntil(stopChan)
return &prometheusProvider{ return &prometheusProvider{
mapper: mapper, mapper: mapper,
kubeClient: kubeClient, kubeClient: kubeClient,
promClient: promClient, promClient: promClient,
SeriesRegistry: lister, SeriesRegistry: lister,
}, lister
rateInterval: rateInterval,
}
} }
func (p *prometheusProvider) metricFor(value pmodel.SampleValue, groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) { func (p *prometheusProvider) metricFor(value pmodel.SampleValue, name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
kind, err := p.mapper.KindFor(groupResource.WithVersion("")) ref, err := helpers.ReferenceFor(p.mapper, name, info)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &custom_metrics.MetricValue{ var q *resource.Quantity
DescribedObject: custom_metrics.ObjectReference{ if math.IsNaN(float64(value)) {
APIVersion: groupResource.Group + "/" + runtime.APIVersionInternal, q = resource.NewQuantity(0, resource.DecimalSI)
Kind: kind.Kind, } else {
Name: name, q = resource.NewMilliQuantity(int64(value*1000.0), resource.DecimalSI)
Namespace: namespace,
},
MetricName: metricName,
Timestamp: metav1.Time{time.Now()},
Value: *resource.NewMilliQuantity(int64(value*1000.0), resource.DecimalSI),
}, nil
}
func (p *prometheusProvider) metricsFor(valueSet pmodel.Vector, info provider.MetricInfo, list runtime.Object) (*custom_metrics.MetricValueList, error) {
if !apimeta.IsListType(list) {
return nil, apierr.NewInternalError(fmt.Errorf("result of label selector list operation was not a list"))
} }
metric := &custom_metrics.MetricValue{
DescribedObject: ref,
Metric: custom_metrics.MetricIdentifier{
Name: info.Metric,
},
// TODO(directxman12): use the right timestamp
Timestamp: metav1.Time{Time: time.Now()},
Value: *q,
}
if !metricSelector.Empty() {
sel, err := metav1.ParseToLabelSelector(metricSelector.String())
if err != nil {
return nil, err
}
metric.Metric.Selector = sel
}
return metric, nil
}
func (p *prometheusProvider) metricsFor(valueSet pmodel.Vector, namespace string, names []string, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
values, found := p.MatchValuesToNames(info, valueSet) values, found := p.MatchValuesToNames(info, valueSet)
if !found { if !found {
return nil, provider.NewMetricNotFoundError(info.GroupResource, info.Metric) return nil, provider.NewMetricNotFoundError(info.GroupResource, info.Metric)
} }
res := []custom_metrics.MetricValue{} res := []custom_metrics.MetricValue{}
err := apimeta.EachListItem(list, func(item runtime.Object) error { for _, name := range names {
objUnstructured := item.(*unstructured.Unstructured) if _, found := values[name]; !found {
objName := objUnstructured.GetName() continue
if _, found := values[objName]; !found {
return nil
} }
value, err := p.metricFor(values[objName], info.GroupResource, objUnstructured.GetNamespace(), objName, info.Metric)
value, err := p.metricFor(values[name], types.NamespacedName{Namespace: namespace, Name: name}, info, metricSelector)
if err != nil { if err != nil {
return err return nil, err
} }
res = append(res, *value) res = append(res, *value)
return nil
})
if err != nil {
return nil, err
} }
return &custom_metrics.MetricValueList{ return &custom_metrics.MetricValueList{
@ -130,52 +138,38 @@ func (p *prometheusProvider) metricsFor(valueSet pmodel.Vector, info provider.Me
}, nil }, nil
} }
func (p *prometheusProvider) buildQuery(info provider.MetricInfo, namespace string, names ...string) (pmodel.Vector, error) { func (p *prometheusProvider) buildQuery(ctx context.Context, info provider.CustomMetricInfo, namespace string, metricSelector labels.Selector, names ...string) (pmodel.Vector, error) {
kind, baseQuery, groupBy, found := p.QueryForMetric(info, namespace, names...) query, found := p.QueryForMetric(info, namespace, metricSelector, names...)
if !found { if !found {
return nil, provider.NewMetricNotFoundError(info.GroupResource, info.Metric) return nil, provider.NewMetricNotFoundError(info.GroupResource, info.Metric)
} }
fullQuery := baseQuery
switch kind {
case CounterSeries:
fullQuery = prom.Selector(fmt.Sprintf("rate(%s[%s])", baseQuery, pmodel.Duration(p.rateInterval).String()))
case SecondsCounterSeries:
// TODO: futher modify for seconds?
fullQuery = prom.Selector(prom.Selector(fmt.Sprintf("rate(%s[%s])", baseQuery, pmodel.Duration(p.rateInterval).String())))
}
// NB: too small of a rate interval will return no results...
// sum over all other dimensions of this query (e.g. if we select on route, sum across all pods,
// but if we select on pods, sum across all routes), and split by the dimension of our resource
// TODO: return/populate the by list in SeriesForMetric
fullQuery = prom.Selector(fmt.Sprintf("sum(%s) by (%s)", fullQuery, groupBy))
// TODO: use an actual context // TODO: use an actual context
queryResults, err := p.promClient.Query(context.Background(), pmodel.Now(), fullQuery) queryResults, err := p.promClient.Query(ctx, pmodel.Now(), query)
if err != nil { if err != nil {
glog.Errorf("unable to fetch metrics from prometheus: %v", err) klog.Errorf("unable to fetch metrics from prometheus: %v", err)
// don't leak implementation details to the user // don't leak implementation details to the user
return nil, apierr.NewInternalError(fmt.Errorf("unable to fetch metrics")) return nil, apierr.NewInternalError(fmt.Errorf("unable to fetch metrics"))
} }
if queryResults.Type != pmodel.ValVector { if queryResults.Type != pmodel.ValVector {
glog.Errorf("unexpected results from prometheus: expected %s, got %s on results %v", pmodel.ValVector, queryResults.Type, queryResults) klog.Errorf("unexpected results from prometheus: expected %s, got %s on results %v", pmodel.ValVector, queryResults.Type, queryResults)
return nil, apierr.NewInternalError(fmt.Errorf("unable to fetch metrics")) return nil, apierr.NewInternalError(fmt.Errorf("unable to fetch metrics"))
} }
return *queryResults.Vector, nil return *queryResults.Vector, nil
} }
func (p *prometheusProvider) getSingle(info provider.MetricInfo, namespace, name string) (*custom_metrics.MetricValue, error) { func (p *prometheusProvider) GetMetricByName(ctx context.Context, name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
queryResults, err := p.buildQuery(info, namespace, name) // construct a query
queryResults, err := p.buildQuery(ctx, info, name.Namespace, metricSelector, name.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// associate the metrics
if len(queryResults) < 1 { if len(queryResults) < 1 {
return nil, provider.NewMetricNotFoundForError(info.GroupResource, info.Metric, name) return nil, provider.NewMetricNotFoundForError(info.GroupResource, info.Metric, name.Name)
} }
namedValues, found := p.MatchValuesToNames(info, queryResults) namedValues, found := p.MatchValuesToNames(info, queryResults)
@ -184,99 +178,36 @@ func (p *prometheusProvider) getSingle(info provider.MetricInfo, namespace, name
} }
if len(namedValues) > 1 { if len(namedValues) > 1 {
glog.V(2).Infof("Got more than one result (%v results) when fetching metric %s for %q, using the first one with a matching name...", len(queryResults), info.String(), name) klog.V(2).Infof("Got more than one result (%v results) when fetching metric %s for %q, using the first one with a matching name...", len(queryResults), info.String(), name)
} }
resultValue, nameFound := namedValues[name] resultValue, nameFound := namedValues[name.Name]
if !nameFound { if !nameFound {
glog.Errorf("None of the results returned by when fetching metric %s for %q matched the resource name", info.String(), name) klog.Errorf("None of the results returned by when fetching metric %s for %q matched the resource name", info.String(), name)
return nil, provider.NewMetricNotFoundForError(info.GroupResource, info.Metric, name) return nil, provider.NewMetricNotFoundForError(info.GroupResource, info.Metric, name.Name)
} }
return p.metricFor(resultValue, info.GroupResource, "", name, info.Metric) // return the resulting metric
return p.metricFor(resultValue, name, info, metricSelector)
} }
func (p *prometheusProvider) getMultiple(info provider.MetricInfo, namespace string, selector labels.Selector) (*custom_metrics.MetricValueList, error) { func (p *prometheusProvider) GetMetricBySelector(ctx context.Context, namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
// construct a client to list the names of objects matching the label selector // fetch a list of relevant resource names
client, err := p.kubeClient.ClientForGroupVersionResource(info.GroupResource.WithVersion("")) resourceNames, err := helpers.ListObjectNames(p.mapper, p.kubeClient, namespace, selector, info)
if err != nil { if err != nil {
glog.Errorf("unable to construct dynamic client to list matching resource names: %v", err) klog.Errorf("unable to list matching resource names: %v", err)
// don't leak implementation details to the user // don't leak implementation details to the user
return nil, apierr.NewInternalError(fmt.Errorf("unable to list matching resources")) return nil, apierr.NewInternalError(fmt.Errorf("unable to list matching resources"))
} }
// we can construct a this APIResource ourself, since the dynamic client only uses Name and Namespaced
apiRes := &metav1.APIResource{
Name: info.GroupResource.Resource,
Namespaced: info.Namespaced,
}
// actually list the objects matching the label selector
matchingObjectsRaw, err := client.Resource(apiRes, namespace).
List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
glog.Errorf("unable to list matching resource names: %v", err)
// don't leak implementation details to the user
return nil, apierr.NewInternalError(fmt.Errorf("unable to list matching resources"))
}
// make sure we have a list
if !apimeta.IsListType(matchingObjectsRaw) {
return nil, apierr.NewInternalError(fmt.Errorf("result of label selector list operation was not a list"))
}
// convert a list of objects into the corresponding list of names
resourceNames := []string{}
err = apimeta.EachListItem(matchingObjectsRaw, func(item runtime.Object) error {
objName := item.(*unstructured.Unstructured).GetName()
resourceNames = append(resourceNames, objName)
return nil
})
// construct the actual query // construct the actual query
queryResults, err := p.buildQuery(info, namespace, resourceNames...) queryResults, err := p.buildQuery(ctx, info, namespace, metricSelector, resourceNames...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return p.metricsFor(queryResults, info, matchingObjectsRaw)
}
func (p *prometheusProvider) GetRootScopedMetricByName(groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValue, error) { // return the resulting metrics
info := provider.MetricInfo{ return p.metricsFor(queryResults, namespace, resourceNames, info, metricSelector)
GroupResource: groupResource,
Metric: metricName,
Namespaced: false,
}
return p.getSingle(info, "", name)
}
func (p *prometheusProvider) GetRootScopedMetricBySelector(groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
info := provider.MetricInfo{
GroupResource: groupResource,
Metric: metricName,
Namespaced: false,
}
return p.getMultiple(info, "", selector)
}
func (p *prometheusProvider) GetNamespacedMetricByName(groupResource schema.GroupResource, namespace string, name string, metricName string) (*custom_metrics.MetricValue, error) {
info := provider.MetricInfo{
GroupResource: groupResource,
Metric: metricName,
Namespaced: true,
}
return p.getSingle(info, namespace, name)
}
func (p *prometheusProvider) GetNamespacedMetricBySelector(groupResource schema.GroupResource, namespace string, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
info := provider.MetricInfo{
GroupResource: groupResource,
Metric: metricName,
Namespaced: true,
}
return p.getMultiple(info, namespace, selector)
} }
type cachingMetricsLister struct { type cachingMetricsLister struct {
@ -284,6 +215,8 @@ type cachingMetricsLister struct {
promClient prom.Client promClient prom.Client
updateInterval time.Duration updateInterval time.Duration
maxAge time.Duration
namers []naming.MetricNamer
} }
func (l *cachingMetricsLister) Run() { func (l *cachingMetricsLister) Run() {
@ -298,23 +231,65 @@ func (l *cachingMetricsLister) RunUntil(stopChan <-chan struct{}) {
}, l.updateInterval, stopChan) }, l.updateInterval, stopChan)
} }
type selectorSeries struct {
selector prom.Selector
series []prom.Series
}
func (l *cachingMetricsLister) updateMetrics() error { func (l *cachingMetricsLister) updateMetrics() error {
startTime := pmodel.Now().Add(-1 * l.updateInterval) startTime := pmodel.Now().Add(-1 * l.maxAge)
// container-specific metrics from cAdvsior have their own form, and need special handling // don't do duplicate queries when it's just the matchers that change
containerSel := prom.MatchSeries("", prom.NameMatches("^container_.*"), prom.LabelNeq("container_name", "POD"), prom.LabelNeq("namespace", ""), prom.LabelNeq("pod_name", "")) seriesCacheByQuery := make(map[prom.Selector][]prom.Series)
namespacedSel := prom.MatchSeries("", prom.LabelNeq("namespace", ""), prom.NameNotMatches("^container_.*"))
// TODO: figure out how to determine which metrics on non-namespaced objects are kubernetes-related
// TODO: use an actual context here // these can take a while on large clusters, so launch in parallel
series, err := l.promClient.Series(context.Background(), pmodel.Interval{startTime, 0}, containerSel, namespacedSel) // and don't duplicate
if err != nil { selectors := make(map[prom.Selector]struct{})
return fmt.Errorf("unable to update list of all available metrics: %v", err) selectorSeriesChan := make(chan selectorSeries, len(l.namers))
errs := make(chan error, len(l.namers))
for _, namer := range l.namers {
sel := namer.Selector()
if _, ok := selectors[sel]; ok {
errs <- nil
selectorSeriesChan <- selectorSeries{}
continue
}
selectors[sel] = struct{}{}
go func() {
series, err := l.promClient.Series(context.TODO(), pmodel.Interval{Start: startTime, End: 0}, sel)
if err != nil {
errs <- fmt.Errorf("unable to fetch metrics for query %q: %v", sel, err)
return
}
errs <- nil
selectorSeriesChan <- selectorSeries{
selector: sel,
series: series,
}
}()
} }
glog.V(10).Infof("Set available metric list from Prometheus to: %v", series) // iterate through, blocking until we've got all results
for range l.namers {
if err := <-errs; err != nil {
return fmt.Errorf("unable to update list of all metrics: %v", err)
}
if ss := <-selectorSeriesChan; ss.series != nil {
seriesCacheByQuery[ss.selector] = ss.series
}
}
close(errs)
l.SetSeries(series) newSeries := make([][]prom.Series, len(l.namers))
for i, namer := range l.namers {
series, cached := seriesCacheByQuery[namer.Selector()]
if !cached {
return fmt.Errorf("unable to update list of all metrics: no metrics retrieved for query %q", namer.Selector())
}
newSeries[i] = namer.FilterSeries(series)
}
return nil klog.V(10).Infof("Set available metric list from Prometheus to: %v", newSeries)
return l.SetSeries(newSeries, l.namers)
} }

View file

@ -0,0 +1,29 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestProvider(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Custom Metrics Provider Suite")
}

View file

@ -17,92 +17,43 @@ limitations under the License.
package provider package provider
import ( import (
"context"
"fmt"
"sort"
"testing"
"time" "time"
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" . "github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert" . "github.com/onsi/gomega"
"github.com/stretchr/testify/require" pmodel "github.com/prometheus/common/model"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
fakedyn "k8s.io/client-go/dynamic/fake" fakedyn "k8s.io/client-go/dynamic/fake"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" "sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
pmodel "github.com/prometheus/common/model"
config "sigs.k8s.io/prometheus-adapter/cmd/config-gen/utils"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
fakeprom "sigs.k8s.io/prometheus-adapter/pkg/client/fake"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
) )
const fakeProviderUpdateInterval = 2 * time.Second const fakeProviderUpdateInterval = 2 * time.Second
const fakeProviderStartDuration = 2 * time.Second
// fakePromClient is a fake instance of prom.Client func setupPrometheusProvider() (provider.CustomMetricsProvider, *fakeprom.FakePrometheusClient) {
type fakePromClient struct { fakeProm := &fakeprom.FakePrometheusClient{}
// acceptibleInterval is the interval in which to return queries fakeKubeClient := &fakedyn.FakeDynamicClient{}
acceptibleInterval pmodel.Interval
// errQueries are queries that result in an error (whether from Query or Series)
errQueries map[prom.Selector]error
// series are non-error responses to partial Series calls
series map[prom.Selector][]prom.Series
// queryResults are non-error responses to Query
queryResults map[prom.Selector]prom.QueryResult
}
func (c *fakePromClient) Series(_ context.Context, interval pmodel.Interval, selectors ...prom.Selector) ([]prom.Series, error) { cfg := config.DefaultConfig(1*time.Minute, "")
if (interval.Start != 0 && interval.Start < c.acceptibleInterval.Start) || (interval.End != 0 && interval.End > c.acceptibleInterval.End) { namers, err := naming.NamersFromConfig(cfg.Rules, restMapper())
return nil, fmt.Errorf("interval [%v, %v] for query is outside range [%v, %v]", interval.Start, interval.End, c.acceptibleInterval.Start, c.acceptibleInterval.End) Expect(err).NotTo(HaveOccurred())
}
res := []prom.Series{}
for _, sel := range selectors {
if err, found := c.errQueries[sel]; found {
return nil, err
}
if series, found := c.series[sel]; found {
res = append(res, series...)
}
}
return res, nil prov, _ := NewPrometheusProvider(restMapper(), fakeKubeClient, fakeProm, namers, fakeProviderUpdateInterval, fakeProviderStartDuration)
}
func (c *fakePromClient) Query(_ context.Context, t pmodel.Time, query prom.Selector) (prom.QueryResult, error) { containerSel := prom.MatchSeries("", prom.NameMatches("^container_.*"), prom.LabelNeq("container", "POD"), prom.LabelNeq("namespace", ""), prom.LabelNeq("pod", ""))
if t < c.acceptibleInterval.Start || t > c.acceptibleInterval.End {
return prom.QueryResult{}, fmt.Errorf("time %v for query is outside range [%v, %v]", t, c.acceptibleInterval.Start, c.acceptibleInterval.End)
}
if err, found := c.errQueries[query]; found {
return prom.QueryResult{}, err
}
if res, found := c.queryResults[query]; found {
return res, nil
}
return prom.QueryResult{
Type: pmodel.ValVector,
Vector: &pmodel.Vector{},
}, nil
}
func (c *fakePromClient) QueryRange(_ context.Context, r prom.Range, query prom.Selector) (prom.QueryResult, error) {
return prom.QueryResult{}, nil
}
func setupPrometheusProvider(t *testing.T, stopCh <-chan struct{}) (provider.CustomMetricsProvider, *fakePromClient) {
fakeProm := &fakePromClient{}
fakeKubeClient := &fakedyn.FakeClientPool{}
prov := NewPrometheusProvider(restMapper(), fakeKubeClient, fakeProm, fakeProviderUpdateInterval, 1*time.Minute, stopCh)
containerSel := prom.MatchSeries("", prom.NameMatches("^container_.*"), prom.LabelNeq("container_name", "POD"), prom.LabelNeq("namespace", ""), prom.LabelNeq("pod_name", ""))
namespacedSel := prom.MatchSeries("", prom.LabelNeq("namespace", ""), prom.NameNotMatches("^container_.*")) namespacedSel := prom.MatchSeries("", prom.LabelNeq("namespace", ""), prom.NameNotMatches("^container_.*"))
fakeProm.series = map[prom.Selector][]prom.Series{ fakeProm.SeriesResults = map[prom.Selector][]prom.Series{
containerSel: { containerSel: {
{
Name: "container_actually_gauge_seconds_total",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"},
},
{ {
Name: "container_some_usage", Name: "container_some_usage",
Labels: pmodel.LabelSet{"pod_name": "somepod", "namespace": "somens", "container_name": "somecont"}, Labels: pmodel.LabelSet{"pod": "somepod", "namespace": "somens", "container": "somecont"},
}, },
}, },
namespacedSel: { namespacedSel: {
@ -128,41 +79,35 @@ func setupPrometheusProvider(t *testing.T, stopCh <-chan struct{}) (provider.Cus
return prov, fakeProm return prov, fakeProm
} }
func TestListAllMetrics(t *testing.T) { var _ = Describe("Custom Metrics Provider", func() {
// setup It("should be able to list all metrics", func() {
stopCh := make(chan struct{}) By("setting up the provider")
defer close(stopCh) prov, fakeProm := setupPrometheusProvider()
prov, fakeProm := setupPrometheusProvider(t, stopCh)
// assume we have no updates By("ensuring that no metrics are present before we start listing")
require.Len(t, prov.ListAllMetrics(), 0, "assume: should have no metrics updates at the start") Expect(prov.ListAllMetrics()).To(BeEmpty())
// set the acceptible interval (now until the next update, with a bit of wiggle room) By("setting the acceptable interval to now until the next update, with a bit of wiggle room")
startTime := pmodel.Now() startTime := pmodel.Now().Add(-1*fakeProviderUpdateInterval - fakeProviderUpdateInterval/10)
endTime := startTime.Add(fakeProviderUpdateInterval + fakeProviderUpdateInterval/10) fakeProm.AcceptableInterval = pmodel.Interval{Start: startTime, End: 0}
fakeProm.acceptibleInterval = pmodel.Interval{Start: startTime, End: endTime}
// wait one update interval (with a bit of wiggle room) By("updating the list of available metrics")
time.Sleep(fakeProviderUpdateInterval + fakeProviderUpdateInterval/10) // don't call RunUntil to avoid timing issue
lister := prov.(*prometheusProvider).SeriesRegistry.(*cachingMetricsLister)
Expect(lister.updateMetrics()).To(Succeed())
// list/sort the metrics By("listing all metrics, and checking that they contain the expected results")
actualMetrics := prov.ListAllMetrics() Expect(prov.ListAllMetrics()).To(ConsistOf(
sort.Sort(metricInfoSorter(actualMetrics)) provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "services"}, Namespaced: true, Metric: "ingress_hits"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Group: "extensions", Resource: "ingresses"}, Namespaced: true, Metric: "ingress_hits"},
expectedMetrics := []provider.MetricInfo{ provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "ingress_hits"},
{schema.GroupResource{Resource: "pods"}, true, "actually_gauge"}, provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "ingress_hits"},
{schema.GroupResource{Resource: "pods"}, true, "some_usage"}, provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "services"}, Namespaced: true, Metric: "service_proxy_packets"},
{schema.GroupResource{Resource: "services"}, true, "ingress_hits"}, provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "service_proxy_packets"},
{schema.GroupResource{Group: "extensions", Resource: "ingresses"}, true, "ingress_hits"}, provider.CustomMetricInfo{GroupResource: schema.GroupResource{Group: "extensions", Resource: "deployments"}, Namespaced: true, Metric: "work_queue_wait"},
{schema.GroupResource{Resource: "pods"}, true, "ingress_hits"}, provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "work_queue_wait"},
{schema.GroupResource{Resource: "namespaces"}, false, "ingress_hits"}, provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "some_usage"},
{schema.GroupResource{Resource: "services"}, true, "service_proxy_packets"}, provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "some_usage"},
{schema.GroupResource{Resource: "namespaces"}, false, "service_proxy_packets"}, ))
{schema.GroupResource{Group: "extensions", Resource: "deployments"}, true, "work_queue_wait"}, })
{schema.GroupResource{Resource: "namespaces"}, false, "work_queue_wait"}, })
}
sort.Sort(metricInfoSorter(expectedMetrics))
// assert that we got what we expected
assert.Equal(t, expectedMetrics, actualMetrics)
}

View file

@ -0,0 +1,202 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"fmt"
"sync"
pmodel "github.com/prometheus/common/model"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/klog/v2"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
)
// NB: container metrics sourced from cAdvisor don't consistently follow naming conventions,
// so we need to whitelist them and handle them on a case-by-case basis. Metrics ending in `_total`
// *should* be counters, but may actually be guages in this case.
// SeriesType represents the kind of series backing a metric.
type SeriesType int
const (
CounterSeries SeriesType = iota
SecondsCounterSeries
GaugeSeries
)
// SeriesRegistry provides conversions between Prometheus series and MetricInfo
type SeriesRegistry interface {
// SetSeries replaces the known series in this registry.
// Each slice in series should correspond to a MetricNamer in namers.
SetSeries(series [][]prom.Series, namers []naming.MetricNamer) error
// ListAllMetrics lists all metrics known to this registry
ListAllMetrics() []provider.CustomMetricInfo
// SeriesForMetric looks up the minimum required series information to make a query for the given metric
// against the given resource (namespace may be empty for non-namespaced resources)
QueryForMetric(info provider.CustomMetricInfo, namespace string, metricSelector labels.Selector, resourceNames ...string) (query prom.Selector, found bool)
// MatchValuesToNames matches result values to resource names for the given metric and value set
MatchValuesToNames(metricInfo provider.CustomMetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool)
}
type seriesInfo struct {
// seriesName is the name of the corresponding Prometheus series
seriesName string
// namer is the MetricNamer used to name this series
namer naming.MetricNamer
}
// overridableSeriesRegistry is a basic SeriesRegistry
type basicSeriesRegistry struct {
mu sync.RWMutex
// info maps metric info to information about the corresponding series
info map[provider.CustomMetricInfo]seriesInfo
// metrics is the list of all known metrics
metrics []provider.CustomMetricInfo
mapper apimeta.RESTMapper
}
func (r *basicSeriesRegistry) SetSeries(newSeriesSlices [][]prom.Series, namers []naming.MetricNamer) error {
if len(newSeriesSlices) != len(namers) {
return fmt.Errorf("need one set of series per namer")
}
newInfo := make(map[provider.CustomMetricInfo]seriesInfo)
for i, newSeries := range newSeriesSlices {
namer := namers[i]
for _, series := range newSeries {
// TODO: warn if it doesn't match any resources
resources, namespaced := namer.ResourcesForSeries(series)
name, err := namer.MetricNameForSeries(series)
if err != nil {
klog.Errorf("unable to name series %q, skipping: %v", series.String(), err)
continue
}
for _, resource := range resources {
info := provider.CustomMetricInfo{
GroupResource: resource,
Namespaced: namespaced,
Metric: name,
}
// some metrics aren't counted as namespaced
if resource == naming.NsGroupResource || resource == naming.NodeGroupResource || resource == naming.PVGroupResource {
info.Namespaced = false
}
// we don't need to re-normalize, because the metric namer should have already normalized for us
newInfo[info] = seriesInfo{
seriesName: series.Name,
namer: namer,
}
}
}
}
// regenerate metrics
newMetrics := make([]provider.CustomMetricInfo, 0, len(newInfo))
for info := range newInfo {
newMetrics = append(newMetrics, info)
}
r.mu.Lock()
defer r.mu.Unlock()
r.info = newInfo
r.metrics = newMetrics
return nil
}
func (r *basicSeriesRegistry) ListAllMetrics() []provider.CustomMetricInfo {
r.mu.RLock()
defer r.mu.RUnlock()
return r.metrics
}
func (r *basicSeriesRegistry) QueryForMetric(metricInfo provider.CustomMetricInfo, namespace string, metricSelector labels.Selector, resourceNames ...string) (prom.Selector, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
if len(resourceNames) == 0 {
klog.Errorf("no resource names requested while producing a query for metric %s", metricInfo.String())
return "", false
}
metricInfo, _, err := metricInfo.Normalized(r.mapper)
if err != nil {
klog.Errorf("unable to normalize group resource while producing a query: %v", err)
return "", false
}
info, infoFound := r.info[metricInfo]
if !infoFound {
klog.V(10).Infof("metric %v not registered", metricInfo)
return "", false
}
query, err := info.namer.QueryForSeries(info.seriesName, metricInfo.GroupResource, namespace, metricSelector, resourceNames...)
if err != nil {
klog.Errorf("unable to construct query for metric %s: %v", metricInfo.String(), err)
return "", false
}
return query, true
}
func (r *basicSeriesRegistry) MatchValuesToNames(metricInfo provider.CustomMetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool) {
r.mu.RLock()
defer r.mu.RUnlock()
metricInfo, _, err := metricInfo.Normalized(r.mapper)
if err != nil {
klog.Errorf("unable to normalize group resource while matching values to names: %v", err)
return nil, false
}
info, infoFound := r.info[metricInfo]
if !infoFound {
return nil, false
}
resourceLbl, err := info.namer.LabelForResource(metricInfo.GroupResource)
if err != nil {
klog.Errorf("unable to construct resource label for metric %s: %v", metricInfo.String(), err)
return nil, false
}
res := make(map[string]pmodel.SampleValue, len(values))
for _, val := range values {
if val == nil {
// skip empty values
continue
}
res[string(val.Metric[resourceLbl])] = val.Value
}
return res, true
}

View file

@ -0,0 +1,304 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"fmt"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
pmodel "github.com/prometheus/common/model"
coreapi "k8s.io/api/core/v1"
extapi "k8s.io/api/extensions/v1beta1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
config "sigs.k8s.io/prometheus-adapter/cmd/config-gen/utils"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
)
// restMapper creates a RESTMapper with just the types we need for
// these tests.
func restMapper() apimeta.RESTMapper {
mapper := apimeta.NewDefaultRESTMapper([]schema.GroupVersion{coreapi.SchemeGroupVersion})
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Pod"), apimeta.RESTScopeNamespace)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Service"), apimeta.RESTScopeNamespace)
mapper.Add(extapi.SchemeGroupVersion.WithKind("Ingress"), apimeta.RESTScopeNamespace)
mapper.Add(extapi.SchemeGroupVersion.WithKind("Deployment"), apimeta.RESTScopeNamespace)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Node"), apimeta.RESTScopeRoot)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("PersistentVolume"), apimeta.RESTScopeRoot)
mapper.Add(coreapi.SchemeGroupVersion.WithKind("Namespace"), apimeta.RESTScopeRoot)
return mapper
}
func setupMetricNamer() []naming.MetricNamer {
cfg := config.DefaultConfig(1*time.Minute, "kube_")
namers, err := naming.NamersFromConfig(cfg.Rules, restMapper())
Expect(err).NotTo(HaveOccurred())
return namers
}
var seriesRegistryTestSeries = [][]prom.Series{
// container series
{
{
Name: "container_some_time_seconds_total",
Labels: pmodel.LabelSet{"pod": "somepod", "namespace": "somens", "container": "somecont"},
},
},
{
{
Name: "container_some_count_total",
Labels: pmodel.LabelSet{"pod": "somepod", "namespace": "somens", "container": "somecont"},
},
},
{
{
Name: "container_some_usage",
Labels: pmodel.LabelSet{"pod": "somepod", "namespace": "somens", "container": "somecont"},
},
},
{
// gauge metrics
{
Name: "node_gigawatts",
Labels: pmodel.LabelSet{"kube_node": "somenode"},
},
{
Name: "service_proxy_packets",
Labels: pmodel.LabelSet{"kube_service": "somesvc", "kube_namespace": "somens"},
},
},
{
// cumulative --> rate metrics
{
Name: "ingress_hits_total",
Labels: pmodel.LabelSet{"kube_ingress": "someingress", "kube_service": "somesvc", "kube_pod": "backend1", "kube_namespace": "somens"},
},
{
Name: "ingress_hits_total",
Labels: pmodel.LabelSet{"kube_ingress": "someingress", "kube_service": "somesvc", "kube_pod": "backend2", "kube_namespace": "somens"},
},
{
Name: "volume_claims_total",
Labels: pmodel.LabelSet{"kube_persistentvolume": "somepv"},
},
},
{
// cumulative seconds --> rate metrics
{
Name: "work_queue_wait_seconds_total",
Labels: pmodel.LabelSet{"kube_deployment": "somedep", "kube_namespace": "somens"},
},
{
Name: "node_fan_seconds_total",
Labels: pmodel.LabelSet{"kube_node": "somenode"},
},
},
}
type regTestCase struct {
title string
info provider.CustomMetricInfo
namespace string
resourceNames []string
metricSelector labels.Selector
expectedQuery string
}
func mustNewLabelRequirement(key string, op selection.Operator, vals []string) *labels.Requirement {
req, err := labels.NewRequirement(key, op, vals)
if err != nil {
panic(err)
}
return req
}
var _ = Describe("Series Registry", func() {
var (
registry *basicSeriesRegistry
)
BeforeEach(func() {
namers := setupMetricNamer()
registry = &basicSeriesRegistry{
mapper: restMapper(),
}
Expect(registry.SetSeries(seriesRegistryTestSeries, namers)).To(Succeed())
})
Context("with the default configuration rules", func() {
// make sure each metric got registered and can form queries
testCases := []regTestCase{
// container metrics
{
title: "container metrics gauge / multiple resource names",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "some_usage"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
metricSelector: labels.Everything(),
expectedQuery: "sum(container_some_usage{namespace=\"somens\",pod=~\"somepod1|somepod2\",container!=\"POD\"}) by (pod)",
},
{
title: "container metrics counter",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "some_count"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(container_some_count_total{namespace=\"somens\",pod=~\"somepod1|somepod2\",container!=\"POD\"}[1m])) by (pod)",
},
{
title: "container metrics seconds counter",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "some_time"},
namespace: "somens",
resourceNames: []string{"somepod1", "somepod2"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(container_some_time_seconds_total{namespace=\"somens\",pod=~\"somepod1|somepod2\",container!=\"POD\"}[1m])) by (pod)",
},
// namespaced metrics
{
title: "namespaced metrics counter / multidimensional (service)",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "service"}, Namespaced: true, Metric: "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somesvc"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(ingress_hits_total{kube_namespace=\"somens\",kube_service=\"somesvc\"}[1m])) by (kube_service)",
},
{
title: "namespaced metrics counter / multidimensional (service) / selection using labels",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "service"}, Namespaced: true, Metric: "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somesvc"},
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("param1", selection.Equals, []string{"value1"}),
),
expectedQuery: "sum(rate(ingress_hits_total{param1=\"value1\",kube_namespace=\"somens\",kube_service=\"somesvc\"}[1m])) by (kube_service)",
},
{
title: "namespaced metrics counter / multidimensional (ingress)",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Group: "extensions", Resource: "ingress"}, Namespaced: true, Metric: "ingress_hits"},
namespace: "somens",
resourceNames: []string{"someingress"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(ingress_hits_total{kube_namespace=\"somens\",kube_ingress=\"someingress\"}[1m])) by (kube_ingress)",
},
{
title: "namespaced metrics counter / multidimensional (pod)",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pod"}, Namespaced: true, Metric: "ingress_hits"},
namespace: "somens",
resourceNames: []string{"somepod"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(ingress_hits_total{kube_namespace=\"somens\",kube_pod=\"somepod\"}[1m])) by (kube_pod)",
},
{
title: "namespaced metrics gauge",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "service"}, Namespaced: true, Metric: "service_proxy_packets"},
namespace: "somens",
resourceNames: []string{"somesvc"},
metricSelector: labels.Everything(),
expectedQuery: "sum(service_proxy_packets{kube_namespace=\"somens\",kube_service=\"somesvc\"}) by (kube_service)",
},
{
title: "namespaced metrics seconds counter",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Group: "extensions", Resource: "deployment"}, Namespaced: true, Metric: "work_queue_wait"},
namespace: "somens",
resourceNames: []string{"somedep"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(work_queue_wait_seconds_total{kube_namespace=\"somens\",kube_deployment=\"somedep\"}[1m])) by (kube_deployment)",
},
// non-namespaced series
{
title: "root scoped metrics gauge",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "node"}, Namespaced: false, Metric: "node_gigawatts"},
resourceNames: []string{"somenode"},
metricSelector: labels.Everything(),
expectedQuery: "sum(node_gigawatts{kube_node=\"somenode\"}) by (kube_node)",
},
{
title: "root scoped metrics counter",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "persistentvolume"}, Namespaced: false, Metric: "volume_claims"},
resourceNames: []string{"somepv"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(volume_claims_total{kube_persistentvolume=\"somepv\"}[1m])) by (kube_persistentvolume)",
},
{
title: "root scoped metrics seconds counter",
info: provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "node"}, Namespaced: false, Metric: "node_fan"},
resourceNames: []string{"somenode"},
metricSelector: labels.Everything(),
expectedQuery: "sum(rate(node_fan_seconds_total{kube_node=\"somenode\"}[1m])) by (kube_node)",
},
}
for _, tc := range testCases {
tc := tc // copy to avoid iteration variable issues
It(fmt.Sprintf("should build a query for %s", tc.title), func() {
By(fmt.Sprintf("composing the query for the %s metric on %v in namespace %s", tc.info, tc.resourceNames, tc.namespace))
outputQuery, found := registry.QueryForMetric(tc.info, tc.namespace, tc.metricSelector, tc.resourceNames...)
Expect(found).To(BeTrue(), "metric %s should be available", tc.info)
By("verifying that the query is as expected")
Expect(outputQuery).To(Equal(prom.Selector(tc.expectedQuery)))
})
}
It("should list all metrics", func() {
Expect(registry.ListAllMetrics()).To(ConsistOf(
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "some_count"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "some_count"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "some_time"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "some_time"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "some_usage"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "some_usage"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "services"}, Namespaced: true, Metric: "ingress_hits"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Group: "extensions", Resource: "ingresses"}, Namespaced: true, Metric: "ingress_hits"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "pods"}, Namespaced: true, Metric: "ingress_hits"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "ingress_hits"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "services"}, Namespaced: true, Metric: "service_proxy_packets"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "service_proxy_packets"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Group: "extensions", Resource: "deployments"}, Namespaced: true, Metric: "work_queue_wait"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "namespaces"}, Namespaced: false, Metric: "work_queue_wait"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "nodes"}, Namespaced: false, Metric: "node_gigawatts"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "persistentvolumes"}, Namespaced: false, Metric: "volume_claims"},
provider.CustomMetricInfo{GroupResource: schema.GroupResource{Resource: "nodes"}, Namespaced: false, Metric: "node_fan"},
))
})
})
})

View file

@ -0,0 +1,167 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"context"
"fmt"
"time"
"k8s.io/klog/v2"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
pmodel "github.com/prometheus/common/model"
)
// Runnable represents something that can be run until told to stop.
type Runnable interface {
// Run runs the runnable forever.
Run()
// RunUntil runs the runnable until the given channel is closed.
RunUntil(stopChan <-chan struct{})
}
// A MetricLister provides a window into all of the metrics that are available within a given
// Prometheus instance, classified as either Custom or External metrics, but presented generically
// so that it can manage both types simultaneously.
type MetricLister interface {
ListAllMetrics() (MetricUpdateResult, error)
}
// A MetricListerWithNotification is a MetricLister that has the ability to notify listeners
// when new metric data is available.
type MetricListerWithNotification interface {
MetricLister
Runnable
// AddNotificationReceiver registers a callback to be invoked when new metric data is available.
AddNotificationReceiver(MetricUpdateCallback)
// UpdateNow forces an immediate refresh from the source data. Primarily for test purposes.
UpdateNow()
}
type basicMetricLister struct {
promClient prom.Client
namers []naming.MetricNamer
lookback time.Duration
}
// NewBasicMetricLister creates a MetricLister that is capable of interactly directly with Prometheus to list metrics.
func NewBasicMetricLister(promClient prom.Client, namers []naming.MetricNamer, lookback time.Duration) MetricLister {
lister := basicMetricLister{
promClient: promClient,
namers: namers,
lookback: lookback,
}
return &lister
}
type selectorSeries struct {
selector prom.Selector
series []prom.Series
}
func (l *basicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
result := MetricUpdateResult{
series: make([][]prom.Series, 0),
namers: make([]naming.MetricNamer, 0),
}
startTime := pmodel.Now().Add(-1 * l.lookback)
// these can take a while on large clusters, so launch in parallel
// and don't duplicate
selectors := make(map[prom.Selector]struct{})
selectorSeriesChan := make(chan selectorSeries, len(l.namers))
errs := make(chan error, len(l.namers))
for _, converter := range l.namers {
sel := converter.Selector()
if _, ok := selectors[sel]; ok {
errs <- nil
selectorSeriesChan <- selectorSeries{}
continue
}
selectors[sel] = struct{}{}
go func() {
series, err := l.promClient.Series(context.TODO(), pmodel.Interval{Start: startTime, End: 0}, sel)
if err != nil {
errs <- fmt.Errorf("unable to fetch metrics for query %q: %v", sel, err)
return
}
errs <- nil
// Push into the channel: "this selector produced these series"
selectorSeriesChan <- selectorSeries{
selector: sel,
series: series,
}
}()
}
// don't do duplicate queries when it's just the matchers that change
seriesCacheByQuery := make(map[prom.Selector][]prom.Series)
// iterate through, blocking until we've got all results
// We know that, from above, we should have pushed one item into the channel
// for each converter. So here, we'll assume that we should receive one item per converter.
for range l.namers {
if err := <-errs; err != nil {
return result, fmt.Errorf("unable to update list of all metrics: %v", err)
}
// Receive from the channel: "this selector produced these series"
// We stuff that into this map so that we can collect the data as it arrives
// and then, once we've received it all, we can process it below.
if ss := <-selectorSeriesChan; ss.series != nil {
seriesCacheByQuery[ss.selector] = ss.series
}
}
close(errs)
// Now that we've collected all of the results into `seriesCacheByQuery`
// we can start processing them.
newSeries := make([][]prom.Series, len(l.namers))
for i, namer := range l.namers {
series, cached := seriesCacheByQuery[namer.Selector()]
if !cached {
return result, fmt.Errorf("unable to update list of all metrics: no metrics retrieved for query %q", namer.Selector())
}
// Because converters provide a "post-filtering" option, it's not enough to
// simply take all the series that were produced. We need to further filter them.
newSeries[i] = namer.FilterSeries(series)
}
klog.V(10).Infof("Set available metric list from Prometheus to: %v", newSeries)
result.series = newSeries
result.namers = l.namers
return result, nil
}
// MetricUpdateResult represents the output of a periodic inspection of metrics found to be
// available in Prometheus.
// It includes both the series data the Prometheus exposed, as well as the configurational
// object that led to their discovery.
type MetricUpdateResult struct {
series [][]prom.Series
namers []naming.MetricNamer
}
// MetricUpdateCallback is a function signature for receiving periodic updates about
// available metrics.
type MetricUpdateCallback func(MetricUpdateResult)

View file

@ -0,0 +1,128 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"sync"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/klog/v2"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
)
// ExternalSeriesRegistry acts as the top-level converter for transforming Kubernetes requests
// for external metrics into Prometheus queries.
type ExternalSeriesRegistry interface {
// ListAllMetrics lists all metrics known to this registry
ListAllMetrics() []provider.ExternalMetricInfo
QueryForMetric(namespace string, metricName string, metricSelector labels.Selector) (prom.Selector, bool, error)
}
// overridableSeriesRegistry is a basic SeriesRegistry
type externalSeriesRegistry struct {
// We lock when reading/writing metrics, and metricsInfo to prevent inconsistencies.
mu sync.RWMutex
// metrics is the list of all known metrics, ready to return from the API
metrics []provider.ExternalMetricInfo
// metricsInfo is a lookup from a metric to SeriesConverter for the sake of generating queries
metricsInfo map[string]seriesInfo
}
type seriesInfo struct {
// seriesName is the name of the corresponding Prometheus series
seriesName string
// namer is the MetricNamer used to name this series
namer naming.MetricNamer
}
// NewExternalSeriesRegistry creates an ExternalSeriesRegistry driven by the data from the provided MetricLister.
func NewExternalSeriesRegistry(lister MetricListerWithNotification) ExternalSeriesRegistry {
var registry = externalSeriesRegistry{
metrics: make([]provider.ExternalMetricInfo, 0),
metricsInfo: map[string]seriesInfo{},
}
lister.AddNotificationReceiver(registry.filterAndStoreMetrics)
return &registry
}
func (r *externalSeriesRegistry) filterAndStoreMetrics(result MetricUpdateResult) {
newSeriesSlices := result.series
namers := result.namers
if len(newSeriesSlices) != len(namers) {
klog.Fatal("need one set of series per converter")
}
apiMetricsCache := make([]provider.ExternalMetricInfo, 0)
rawMetricsCache := make(map[string]seriesInfo)
for i, newSeries := range newSeriesSlices {
namer := namers[i]
for _, series := range newSeries {
identity, err := namer.MetricNameForSeries(series)
if err != nil {
klog.Errorf("unable to name series %q, skipping: %v", series.String(), err)
continue
}
name := identity
rawMetricsCache[name] = seriesInfo{
seriesName: series.Name,
namer: namer,
}
}
}
for metricName := range rawMetricsCache {
apiMetricsCache = append(apiMetricsCache, provider.ExternalMetricInfo{
Metric: metricName,
})
}
r.mu.Lock()
defer r.mu.Unlock()
r.metrics = apiMetricsCache
r.metricsInfo = rawMetricsCache
}
func (r *externalSeriesRegistry) ListAllMetrics() []provider.ExternalMetricInfo {
r.mu.RLock()
defer r.mu.RUnlock()
return r.metrics
}
func (r *externalSeriesRegistry) QueryForMetric(namespace string, metricName string, metricSelector labels.Selector) (prom.Selector, bool, error) {
r.mu.RLock()
defer r.mu.RUnlock()
info, found := r.metricsInfo[metricName]
if !found {
klog.V(10).Infof("external metric %q not found", metricName)
return "", false, nil
}
query, err := info.namer.QueryForExternalSeries(info.seriesName, namespace, metricSelector)
return query, found, err
}

View file

@ -0,0 +1,143 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"errors"
"fmt"
"github.com/prometheus/common/model"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/metrics/pkg/apis/external_metrics"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
)
// MetricConverter provides a unified interface for converting the results of
// Prometheus queries into external metric types.
type MetricConverter interface {
Convert(info provider.ExternalMetricInfo, queryResult prom.QueryResult) (*external_metrics.ExternalMetricValueList, error)
}
type metricConverter struct {
}
// NewMetricConverter creates a MetricCoverter, capable of converting any of the three metric types
// returned by the Prometheus client into external metrics types.
func NewMetricConverter() MetricConverter {
return &metricConverter{}
}
func (c *metricConverter) Convert(info provider.ExternalMetricInfo, queryResult prom.QueryResult) (*external_metrics.ExternalMetricValueList, error) {
if queryResult.Type == model.ValScalar {
return c.convertScalar(info, queryResult)
}
if queryResult.Type == model.ValVector {
return c.convertVector(info, queryResult)
}
return nil, errors.New("encountered an unexpected query result type")
}
func (c *metricConverter) convertSample(info provider.ExternalMetricInfo, sample *model.Sample) (*external_metrics.ExternalMetricValue, error) {
labels := c.convertLabels(sample.Metric)
singleMetric := external_metrics.ExternalMetricValue{
MetricName: info.Metric,
Timestamp: metav1.Time{
Time: sample.Timestamp.Time(),
},
Value: *resource.NewMilliQuantity(int64(sample.Value*1000.0), resource.DecimalSI),
MetricLabels: labels,
}
return &singleMetric, nil
}
func (c *metricConverter) convertLabels(inLabels model.Metric) map[string]string {
numLabels := len(inLabels)
outLabels := make(map[string]string, numLabels)
for labelName, labelVal := range inLabels {
outLabels[string(labelName)] = string(labelVal)
}
return outLabels
}
func (c *metricConverter) convertVector(info provider.ExternalMetricInfo, queryResult prom.QueryResult) (*external_metrics.ExternalMetricValueList, error) {
if queryResult.Type != model.ValVector {
return nil, errors.New("incorrect query result type")
}
toConvert := *queryResult.Vector
if toConvert == nil {
return nil, errors.New("the provided input did not contain vector query results")
}
items := []external_metrics.ExternalMetricValue{}
metricValueList := external_metrics.ExternalMetricValueList{
Items: items,
}
numSamples := toConvert.Len()
if numSamples == 0 {
return &metricValueList, nil
}
for _, val := range toConvert {
singleMetric, err := c.convertSample(info, val)
if err != nil {
return nil, fmt.Errorf("unable to convert vector: %v", err)
}
items = append(items, *singleMetric)
}
metricValueList = external_metrics.ExternalMetricValueList{
Items: items,
}
return &metricValueList, nil
}
func (c *metricConverter) convertScalar(info provider.ExternalMetricInfo, queryResult prom.QueryResult) (*external_metrics.ExternalMetricValueList, error) {
if queryResult.Type != model.ValScalar {
return nil, errors.New("scalarConverter can only convert scalar query results")
}
toConvert := queryResult.Scalar
if toConvert == nil {
return nil, errors.New("the provided input did not contain scalar query results")
}
result := external_metrics.ExternalMetricValueList{
Items: []external_metrics.ExternalMetricValue{
{
MetricName: info.Metric,
Timestamp: metav1.Time{
Time: toConvert.Timestamp.Time(),
},
Value: *resource.NewMilliQuantity(int64(toConvert.Value*1000.0), resource.DecimalSI),
},
},
}
return &result, nil
}

View file

@ -0,0 +1,91 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"time"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
)
type periodicMetricLister struct {
realLister MetricLister
updateInterval time.Duration
mostRecentResult MetricUpdateResult
callbacks []MetricUpdateCallback
}
// NewPeriodicMetricLister creates a MetricLister that periodically pulls the list of available metrics
// at the provided interval, but defers the actual act of retrieving the metrics to the supplied MetricLister.
func NewPeriodicMetricLister(realLister MetricLister, updateInterval time.Duration) (MetricListerWithNotification, Runnable) {
lister := periodicMetricLister{
updateInterval: updateInterval,
realLister: realLister,
callbacks: make([]MetricUpdateCallback, 0),
}
return &lister, &lister
}
func (l *periodicMetricLister) AddNotificationReceiver(callback MetricUpdateCallback) {
l.callbacks = append(l.callbacks, callback)
}
func (l *periodicMetricLister) ListAllMetrics() (MetricUpdateResult, error) {
return l.mostRecentResult, nil
}
func (l *periodicMetricLister) Run() {
l.RunUntil(wait.NeverStop)
}
func (l *periodicMetricLister) RunUntil(stopChan <-chan struct{}) {
go wait.Until(func() {
if err := l.updateMetrics(); err != nil {
utilruntime.HandleError(err)
}
}, l.updateInterval, stopChan)
}
func (l *periodicMetricLister) updateMetrics() error {
result, err := l.realLister.ListAllMetrics()
if err != nil {
return err
}
// Cache the result.
l.mostRecentResult = result
// Let our listeners know we've got new data ready for them.
l.notifyListeners()
return nil
}
func (l *periodicMetricLister) notifyListeners() {
for _, listener := range l.callbacks {
if listener != nil {
listener(l.mostRecentResult)
}
}
}
func (l *periodicMetricLister) UpdateNow() {
if err := l.updateMetrics(); err != nil {
utilruntime.HandleError(err)
}
}

View file

@ -0,0 +1,83 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"testing"
"time"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"github.com/stretchr/testify/require"
)
type fakeLister struct {
callCount int
}
func (f *fakeLister) ListAllMetrics() (MetricUpdateResult, error) {
f.callCount++
return MetricUpdateResult{
series: [][]prom.Series{
{
{
Name: "a_series",
},
},
},
}, nil
}
func TestWhenNewMetricsAvailableCallbackIsInvoked(t *testing.T) {
fakeLister := &fakeLister{}
targetLister, _ := NewPeriodicMetricLister(fakeLister, time.Duration(1000))
periodicLister := targetLister.(*periodicMetricLister)
callbackInvoked := false
callback := func(r MetricUpdateResult) {
callbackInvoked = true
}
periodicLister.AddNotificationReceiver(callback)
err := periodicLister.updateMetrics()
require.NoError(t, err)
require.True(t, callbackInvoked)
}
func TestWhenListingMetricsReturnsCachedValues(t *testing.T) {
fakeLister := &fakeLister{}
targetLister, _ := NewPeriodicMetricLister(fakeLister, time.Duration(1000))
periodicLister := targetLister.(*periodicMetricLister)
// We haven't invoked the inner lister yet, so we should have no results.
resultBeforeUpdate, err := periodicLister.ListAllMetrics()
require.NoError(t, err)
require.Equal(t, 0, len(resultBeforeUpdate.series))
require.Equal(t, 0, fakeLister.callCount)
// We can simulate waiting for the udpate interval to pass...
// which should result in calling the inner lister to get the metrics.
err = periodicLister.updateMetrics()
require.NoError(t, err)
require.Equal(t, 1, fakeLister.callCount)
// If we list now, we should return the cached values.
// Make sure we got some results this time
// as well as that we didn't unnecessarily invoke the inner lister.
resultAfterUpdate, err := periodicLister.ListAllMetrics()
require.NoError(t, err)
require.NotEqual(t, 0, len(resultAfterUpdate.series))
require.Equal(t, 1, fakeLister.callCount)
}

View file

@ -0,0 +1,90 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package provider
import (
"context"
"fmt"
"time"
pmodel "github.com/prometheus/common/model"
apierr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
"k8s.io/metrics/pkg/apis/external_metrics"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
)
type externalPrometheusProvider struct {
promClient prom.Client
metricConverter MetricConverter
seriesRegistry ExternalSeriesRegistry
}
func (p *externalPrometheusProvider) GetExternalMetric(ctx context.Context, namespace string, metricSelector labels.Selector, info provider.ExternalMetricInfo) (*external_metrics.ExternalMetricValueList, error) {
selector, found, err := p.seriesRegistry.QueryForMetric(namespace, info.Metric, metricSelector)
if err != nil {
klog.Errorf("unable to generate a query for the metric: %v", err)
return nil, apierr.NewInternalError(fmt.Errorf("unable to fetch metrics"))
}
if !found {
return nil, provider.NewMetricNotFoundError(p.selectGroupResource(namespace), info.Metric)
}
// Here is where we're making the query, need to be before here xD
queryResults, err := p.promClient.Query(ctx, pmodel.Now(), selector)
if err != nil {
klog.Errorf("unable to fetch metrics from prometheus: %v", err)
// don't leak implementation details to the user
return nil, apierr.NewInternalError(fmt.Errorf("unable to fetch metrics"))
}
return p.metricConverter.Convert(info, queryResults)
}
func (p *externalPrometheusProvider) ListAllExternalMetrics() []provider.ExternalMetricInfo {
return p.seriesRegistry.ListAllMetrics()
}
func (p *externalPrometheusProvider) selectGroupResource(namespace string) schema.GroupResource {
if namespace == "default" {
return naming.NsGroupResource
}
return schema.GroupResource{
Group: "",
Resource: "",
}
}
// NewExternalPrometheusProvider creates an ExternalMetricsProvider capable of responding to Kubernetes requests for external metric data
func NewExternalPrometheusProvider(promClient prom.Client, namers []naming.MetricNamer, updateInterval time.Duration, maxAge time.Duration) (provider.ExternalMetricsProvider, Runnable) {
metricConverter := NewMetricConverter()
basicLister := NewBasicMetricLister(promClient, namers, maxAge)
periodicLister, _ := NewPeriodicMetricLister(basicLister, updateInterval)
seriesRegistry := NewExternalSeriesRegistry(periodicLister)
return &externalPrometheusProvider{
promClient: promClient,
seriesRegistry: seriesRegistry,
metricConverter: metricConverter,
}, periodicLister
}

34
pkg/naming/errors.go Normal file
View file

@ -0,0 +1,34 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package naming
import "errors"
var (
// ErrUnsupportedOperator creates an error that represents the fact that we were requested to service a query that
// Prometheus would be unable to support.
ErrUnsupportedOperator = errors.New("operator not supported by prometheus")
// ErrMalformedQuery creates an error that represents the fact that we were requested to service a query
// that was malformed in its operator/value combination.
ErrMalformedQuery = errors.New("operator requires values")
// ErrQueryUnsupportedValues creates an error that represents an unsupported return value from the
// specified query.
ErrQueryUnsupportedValues = errors.New("operator does not support values")
// ErrLabelNotSpecified creates an error that represents the fact that we were requested to service a query
// that was malformed in its label specification.
ErrLabelNotSpecified = errors.New("label not specified")
)

100
pkg/naming/lbl_res.go Normal file
View file

@ -0,0 +1,100 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package naming
import (
"bytes"
"fmt"
"regexp"
"text/template"
"k8s.io/apimachinery/pkg/runtime/schema"
pmodel "github.com/prometheus/common/model"
)
// labelGroupResExtractor extracts schema.GroupResources from series labels.
type labelGroupResExtractor struct {
regex *regexp.Regexp
resourceInd int
groupInd *int
}
// newLabelGroupResExtractor creates a new labelGroupResExtractor for labels whose form
// matches the given template. It does so by creating a regular expression from the template,
// so anything in the template which limits resource or group name length will cause issues.
func newLabelGroupResExtractor(labelTemplate *template.Template) (*labelGroupResExtractor, error) {
labelRegexBuff := new(bytes.Buffer)
if err := labelTemplate.Execute(labelRegexBuff, schema.GroupResource{
Group: "(?P<group>.+?)",
Resource: "(?P<resource>.+?)"},
); err != nil {
return nil, fmt.Errorf("unable to convert label template to matcher: %v", err)
}
if labelRegexBuff.Len() == 0 {
return nil, fmt.Errorf("unable to convert label template to matcher: empty template")
}
labelRegexRaw := "^" + labelRegexBuff.String() + "$"
labelRegex, err := regexp.Compile(labelRegexRaw)
if err != nil {
return nil, fmt.Errorf("unable to convert label template to matcher: %v", err)
}
var groupInd *int
var resInd *int
for i, name := range labelRegex.SubexpNames() {
switch name {
case "group":
ind := i // copy to avoid iteration variable reference
groupInd = &ind
case "resource":
ind := i // copy to avoid iteration variable reference
resInd = &ind
}
}
if resInd == nil {
return nil, fmt.Errorf("must include at least `{{.Resource}}` in the label template")
}
return &labelGroupResExtractor{
regex: labelRegex,
resourceInd: *resInd,
groupInd: groupInd,
}, nil
}
// GroupResourceForLabel extracts a schema.GroupResource from the given label, if possible.
// The second return value indicates whether or not a potential group-resource was found in this label.
func (e *labelGroupResExtractor) GroupResourceForLabel(lbl pmodel.LabelName) (schema.GroupResource, bool) {
matchGroups := e.regex.FindStringSubmatch(string(lbl))
if matchGroups != nil {
group := ""
if e.groupInd != nil {
group = matchGroups[*e.groupInd]
}
return schema.GroupResource{
Group: group,
Resource: matchGroups[e.resourceInd],
}, true
}
return schema.GroupResource{}, false
}

222
pkg/naming/metric_namer.go Normal file
View file

@ -0,0 +1,222 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package naming
import (
"fmt"
"regexp"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/config"
)
// MetricNamer knows how to convert Prometheus series names and label names to
// metrics API resources, and vice-versa. MetricNamers should be safe to access
// concurrently. Returned group-resources are "normalized" as per the
// MetricInfo#Normalized method. Group-resources passed as arguments must
// themselves be normalized.
type MetricNamer interface {
// Selector produces the appropriate Prometheus series selector to match all
// series handable by this namer.
Selector() prom.Selector
// FilterSeries checks to see which of the given series match any additional
// constraints beyond the series query. It's assumed that the series given
// already match the series query.
FilterSeries(series []prom.Series) []prom.Series
// MetricNameForSeries returns the name (as presented in the API) for a given series.
MetricNameForSeries(series prom.Series) (string, error)
// QueryForSeries returns the query for a given series (not API metric name), with
// the given namespace name (if relevant), resource, and resource names.
QueryForSeries(series string, resource schema.GroupResource, namespace string, metricSelector labels.Selector, names ...string) (prom.Selector, error)
// QueryForExternalSeries returns the query for a given series (not API metric name), with
// the given namespace name (if relevant), resource, and resource names.
QueryForExternalSeries(series string, namespace string, targetLabels labels.Selector) (prom.Selector, error)
ResourceConverter
}
func (n *metricNamer) Selector() prom.Selector {
return n.seriesQuery
}
// ReMatcher either positively or negatively matches a regex
type ReMatcher struct {
regex *regexp.Regexp
positive bool
}
func NewReMatcher(cfg config.RegexFilter) (*ReMatcher, error) {
if cfg.Is != "" && cfg.IsNot != "" {
return nil, fmt.Errorf("cannot have both an `is` (%q) and `isNot` (%q) expression in a single filter", cfg.Is, cfg.IsNot)
}
if cfg.Is == "" && cfg.IsNot == "" {
return nil, fmt.Errorf("must have either an `is` or `isNot` expression in a filter")
}
var positive bool
var regexRaw string
if cfg.Is != "" {
positive = true
regexRaw = cfg.Is
} else {
positive = false
regexRaw = cfg.IsNot
}
regex, err := regexp.Compile(regexRaw)
if err != nil {
return nil, fmt.Errorf("unable to compile series filter %q: %v", regexRaw, err)
}
return &ReMatcher{
regex: regex,
positive: positive,
}, nil
}
func (m *ReMatcher) Matches(val string) bool {
return m.regex.MatchString(val) == m.positive
}
type metricNamer struct {
seriesQuery prom.Selector
metricsQuery MetricsQuery
nameMatches *regexp.Regexp
nameAs string
seriesMatchers []*ReMatcher
ResourceConverter
}
// queryTemplateArgs are the arguments for the metrics query template.
func (n *metricNamer) FilterSeries(initialSeries []prom.Series) []prom.Series {
if len(n.seriesMatchers) == 0 {
return initialSeries
}
finalSeries := make([]prom.Series, 0, len(initialSeries))
SeriesLoop:
for _, series := range initialSeries {
for _, matcher := range n.seriesMatchers {
if !matcher.Matches(series.Name) {
continue SeriesLoop
}
}
finalSeries = append(finalSeries, series)
}
return finalSeries
}
func (n *metricNamer) QueryForSeries(series string, resource schema.GroupResource, namespace string, metricSelector labels.Selector, names ...string) (prom.Selector, error) {
return n.metricsQuery.Build(series, resource, namespace, nil, metricSelector, names...)
}
func (n *metricNamer) QueryForExternalSeries(series string, namespace string, metricSelector labels.Selector) (prom.Selector, error) {
return n.metricsQuery.BuildExternal(series, namespace, "", []string{}, metricSelector)
}
func (n *metricNamer) MetricNameForSeries(series prom.Series) (string, error) {
matches := n.nameMatches.FindStringSubmatchIndex(series.Name)
if matches == nil {
return "", fmt.Errorf("series name %q did not match expected pattern %q", series.Name, n.nameMatches.String())
}
outNameBytes := n.nameMatches.ExpandString(nil, n.nameAs, series.Name, matches)
return string(outNameBytes), nil
}
// NamersFromConfig produces a MetricNamer for each rule in the given config.
func NamersFromConfig(cfg []config.DiscoveryRule, mapper apimeta.RESTMapper) ([]MetricNamer, error) {
namers := make([]MetricNamer, len(cfg))
for i, rule := range cfg {
resConv, err := NewResourceConverter(rule.Resources.Template, rule.Resources.Overrides, mapper)
if err != nil {
return nil, err
}
// queries are namespaced by default unless the rule specifically disables it
namespaced := true
if rule.Resources.Namespaced != nil {
namespaced = *rule.Resources.Namespaced
}
metricsQuery, err := NewExternalMetricsQuery(rule.MetricsQuery, resConv, namespaced)
if err != nil {
return nil, fmt.Errorf("unable to construct metrics query associated with series query %q: %v", rule.SeriesQuery, err)
}
seriesMatchers := make([]*ReMatcher, len(rule.SeriesFilters))
for i, filterRaw := range rule.SeriesFilters {
matcher, err := NewReMatcher(filterRaw)
if err != nil {
return nil, fmt.Errorf("unable to generate series name filter associated with series query %q: %v", rule.SeriesQuery, err)
}
seriesMatchers[i] = matcher
}
if rule.Name.Matches != "" {
matcher, err := NewReMatcher(config.RegexFilter{Is: rule.Name.Matches})
if err != nil {
return nil, fmt.Errorf("unable to generate series name filter from name rules associated with series query %q: %v", rule.SeriesQuery, err)
}
seriesMatchers = append(seriesMatchers, matcher)
}
var nameMatches *regexp.Regexp
if rule.Name.Matches != "" {
nameMatches, err = regexp.Compile(rule.Name.Matches)
if err != nil {
return nil, fmt.Errorf("unable to compile series name match expression %q associated with series query %q: %v", rule.Name.Matches, rule.SeriesQuery, err)
}
} else {
// this will always succeed
nameMatches = regexp.MustCompile(".*")
}
nameAs := rule.Name.As
if nameAs == "" {
// check if we have an obvious default
subexpNames := nameMatches.SubexpNames()
switch len(subexpNames) {
case 1:
// no capture groups, use the whole thing
nameAs = "$0"
case 2:
// one capture group, use that
nameAs = "$1"
default:
return nil, fmt.Errorf("must specify an 'as' value for name matcher %q associated with series query %q", rule.Name.Matches, rule.SeriesQuery)
}
}
namer := &metricNamer{
seriesQuery: prom.Selector(rule.SeriesQuery),
metricsQuery: metricsQuery,
nameMatches: nameMatches,
nameAs: nameAs,
seriesMatchers: seriesMatchers,
ResourceConverter: resConv,
}
namers[i] = namer
}
return namers, nil
}

369
pkg/naming/metrics_query.go Normal file
View file

@ -0,0 +1,369 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package naming
import (
"bytes"
"errors"
"fmt"
"strings"
"text/template"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
)
// MetricsQuery represents a compiled metrics query for some set of
// series that can be converted into an series of Prometheus expressions to
// be passed to a client.
type MetricsQuery interface {
// Build constructs Prometheus expressions to represent this query
// over the given group-resource. If namespace is empty, the resource
// is considered to be root-scoped. extraGroupBy may be used for cases
// where we need to scope down more specifically than just the group-resource
// (e.g. container metrics).
Build(series string, groupRes schema.GroupResource, namespace string, extraGroupBy []string, metricSelector labels.Selector, resourceNames ...string) (prom.Selector, error)
BuildExternal(seriesName string, namespace string, groupBy string, groupBySlice []string, metricSelector labels.Selector) (prom.Selector, error)
}
// NewMetricsQuery constructs a new MetricsQuery by compiling the given Go template.
// The delimiters on the template are `<<` and `>>`, and it may use the following fields:
// - Series: the series in question
// - LabelMatchers: a pre-stringified form of the label matchers for the resources in the query
// - LabelMatchersByName: the raw map-form of the above matchers
// - GroupBy: the group-by clause to use for the resources in the query (stringified)
// - GroupBySlice: the raw slice form of the above group-by clause
func NewMetricsQuery(queryTemplate string, resourceConverter ResourceConverter) (MetricsQuery, error) {
templ, err := template.New("metrics-query").Delims("<<", ">>").Parse(queryTemplate)
if err != nil {
return nil, fmt.Errorf("unable to parse metrics query template %q: %v", queryTemplate, err)
}
return &metricsQuery{
resConverter: resourceConverter,
template: templ,
namespaced: true,
}, nil
}
// NewExternalMetricsQuery constructs a new MetricsQuery by compiling the given Go template.
// The delimiters on the template are `<<` and `>>`, and it may use the following fields:
// - Series: the series in question
// - LabelMatchers: a pre-stringified form of the label matchers for the resources in the query
// - LabelMatchersByName: the raw map-form of the above matchers
// - GroupBy: the group-by clause to use for the resources in the query (stringified)
// - GroupBySlice: the raw slice form of the above group-by clause
func NewExternalMetricsQuery(queryTemplate string, resourceConverter ResourceConverter, namespaced bool) (MetricsQuery, error) {
templ, err := template.New("metrics-query").Delims("<<", ">>").Parse(queryTemplate)
if err != nil {
return nil, fmt.Errorf("unable to parse metrics query template %q: %v", queryTemplate, err)
}
return &metricsQuery{
resConverter: resourceConverter,
template: templ,
namespaced: namespaced,
}, nil
}
// metricsQuery is a MetricsQuery based on a compiled Go text template.
// with the delimiters as `<<` and `>>`, and the arguments found in
// queryTemplateArgs.
type metricsQuery struct {
resConverter ResourceConverter
template *template.Template
namespaced bool
}
// queryTemplateArgs contains the arguments for the template used in metricsQuery.
type queryTemplateArgs struct {
Series string
LabelMatchers string
LabelValuesByName map[string]string
GroupBy string
GroupBySlice []string
}
type queryPart struct {
labelName string
values []string
operator selection.Operator
}
func (q *metricsQuery) Build(series string, resource schema.GroupResource, namespace string, extraGroupBy []string, metricSelector labels.Selector, names ...string) (prom.Selector, error) {
queryParts := q.createQueryPartsFromSelector(metricSelector)
if namespace != "" {
namespaceLbl, err := q.resConverter.LabelForResource(NsGroupResource)
if err != nil {
return "", err
}
queryParts = append(queryParts, queryPart{
labelName: string(namespaceLbl),
values: []string{namespace},
operator: selection.Equals,
})
}
exprs, valuesByName, err := q.processQueryParts(queryParts)
if err != nil {
return "", err
}
resourceLbl, err := q.resConverter.LabelForResource(resource)
if err != nil {
return "", err
}
matcher := prom.LabelEq
targetValue := strings.Join(names, "|")
if len(names) > 1 {
matcher = prom.LabelMatches
}
exprs = append(exprs, matcher(string(resourceLbl), targetValue))
valuesByName[string(resourceLbl)] = targetValue
groupBy := make([]string, 0, len(extraGroupBy)+1)
groupBy = append(groupBy, string(resourceLbl))
groupBy = append(groupBy, extraGroupBy...)
args := queryTemplateArgs{
Series: series,
LabelMatchers: strings.Join(exprs, ","),
LabelValuesByName: valuesByName,
GroupBy: strings.Join(groupBy, ","),
GroupBySlice: groupBy,
}
queryBuff := new(bytes.Buffer)
if err := q.template.Execute(queryBuff, args); err != nil {
return "", err
}
if queryBuff.Len() == 0 {
return "", fmt.Errorf("empty query produced by metrics query template")
}
return prom.Selector(queryBuff.String()), nil
}
func (q *metricsQuery) BuildExternal(seriesName string, namespace string, groupBy string, groupBySlice []string, metricSelector labels.Selector) (prom.Selector, error) {
queryParts := []queryPart{}
// Build up the query parts from the selector.
queryParts = append(queryParts, q.createQueryPartsFromSelector(metricSelector)...)
if q.namespaced && namespace != "" {
namespaceLbl, err := q.resConverter.LabelForResource(NsGroupResource)
if err != nil {
return "", err
}
queryParts = append(queryParts, queryPart{
labelName: string(namespaceLbl),
values: []string{namespace},
operator: selection.Equals,
})
}
// Convert our query parts into the types we need for our template.
exprs, valuesByName, err := q.processQueryParts(queryParts)
if err != nil {
return "", err
}
args := queryTemplateArgs{
Series: seriesName,
LabelMatchers: strings.Join(exprs, ","),
LabelValuesByName: valuesByName,
GroupBy: groupBy,
GroupBySlice: groupBySlice,
}
queryBuff := new(bytes.Buffer)
if err := q.template.Execute(queryBuff, args); err != nil {
return "", err
}
if queryBuff.Len() == 0 {
return "", fmt.Errorf("empty query produced by metrics query template")
}
return prom.Selector(queryBuff.String()), nil
}
func (q *metricsQuery) createQueryPartsFromSelector(metricSelector labels.Selector) []queryPart {
requirements, _ := metricSelector.Requirements()
selectors := []queryPart{}
for i := 0; i < len(requirements); i++ {
selector := q.convertRequirement(requirements[i])
selectors = append(selectors, selector)
}
return selectors
}
func (q *metricsQuery) convertRequirement(requirement labels.Requirement) queryPart {
labelName := requirement.Key()
values := requirement.Values().List()
return queryPart{
labelName: labelName,
values: values,
operator: requirement.Operator(),
}
}
func (q *metricsQuery) processQueryParts(queryParts []queryPart) ([]string, map[string]string, error) {
// We've take the approach here that if we can't perfectly map their query into a Prometheus
// query that we should abandon the effort completely.
// The concern is that if we don't get a perfect match on their query parameters, the query result
// might contain unexpected data that would cause them to take an erroneous action based on the result.
// Contains the expressions that we want to include as part of the query to Prometheus.
// e.g. "namespace=my-namespace"
// e.g. "some_label=some-value"
var exprs []string
// Contains the list of label values we're targeting, by namespace.
// e.g. "some_label" => "value-one|value-two"
valuesByName := map[string]string{}
// Convert our query parts into template arguments.
for _, qPart := range queryParts {
// Be resilient against bad inputs.
// We obviously can't generate label filters for these cases.
if qPart.labelName == "" {
return nil, nil, ErrLabelNotSpecified
}
if !q.operatorIsSupported(qPart.operator) {
return nil, nil, ErrUnsupportedOperator
}
matcher, err := q.selectMatcher(qPart.operator, qPart.values)
if err != nil {
return nil, nil, err
}
targetValue, err := q.selectTargetValue(qPart.operator, qPart.values)
if err != nil {
return nil, nil, err
}
expression := matcher(qPart.labelName, targetValue)
exprs = append(exprs, expression)
valuesByName[qPart.labelName] = strings.Join(qPart.values, "|")
}
return exprs, valuesByName, nil
}
func (q *metricsQuery) selectMatcher(operator selection.Operator, values []string) (func(string, string) string, error) {
switch len(values) {
case 0:
switch operator {
case selection.Exists:
return prom.LabelNeq, nil
case selection.DoesNotExist:
return prom.LabelEq, nil
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
return nil, ErrMalformedQuery
}
case 1:
switch operator {
case selection.Equals, selection.DoubleEquals:
return prom.LabelEq, nil
case selection.NotEquals:
return prom.LabelNeq, nil
case selection.In, selection.Exists:
return prom.LabelMatches, nil
case selection.DoesNotExist, selection.NotIn:
return prom.LabelNotMatches, nil
}
default:
// Since labels can only have one value, providing multiple
// values results in a regex match, even if that's not what the user
// asked for.
switch operator {
case selection.Equals, selection.DoubleEquals, selection.In, selection.Exists:
return prom.LabelMatches, nil
case selection.NotEquals, selection.DoesNotExist, selection.NotIn:
return prom.LabelNotMatches, nil
}
}
return nil, errors.New("operator not supported by query builder")
}
func (q *metricsQuery) selectTargetValue(operator selection.Operator, values []string) (string, error) {
switch len(values) {
case 0:
switch operator {
case selection.Exists, selection.DoesNotExist:
// Return an empty string when values are equal to 0
// When the operator is LabelNotMatches this will select series without the label
// or with the label but a value of "".
// When the operator is LabelMatches this will select series with the label
// whose value is NOT "".
return "", nil
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
return "", ErrMalformedQuery
}
case 1:
switch operator {
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
// Pass the value through as-is.
// It's somewhat strange to do this for both the regex and equality
// operators, but if we do it this way it gives the user a little more control.
// They might choose to send an "IN" request and give a list of static values
// or they could send a single value that's a regex, giving them a passthrough
// for their label selector.
return values[0], nil
case selection.Exists, selection.DoesNotExist:
return "", ErrQueryUnsupportedValues
}
default:
switch operator {
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.In, selection.NotIn:
// Pass the value through as-is.
// It's somewhat strange to do this for both the regex and equality
// operators, but if we do it this way it gives the user a little more control.
// They might choose to send an "IN" request and give a list of static values
// or they could send a single value that's a regex, giving them a passthrough
// for their label selector.
return strings.Join(values, "|"), nil
case selection.Exists, selection.DoesNotExist:
return "", ErrQueryUnsupportedValues
}
}
return "", errors.New("operator not supported by query builder")
}
func (q *metricsQuery) operatorIsSupported(operator selection.Operator) bool {
return operator != selection.GreaterThan && operator != selection.LessThan
}

View file

@ -0,0 +1,453 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package naming
import (
"fmt"
"testing"
labels "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
pmodel "github.com/prometheus/common/model"
)
type resourceConverterMock struct {
namespaced bool
}
// ResourcesForSeries is a mock that returns a single group resource,
// namely the series as a resource itself.
func (rcm *resourceConverterMock) ResourcesForSeries(series prom.Series) (res []schema.GroupResource, namespaced bool) {
return []schema.GroupResource{{Resource: series.Name}}, rcm.namespaced
}
// LabelForResource is a mock that returns the label name,
// simply by taking the given resource.
func (rcm *resourceConverterMock) LabelForResource(gr schema.GroupResource) (pmodel.LabelName, error) {
return pmodel.LabelName(gr.Resource), nil
}
type checkFunc func(prom.Selector, error) error
func hasError(want error) checkFunc {
return func(_ prom.Selector, got error) error {
if want != got {
return fmt.Errorf("got error %v, want %v", got, want)
}
return nil
}
}
func hasSelector(want string) checkFunc {
return func(got prom.Selector, _ error) error {
if prom.Selector(want) != got {
return fmt.Errorf("got selector %q, want %q", got, want)
}
return nil
}
}
func checks(cs ...checkFunc) checkFunc {
return func(s prom.Selector, e error) error {
for _, c := range cs {
if err := c(s, e); err != nil {
return err
}
}
return nil
}
}
func TestBuildSelector(t *testing.T) {
mustNewQuery := func(queryTemplate string, namespaced bool) MetricsQuery {
mq, err := NewMetricsQuery(queryTemplate, &resourceConverterMock{namespaced})
if err != nil {
t.Fatal(err)
}
return mq
}
mustNewLabelRequirement := func(key string, op selection.Operator, vals []string) *labels.Requirement {
req, err := labels.NewRequirement(key, op, vals)
if err != nil {
t.Fatal(err)
}
return req
}
tests := []struct {
name string
mq MetricsQuery
series string
resource schema.GroupResource
namespace string
extraGroupBy []string
metricSelector labels.Selector
names []string
check checkFunc
}{
{
name: "series",
mq: mustNewQuery(`series <<.Series>>`, false),
metricSelector: labels.NewSelector(),
series: "foo",
check: checks(
hasError(nil),
hasSelector("series foo"),
),
},
{
name: "multiple LabelMatchers values",
mq: mustNewQuery(`<<.LabelMatchers>>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
names: []string{"bar", "baz"},
check: checks(
hasError(nil),
hasSelector(`resource=~"bar|baz"`),
),
},
{
name: "single LabelMatchers value",
mq: mustNewQuery(`<<.LabelMatchers>>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
names: []string{"bar"},
check: checks(
hasError(nil),
hasSelector(`resource="bar"`),
),
},
{
name: "LabelMatchers with additional metrics filter",
mq: mustNewQuery(`<<.LabelMatchers>>`, false),
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("metric1", selection.Equals, []string{"value1"}),
),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
names: []string{"bar"},
check: checks(
hasError(nil),
hasSelector(`metric1="value1",resource="bar"`),
),
},
{
name: "single LabelValuesByName value",
mq: mustNewQuery(`<<index .LabelValuesByName "resource">>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
names: []string{"bar"},
check: checks(
hasError(nil),
hasSelector("bar"),
),
},
{
name: "multiple LabelValuesByName values",
mq: mustNewQuery(`<<index .LabelValuesByName "resource">>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
names: []string{"bar", "baz"},
check: checks(
hasError(nil),
hasSelector("bar|baz"),
),
},
{
name: "multiple LabelValuesByName values with namespace",
mq: mustNewQuery(`<<index .LabelValuesByName "namespaces">> <<index .LabelValuesByName "resource">>`, true),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
namespace: "default",
names: []string{"bar", "baz"},
check: checks(
hasError(nil),
hasSelector("default bar|baz"),
),
},
{
name: "single GroupBy value",
mq: mustNewQuery(`<<.GroupBy>>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
check: checks(
hasError(nil),
hasSelector("resource"),
),
},
{
name: "multiple GroupBy values",
mq: mustNewQuery(`<<.GroupBy>>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
extraGroupBy: []string{"extra", "groups"},
check: checks(
hasError(nil),
hasSelector("resource,extra,groups"),
),
},
{
name: "single GroupBySlice value",
mq: mustNewQuery(`<<.GroupBySlice>>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
check: checks(
hasError(nil),
hasSelector("[resource]"),
),
},
{
name: "multiple GroupBySlice values",
mq: mustNewQuery(`<<.GroupBySlice>>`, false),
metricSelector: labels.NewSelector(),
resource: schema.GroupResource{Group: "group", Resource: "resource"},
extraGroupBy: []string{"extra", "groups"},
check: checks(
hasError(nil),
hasSelector("[resource extra groups]"),
),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
selector, err := tc.mq.Build(tc.series, tc.resource, tc.namespace, tc.extraGroupBy, tc.metricSelector, tc.names...)
if err := tc.check(selector, err); err != nil {
t.Error(err)
}
})
}
}
func TestBuildExternalSelector(t *testing.T) {
mustNewQuery := func(queryTemplate string) MetricsQuery {
mq, err := NewExternalMetricsQuery(queryTemplate, &resourceConverterMock{true}, true)
if err != nil {
t.Fatal(err)
}
return mq
}
mustNewNonNamespacedQuery := func(queryTemplate string) MetricsQuery {
mq, err := NewExternalMetricsQuery(queryTemplate, &resourceConverterMock{true}, false)
if err != nil {
t.Fatal(err)
}
return mq
}
mustNewLabelRequirement := func(key string, op selection.Operator, vals []string) *labels.Requirement {
req, err := labels.NewRequirement(key, op, vals)
if err != nil {
t.Fatal(err)
}
return req
}
tests := []struct {
name string
mq MetricsQuery
series string
namespace string
groupBy string
groupBySlice []string
metricSelector labels.Selector
check checkFunc
}{
{
name: "series",
mq: mustNewQuery(`series <<.Series>>`),
series: "foo",
metricSelector: labels.NewSelector(),
check: checks(
hasError(nil),
hasSelector("series foo"),
),
},
{
name: "single GroupBy value",
mq: mustNewQuery(`<<.GroupBy>>`),
groupBy: "foo",
metricSelector: labels.NewSelector(),
check: checks(
hasError(nil),
hasSelector("foo"),
),
},
{
name: "multiple GroupBySlice values",
mq: mustNewQuery(`<<.GroupBySlice>>`),
groupBySlice: []string{"foo", "bar"},
metricSelector: labels.NewSelector(),
check: checks(
hasError(nil),
hasSelector("[foo bar]"),
),
},
{
name: "multiple GroupBySlice values with namespace",
mq: mustNewQuery(`<<index .LabelValuesByName "namespaces">> <<.GroupBySlice>>`),
namespace: "default",
groupBySlice: []string{"foo", "bar"},
metricSelector: labels.NewSelector(),
check: checks(
hasError(nil),
hasSelector("default [foo bar]"),
),
},
{
name: "multiple GroupBySlice values with namespace disabled",
mq: mustNewNonNamespacedQuery(`<<index .LabelValuesByName "namespaces">> <<.GroupBySlice>>`),
namespace: "default",
groupBySlice: []string{"foo", "bar"},
metricSelector: labels.NewSelector(),
check: checks(
hasError(nil),
hasSelector(" [foo bar]"),
),
},
{
name: "single LabelMatchers value",
mq: mustNewQuery(`<<.LabelMatchers>>`),
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("foo", selection.Equals, []string{"bar"}),
),
check: checks(
hasError(nil),
hasSelector(`foo="bar"`),
),
},
{
name: "single LabelMatchers value with namespace",
mq: mustNewQuery(`<<.LabelMatchers>>`),
namespace: "default",
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("foo", selection.Equals, []string{"bar"}),
),
check: checks(
hasError(nil),
hasSelector(`foo="bar",namespaces="default"`),
),
},
{
name: "multiple LabelMatchers value",
mq: mustNewQuery(`<<.LabelMatchers>>`),
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("foo", selection.Equals, []string{"bar"}),
*mustNewLabelRequirement("qux", selection.In, []string{"bar", "baz"}),
),
check: checks(
hasError(nil),
hasSelector(`foo="bar",qux=~"bar|baz"`),
),
},
{
name: "single LabelValuesByName value",
mq: mustNewQuery(`<<.LabelValuesByName>>`),
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("foo", selection.Equals, []string{"bar"}),
),
check: checks(
hasError(nil),
hasSelector("map[foo:bar]"),
),
},
{
name: "single LabelValuesByName with multiple selectors",
mq: mustNewQuery(`<<.LabelValuesByName>>`),
metricSelector: labels.NewSelector().Add(
*mustNewLabelRequirement("foo", selection.In, []string{"bar", "baz"}),
),
check: checks(
hasError(nil),
hasSelector("map[foo:bar|baz]"),
),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
selector, err := tc.mq.BuildExternal(tc.series, tc.namespace, tc.groupBy, tc.groupBySlice, tc.metricSelector)
t.Logf("selector: '%v'", selector)
if err := tc.check(selector, err); err != nil {
t.Error(err)
}
})
}
}

View file

@ -0,0 +1,65 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package naming
import (
"testing"
"github.com/stretchr/testify/require"
"sigs.k8s.io/prometheus-adapter/pkg/config"
)
func TestReMatcherIs(t *testing.T) {
filter := config.RegexFilter{
Is: "my_.*",
}
matcher, err := NewReMatcher(filter)
require.NoError(t, err)
result := matcher.Matches("my_label")
require.True(t, result)
result = matcher.Matches("your_label")
require.False(t, result)
}
func TestReMatcherIsNot(t *testing.T) {
filter := config.RegexFilter{
IsNot: "my_.*",
}
matcher, err := NewReMatcher(filter)
require.NoError(t, err)
result := matcher.Matches("my_label")
require.False(t, result)
result = matcher.Matches("your_label")
require.True(t, result)
}
func TestEnforcesIsOrIsNotButNotBoth(t *testing.T) {
filter := config.RegexFilter{
Is: "my_.*",
IsNot: "your_.*",
}
_, err := NewReMatcher(filter)
require.Error(t, err)
}

View file

@ -0,0 +1,220 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package naming
import (
"bytes"
"fmt"
"strings"
"sync"
"text/template"
pmodel "github.com/prometheus/common/model"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/config"
)
var (
GroupNameSanitizer = strings.NewReplacer(".", "_", "-", "_")
NsGroupResource = schema.GroupResource{Resource: "namespaces"}
NodeGroupResource = schema.GroupResource{Resource: "nodes"}
PVGroupResource = schema.GroupResource{Resource: "persistentvolumes"}
)
// ResourceConverter knows the relationship between Kubernetes group-resources and Prometheus labels,
// and can convert between the two for any given label or series.
type ResourceConverter interface {
// ResourcesForSeries returns the group-resources associated with the given series,
// as well as whether or not the given series has the "namespace" resource).
ResourcesForSeries(series prom.Series) (res []schema.GroupResource, namespaced bool)
// LabelForResource returns the appropriate label for the given resource.
LabelForResource(resource schema.GroupResource) (pmodel.LabelName, error)
}
type resourceConverter struct {
labelResourceMu sync.RWMutex
labelToResource map[pmodel.LabelName]schema.GroupResource
resourceToLabel map[schema.GroupResource]pmodel.LabelName
labelResExtractor *labelGroupResExtractor
mapper apimeta.RESTMapper
labelTemplate *template.Template
}
// NewResourceConverter creates a ResourceConverter based on a generic template plus any overrides.
// Either overrides or the template may be empty, but not both.
func NewResourceConverter(resourceTemplate string, overrides map[string]config.GroupResource, mapper apimeta.RESTMapper) (ResourceConverter, error) {
converter := &resourceConverter{
labelToResource: make(map[pmodel.LabelName]schema.GroupResource),
resourceToLabel: make(map[schema.GroupResource]pmodel.LabelName),
mapper: mapper,
}
if resourceTemplate != "" {
labelTemplate, err := template.New("resource-label").Delims("<<", ">>").Parse(resourceTemplate)
if err != nil {
return converter, fmt.Errorf("unable to parse label template %q: %v", resourceTemplate, err)
}
converter.labelTemplate = labelTemplate
labelResExtractor, err := newLabelGroupResExtractor(labelTemplate)
if err != nil {
return converter, fmt.Errorf("unable to generate label format from template %q: %v", resourceTemplate, err)
}
converter.labelResExtractor = labelResExtractor
}
// invert the structure for consistency with the template
for lbl, groupRes := range overrides {
infoRaw := provider.CustomMetricInfo{
GroupResource: schema.GroupResource{
Group: groupRes.Group,
Resource: groupRes.Resource,
},
}
info, _, err := infoRaw.Normalized(converter.mapper)
if err != nil {
return nil, fmt.Errorf("unable to normalize group-resource %v: %v", groupRes, err)
}
converter.labelToResource[pmodel.LabelName(lbl)] = info.GroupResource
converter.resourceToLabel[info.GroupResource] = pmodel.LabelName(lbl)
}
return converter, nil
}
func (r *resourceConverter) LabelForResource(resource schema.GroupResource) (pmodel.LabelName, error) {
r.labelResourceMu.RLock()
// check if we have a cached copy or override
lbl, ok := r.resourceToLabel[resource]
r.labelResourceMu.RUnlock() // release before we call makeLabelForResource
if ok {
return lbl, nil
}
// NB: we don't actually care about the gap between releasing read lock
// and acquiring the write lock -- if we do duplicate work sometimes, so be
// it, as long as we're correct.
// otherwise, use the template and save the result
lbl, err := r.makeLabelForResource(resource)
if err != nil {
return "", fmt.Errorf("unable to convert resource %s into label: %v", resource.String(), err)
}
return lbl, nil
}
// makeLabelForResource constructs a label name for the given resource, and saves the result.
// It must *not* be called under an existing lock.
func (r *resourceConverter) makeLabelForResource(resource schema.GroupResource) (pmodel.LabelName, error) {
if r.labelTemplate == nil {
return "", fmt.Errorf("no generic resource label form specified for this metric")
}
buff := new(bytes.Buffer)
singularRes, err := r.mapper.ResourceSingularizer(resource.Resource)
if err != nil {
return "", fmt.Errorf("unable to singularize resource %s: %v", resource.String(), err)
}
convResource := schema.GroupResource{
Group: GroupNameSanitizer.Replace(resource.Group),
Resource: singularRes,
}
if err := r.labelTemplate.Execute(buff, convResource); err != nil {
return "", err
}
if buff.Len() == 0 {
return "", fmt.Errorf("empty label produced by label template")
}
lbl := pmodel.LabelName(buff.String())
r.labelResourceMu.Lock()
defer r.labelResourceMu.Unlock()
r.resourceToLabel[resource] = lbl
r.labelToResource[lbl] = resource
return lbl, nil
}
func (r *resourceConverter) ResourcesForSeries(series prom.Series) ([]schema.GroupResource, bool) {
// use an updates map to avoid having to drop the read lock to update the cache
// until the end. Since we'll probably have few updates after the first run,
// this should mean that we rarely have to hold the write lock.
var resources []schema.GroupResource
updates := make(map[pmodel.LabelName]schema.GroupResource)
namespaced := false
// use an anon func to get the right defer behavior
func() {
r.labelResourceMu.RLock()
defer r.labelResourceMu.RUnlock()
for lbl := range series.Labels {
var groupRes schema.GroupResource
var ok bool
// check if we have an override
if groupRes, ok = r.labelToResource[lbl]; ok {
resources = append(resources, groupRes)
} else if groupRes, ok = updates[lbl]; ok {
resources = append(resources, groupRes)
} else if r.labelResExtractor != nil {
// if not, check if it matches the form we expect, and if so,
// convert to a group-resource.
if groupRes, ok = r.labelResExtractor.GroupResourceForLabel(lbl); ok {
info, _, err := provider.CustomMetricInfo{GroupResource: groupRes}.Normalized(r.mapper)
if err != nil {
// this is likely to show up for a lot of labels, so make it a verbose info log
klog.V(9).Infof("unable to normalize group-resource %s from label %q, skipping: %v", groupRes.String(), lbl, err)
continue
}
groupRes = info.GroupResource
resources = append(resources, groupRes)
updates[lbl] = groupRes
}
}
if groupRes != NsGroupResource && groupRes != NodeGroupResource && groupRes != PVGroupResource {
namespaced = true
}
}
}()
// update the cache for next time. This should only be called by discovery,
// so we don't really have to worry about the gap between read and write locks
// (plus, we don't care if someone else updates the cache first, since the results
// are necessarily the same, so at most we've done extra work).
if len(updates) > 0 {
r.labelResourceMu.Lock()
defer r.labelResourceMu.Unlock()
for lbl, groupRes := range updates {
r.labelToResource[lbl] = groupRes
}
}
return resources, namespaced
}

View file

@ -0,0 +1,420 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourceprovider
import (
"context"
"fmt"
"math"
"sync"
"time"
corev1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
metrics "k8s.io/metrics/pkg/apis/metrics"
"sigs.k8s.io/metrics-server/pkg/api"
"sigs.k8s.io/prometheus-adapter/pkg/client"
"sigs.k8s.io/prometheus-adapter/pkg/config"
"sigs.k8s.io/prometheus-adapter/pkg/naming"
pmodel "github.com/prometheus/common/model"
)
var (
nodeResource = schema.GroupResource{Resource: "nodes"}
podResource = schema.GroupResource{Resource: "pods"}
)
// TODO(directxman12): consider support for nanocore values -- adjust scale if less than 1 millicore, or greater than max int64
// newResourceQuery instantiates query information from the give configuration rule for querying
// resource metrics for some resource.
func newResourceQuery(cfg config.ResourceRule, mapper apimeta.RESTMapper) (resourceQuery, error) {
converter, err := naming.NewResourceConverter(cfg.Resources.Template, cfg.Resources.Overrides, mapper)
if err != nil {
return resourceQuery{}, fmt.Errorf("unable to construct label-resource converter: %v", err)
}
contQuery, err := naming.NewMetricsQuery(cfg.ContainerQuery, converter)
if err != nil {
return resourceQuery{}, fmt.Errorf("unable to construct container metrics query: %v", err)
}
nodeQuery, err := naming.NewMetricsQuery(cfg.NodeQuery, converter)
if err != nil {
return resourceQuery{}, fmt.Errorf("unable to construct node metrics query: %v", err)
}
return resourceQuery{
converter: converter,
contQuery: contQuery,
nodeQuery: nodeQuery,
containerLabel: cfg.ContainerLabel,
}, nil
}
// resourceQuery represents query information for querying resource metrics for some resource,
// like CPU or memory.
type resourceQuery struct {
converter naming.ResourceConverter
contQuery naming.MetricsQuery
nodeQuery naming.MetricsQuery
containerLabel string
}
// NewProvider constructs a new MetricsProvider to provide resource metrics from Prometheus using the given rules.
func NewProvider(prom client.Client, mapper apimeta.RESTMapper, cfg *config.ResourceRules) (api.MetricsGetter, error) {
cpuQuery, err := newResourceQuery(cfg.CPU, mapper)
if err != nil {
return nil, fmt.Errorf("unable to construct querier for CPU metrics: %v", err)
}
memQuery, err := newResourceQuery(cfg.Memory, mapper)
if err != nil {
return nil, fmt.Errorf("unable to construct querier for memory metrics: %v", err)
}
return &resourceProvider{
prom: prom,
cpu: cpuQuery,
mem: memQuery,
window: time.Duration(cfg.Window),
}, nil
}
// resourceProvider is a MetricsProvider that contacts Prometheus to provide
// the resource metrics.
type resourceProvider struct {
prom client.Client
cpu, mem resourceQuery
window time.Duration
}
// nsQueryResults holds the results of one set
// of queries necessary to construct a resource metrics
// API response for a single namespace.
type nsQueryResults struct {
namespace string
cpu, mem queryResults
err error
}
// GetPodMetrics implements the api.MetricsProvider interface.
func (p *resourceProvider) GetPodMetrics(pods ...*metav1.PartialObjectMetadata) ([]metrics.PodMetrics, error) {
resMetrics := make([]metrics.PodMetrics, 0, len(pods))
if len(pods) == 0 {
return resMetrics, nil
}
// TODO(directxman12): figure out how well this scales if we go to list 1000+ pods
// (and consider adding timeouts)
// group pods by namespace (we could be listing for all pods in the cluster)
podsByNs := make(map[string][]string, len(pods))
for _, pod := range pods {
podsByNs[pod.Namespace] = append(podsByNs[pod.Namespace], pod.Name)
}
// actually fetch the results for each namespace
now := pmodel.Now()
resChan := make(chan nsQueryResults, len(podsByNs))
var wg sync.WaitGroup
wg.Add(len(podsByNs))
for ns, podNames := range podsByNs {
go func(ns string, podNames []string) {
defer wg.Done()
resChan <- p.queryBoth(now, podResource, ns, podNames...)
}(ns, podNames)
}
wg.Wait()
close(resChan)
// index those results in a map for easy lookup
resultsByNs := make(map[string]nsQueryResults, len(podsByNs))
for result := range resChan {
if result.err != nil {
klog.Errorf("unable to fetch metrics for pods in namespace %q, skipping: %v", result.namespace, result.err)
continue
}
resultsByNs[result.namespace] = result
}
// convert the unorganized per-container results into results grouped
// together by namespace, pod, and container
for _, pod := range pods {
podMetric := p.assignForPod(pod, resultsByNs)
if podMetric != nil {
resMetrics = append(resMetrics, *podMetric)
}
}
return resMetrics, nil
}
// assignForPod takes the resource metrics for all containers in the given pod
// from resultsByNs, and places them in MetricsProvider response format in resMetrics,
// also recording the earliest time in resTime. It will return without operating if
// any data is missing.
func (p *resourceProvider) assignForPod(pod *metav1.PartialObjectMetadata, resultsByNs map[string]nsQueryResults) *metrics.PodMetrics {
// check to make sure everything is present
nsRes, nsResPresent := resultsByNs[pod.Namespace]
if !nsResPresent {
klog.Errorf("unable to fetch metrics for pods in namespace %q, skipping pod %s", pod.Namespace, pod.String())
return nil
}
cpuRes, hasResult := nsRes.cpu[pod.Name]
if !hasResult {
klog.Errorf("unable to fetch CPU metrics for pod %s, skipping", pod.String())
return nil
}
memRes, hasResult := nsRes.mem[pod.Name]
if !hasResult {
klog.Errorf("unable to fetch memory metrics for pod %s, skipping", pod.String())
return nil
}
containerMetrics := make(map[string]metrics.ContainerMetrics)
earliestTS := pmodel.Latest
// organize all the CPU results
for _, cpu := range cpuRes {
containerName := string(cpu.Metric[pmodel.LabelName(p.cpu.containerLabel)])
if _, present := containerMetrics[containerName]; !present {
containerMetrics[containerName] = metrics.ContainerMetrics{
Name: containerName,
Usage: corev1.ResourceList{},
}
}
containerMetrics[containerName].Usage[corev1.ResourceCPU] = *resource.NewMilliQuantity(int64(cpu.Value*1000.0), resource.DecimalSI)
if cpu.Timestamp.Before(earliestTS) {
earliestTS = cpu.Timestamp
}
}
// organize the memory results
for _, mem := range memRes {
containerName := string(mem.Metric[pmodel.LabelName(p.mem.containerLabel)])
if _, present := containerMetrics[containerName]; !present {
containerMetrics[containerName] = metrics.ContainerMetrics{
Name: containerName,
Usage: corev1.ResourceList{},
}
}
containerMetrics[containerName].Usage[corev1.ResourceMemory] = *resource.NewMilliQuantity(int64(mem.Value*1000.0), resource.BinarySI)
if mem.Timestamp.Before(earliestTS) {
earliestTS = mem.Timestamp
}
}
// check for any containers that have either memory usage or CPU usage, but not both
for _, containerMetric := range containerMetrics {
_, hasMemory := containerMetric.Usage[corev1.ResourceMemory]
_, hasCPU := containerMetric.Usage[corev1.ResourceCPU]
if hasMemory && !hasCPU {
containerMetric.Usage[corev1.ResourceCPU] = *resource.NewMilliQuantity(int64(0), resource.BinarySI)
} else if hasCPU && !hasMemory {
containerMetric.Usage[corev1.ResourceMemory] = *resource.NewMilliQuantity(int64(0), resource.BinarySI)
}
}
podMetric := &metrics.PodMetrics{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
Labels: pod.Labels,
CreationTimestamp: metav1.Now(),
},
// store the time in the final format
Timestamp: metav1.NewTime(earliestTS.Time()),
Window: metav1.Duration{Duration: p.window},
}
// store the container metrics in the final format
podMetric.Containers = make([]metrics.ContainerMetrics, 0, len(containerMetrics))
for _, containerMetric := range containerMetrics {
podMetric.Containers = append(podMetric.Containers, containerMetric)
}
return podMetric
}
// GetNodeMetrics implements the api.MetricsProvider interface.
func (p *resourceProvider) GetNodeMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error) {
resMetrics := make([]metrics.NodeMetrics, 0, len(nodes))
if len(nodes) == 0 {
return resMetrics, nil
}
now := pmodel.Now()
nodeNames := make([]string, 0, len(nodes))
for _, node := range nodes {
nodeNames = append(nodeNames, node.Name)
}
// run the actual query
qRes := p.queryBoth(now, nodeResource, "", nodeNames...)
if qRes.err != nil {
klog.Errorf("failed querying node metrics: %v", qRes.err)
return resMetrics, nil
}
// organize the results
for i, nodeName := range nodeNames {
// skip if any data is missing
rawCPUs, gotResult := qRes.cpu[nodeName]
if !gotResult {
klog.V(1).Infof("missing CPU for node %q, skipping", nodeName)
continue
}
rawMems, gotResult := qRes.mem[nodeName]
if !gotResult {
klog.V(1).Infof("missing memory for node %q, skipping", nodeName)
continue
}
rawMem := rawMems[0]
rawCPU := rawCPUs[0]
// use the earliest timestamp available (in order to be conservative
// when determining if metrics are tainted by startup)
ts := rawCPU.Timestamp.Time()
if ts.After(rawMem.Timestamp.Time()) {
ts = rawMem.Timestamp.Time()
}
// store the results
resMetrics = append(resMetrics, metrics.NodeMetrics{
ObjectMeta: metav1.ObjectMeta{
Name: nodes[i].Name,
Labels: nodes[i].Labels,
CreationTimestamp: metav1.Now(),
},
Usage: corev1.ResourceList{
corev1.ResourceCPU: *resource.NewMilliQuantity(int64(rawCPU.Value*1000.0), resource.DecimalSI),
corev1.ResourceMemory: *resource.NewMilliQuantity(int64(rawMem.Value*1000.0), resource.BinarySI),
},
Timestamp: metav1.NewTime(ts),
Window: metav1.Duration{Duration: p.window},
})
}
return resMetrics, nil
}
// queryBoth queries for both CPU and memory metrics on the given
// Kubernetes API resource (pods or nodes), and errors out if
// either query fails.
func (p *resourceProvider) queryBoth(now pmodel.Time, resource schema.GroupResource, namespace string, names ...string) nsQueryResults {
var cpuRes, memRes queryResults
var cpuErr, memErr error
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
cpuRes, cpuErr = p.runQuery(now, p.cpu, resource, namespace, names...)
}()
go func() {
defer wg.Done()
memRes, memErr = p.runQuery(now, p.mem, resource, namespace, names...)
}()
wg.Wait()
if cpuErr != nil {
return nsQueryResults{
namespace: namespace,
err: fmt.Errorf("unable to fetch node CPU metrics: %v", cpuErr),
}
}
if memErr != nil {
return nsQueryResults{
namespace: namespace,
err: fmt.Errorf("unable to fetch node memory metrics: %v", memErr),
}
}
return nsQueryResults{
namespace: namespace,
cpu: cpuRes,
mem: memRes,
}
}
// queryResults maps an object name to all the results matching that object
type queryResults map[string][]*pmodel.Sample
// runQuery actually queries Prometheus for the metric represented by the given query information, on
// the given Kubernetes API resource (pods or nodes).
func (p *resourceProvider) runQuery(now pmodel.Time, queryInfo resourceQuery, resource schema.GroupResource, namespace string, names ...string) (queryResults, error) {
var query client.Selector
var err error
// build the query, which needs the special "container" group by if this is for pod metrics
if resource == nodeResource {
query, err = queryInfo.nodeQuery.Build("", resource, namespace, nil, labels.Everything(), names...)
} else {
extraGroupBy := []string{queryInfo.containerLabel}
query, err = queryInfo.contQuery.Build("", resource, namespace, extraGroupBy, labels.Everything(), names...)
}
if err != nil {
return nil, fmt.Errorf("unable to construct query: %v", err)
}
// run the query
rawRes, err := p.prom.Query(context.Background(), now, query)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %v", err)
}
if rawRes.Type != pmodel.ValVector || rawRes.Vector == nil {
return nil, fmt.Errorf("invalid or empty value of non-vector type (%s) returned", rawRes.Type)
}
// check the appropriate label for the resource in question
resourceLbl, err := queryInfo.converter.LabelForResource(resource)
if err != nil {
return nil, fmt.Errorf("unable to find label for resource %s: %v", resource.String(), err)
}
// associate the results back to each given pod or node
res := make(queryResults, len(*rawRes.Vector))
for _, sample := range *rawRes.Vector {
// skip empty samples
if sample == nil {
continue
}
// replace NaN and negative values by zero
if math.IsNaN(float64(sample.Value)) || sample.Value < 0 {
sample.Value = 0
}
resKey := string(sample.Metric[resourceLbl])
res[resKey] = append(res[resKey], sample)
}
return res, nil
}

View file

@ -0,0 +1,29 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourceprovider
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestProvider(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Resource Metrics Provider Suite")
}

View file

@ -0,0 +1,352 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resourceprovider
import (
"math"
"time"
corev1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/metrics/pkg/apis/metrics"
"sigs.k8s.io/metrics-server/pkg/api"
config "sigs.k8s.io/prometheus-adapter/cmd/config-gen/utils"
prom "sigs.k8s.io/prometheus-adapter/pkg/client"
fakeprom "sigs.k8s.io/prometheus-adapter/pkg/client/fake"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
pmodel "github.com/prometheus/common/model"
)
func restMapper() apimeta.RESTMapper {
mapper := apimeta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
mapper.Add(corev1.SchemeGroupVersion.WithKind("Pod"), apimeta.RESTScopeNamespace)
mapper.Add(corev1.SchemeGroupVersion.WithKind("Node"), apimeta.RESTScopeRoot)
mapper.Add(corev1.SchemeGroupVersion.WithKind("Namespace"), apimeta.RESTScopeRoot)
return mapper
}
func buildPodSample(namespace, pod, container string, val float64, ts int64) *pmodel.Sample {
return &pmodel.Sample{
Metric: pmodel.Metric{
"namespace": pmodel.LabelValue(namespace),
"pod": pmodel.LabelValue(pod),
"container": pmodel.LabelValue(container),
},
Value: pmodel.SampleValue(val),
Timestamp: pmodel.Time(ts),
}
}
func buildNodeSample(node string, val float64, ts int64) *pmodel.Sample {
return &pmodel.Sample{
Metric: pmodel.Metric{
"instance": pmodel.LabelValue(node),
"id": "/",
},
Value: pmodel.SampleValue(val),
Timestamp: pmodel.Time(ts),
}
}
func buildQueryRes(metric string, samples ...*pmodel.Sample) prom.QueryResult {
for _, sample := range samples {
sample.Metric[pmodel.MetricNameLabel] = pmodel.LabelValue(metric)
}
vec := pmodel.Vector(samples)
return prom.QueryResult{
Type: pmodel.ValVector,
Vector: &vec,
}
}
func mustBuild(sel prom.Selector, err error) prom.Selector {
Expect(err).NotTo(HaveOccurred())
return sel
}
func buildResList(cpu, memory float64) corev1.ResourceList {
return corev1.ResourceList{
corev1.ResourceCPU: *resource.NewMilliQuantity(int64(cpu*1000.0), resource.DecimalSI),
corev1.ResourceMemory: *resource.NewMilliQuantity(int64(memory*1000.0), resource.BinarySI),
}
}
var _ = Describe("Resource Metrics Provider", func() {
var (
prov api.MetricsGetter
fakeProm *fakeprom.FakePrometheusClient
cpuQueries, memQueries resourceQuery
)
BeforeEach(func() {
By("setting up a fake prometheus client and provider")
mapper := restMapper()
cfg := config.DefaultConfig(1*time.Minute, "")
var err error
cpuQueries, err = newResourceQuery(cfg.ResourceRules.CPU, mapper)
Expect(err).NotTo(HaveOccurred())
memQueries, err = newResourceQuery(cfg.ResourceRules.Memory, mapper)
Expect(err).NotTo(HaveOccurred())
fakeProm = &fakeprom.FakePrometheusClient{}
fakeProm.AcceptableInterval = pmodel.Interval{End: pmodel.Latest}
prov, err = NewProvider(fakeProm, restMapper(), cfg.ResourceRules)
Expect(err).NotTo(HaveOccurred())
})
It("should be able to list metrics pods across different namespaces", func() {
pods := []*metav1.PartialObjectMetadata{
{ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "pod1"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "pod3"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "other-ns", Name: "pod27"}},
}
fakeProm.QueryResults = map[prom.Selector]prom.QueryResult{
mustBuild(cpuQueries.contQuery.Build("", podResource, "some-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod1", "pod3")): buildQueryRes("container_cpu_usage_seconds_total",
buildPodSample("some-ns", "pod1", "cont1", 1100.0, 10),
buildPodSample("some-ns", "pod1", "cont2", 1110.0, 20),
buildPodSample("some-ns", "pod3", "cont1", 1300.0, 10),
buildPodSample("some-ns", "pod3", "cont2", 1310.0, 20),
),
mustBuild(cpuQueries.contQuery.Build("", podResource, "other-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod27")): buildQueryRes("container_cpu_usage_seconds_total",
buildPodSample("other-ns", "pod27", "cont1", 2200.0, 270),
),
mustBuild(memQueries.contQuery.Build("", podResource, "some-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod1", "pod3")): buildQueryRes("container_memory_working_set_bytes",
buildPodSample("some-ns", "pod1", "cont1", 3100.0, 11),
buildPodSample("some-ns", "pod1", "cont2", 3110.0, 21),
buildPodSample("some-ns", "pod3", "cont1", 3300.0, 11),
buildPodSample("some-ns", "pod3", "cont2", 3310.0, 21),
),
mustBuild(memQueries.contQuery.Build("", podResource, "other-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod27")): buildQueryRes("container_memory_working_set_bytes",
buildPodSample("other-ns", "pod27", "cont1", 4200.0, 271),
),
}
By("querying for metrics for some pods")
podMetrics, err := prov.GetPodMetrics(pods...)
Expect(err).NotTo(HaveOccurred())
By("verifying that metrics have been fetched for all the pods")
Expect(podMetrics).To(HaveLen(3))
By("verifying that the reported times for each are the earliest times for each pod")
Expect(podMetrics[0].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(podMetrics[0].Window.Duration).To(Equal(time.Minute))
Expect(podMetrics[1].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(podMetrics[1].Window.Duration).To(Equal(time.Minute))
Expect(podMetrics[2].Timestamp.Time).To(Equal(pmodel.Time(270).Time()))
Expect(podMetrics[2].Window.Duration).To(Equal(time.Minute))
By("verifying that the right metrics were fetched")
Expect(podMetrics).To(HaveLen(3))
Expect(podMetrics[0].Containers).To(ConsistOf(
metrics.ContainerMetrics{Name: "cont1", Usage: buildResList(1100.0, 3100.0)},
metrics.ContainerMetrics{Name: "cont2", Usage: buildResList(1110.0, 3110.0)},
))
Expect(podMetrics[1].Containers).To(ConsistOf(
metrics.ContainerMetrics{Name: "cont1", Usage: buildResList(1300.0, 3300.0)},
metrics.ContainerMetrics{Name: "cont2", Usage: buildResList(1310.0, 3310.0)},
))
Expect(podMetrics[2].Containers).To(ConsistOf(
metrics.ContainerMetrics{Name: "cont1", Usage: buildResList(2200.0, 4200.0)},
))
})
It("should return nil metrics for missing pods, but still return partial results", func() {
fakeProm.QueryResults = map[prom.Selector]prom.QueryResult{
mustBuild(cpuQueries.contQuery.Build("", podResource, "some-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod1", "pod-nonexistant")): buildQueryRes("container_cpu_usage_seconds_total",
buildPodSample("some-ns", "pod1", "cont1", 1100.0, 10),
buildPodSample("some-ns", "pod1", "cont2", 1110.0, 20),
),
mustBuild(memQueries.contQuery.Build("", podResource, "some-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod1", "pod-nonexistant")): buildQueryRes("container_memory_working_set_bytes",
buildPodSample("some-ns", "pod1", "cont1", 3100.0, 11),
buildPodSample("some-ns", "pod1", "cont2", 3110.0, 21),
),
}
By("querying for metrics for some pods, one of which is missing")
podMetrics, err := prov.GetPodMetrics(
&metav1.PartialObjectMetadata{ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "pod1"}},
&metav1.PartialObjectMetadata{ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "pod-nonexistant"}},
)
Expect(err).NotTo(HaveOccurred())
By("verifying that the missing pod had no metrics")
Expect(podMetrics).To(HaveLen(1))
By("verifying that the rest of time metrics and times are correct")
Expect(podMetrics[0].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(podMetrics[0].Window.Duration).To(Equal(time.Minute))
Expect(podMetrics[0].Containers).To(ConsistOf(
metrics.ContainerMetrics{Name: "cont1", Usage: buildResList(1100.0, 3100.0)},
metrics.ContainerMetrics{Name: "cont2", Usage: buildResList(1110.0, 3110.0)},
))
})
It("should return metrics of value zero when pod metrics have NaN or negative values", func() {
fakeProm.QueryResults = map[prom.Selector]prom.QueryResult{
mustBuild(cpuQueries.contQuery.Build("", podResource, "some-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod1", "pod3")): buildQueryRes("container_cpu_usage_seconds_total",
buildPodSample("some-ns", "pod1", "cont1", -1100.0, 10),
buildPodSample("some-ns", "pod1", "cont2", math.NaN(), 20),
buildPodSample("some-ns", "pod3", "cont1", -1300.0, 10),
buildPodSample("some-ns", "pod3", "cont2", 1310.0, 20),
),
mustBuild(memQueries.contQuery.Build("", podResource, "some-ns", []string{cpuQueries.containerLabel}, labels.Everything(), "pod1", "pod3")): buildQueryRes("container_memory_working_set_bytes",
buildPodSample("some-ns", "pod1", "cont1", 3100.0, 11),
buildPodSample("some-ns", "pod1", "cont2", -3110.0, 21),
buildPodSample("some-ns", "pod3", "cont1", math.NaN(), 11),
buildPodSample("some-ns", "pod3", "cont2", -3310.0, 21),
),
}
By("querying for metrics for some pods")
podMetrics, err := prov.GetPodMetrics(
&metav1.PartialObjectMetadata{ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "pod1"}},
&metav1.PartialObjectMetadata{ObjectMeta: metav1.ObjectMeta{Namespace: "some-ns", Name: "pod3"}},
)
Expect(err).NotTo(HaveOccurred())
By("verifying that metrics have been fetched for all the pods")
Expect(podMetrics).To(HaveLen(2))
By("verifying that the reported times for each are the earliest times for each pod")
Expect(podMetrics[0].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(podMetrics[0].Window.Duration).To(Equal(time.Minute))
Expect(podMetrics[1].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(podMetrics[1].Window.Duration).To(Equal(time.Minute))
By("verifying that NaN and negative values were replaced by zero")
Expect(podMetrics[0].Containers).To(ConsistOf(
metrics.ContainerMetrics{Name: "cont1", Usage: buildResList(0, 3100.0)},
metrics.ContainerMetrics{Name: "cont2", Usage: buildResList(0, 0)},
))
Expect(podMetrics[1].Containers).To(ConsistOf(
metrics.ContainerMetrics{Name: "cont1", Usage: buildResList(0, 0)},
metrics.ContainerMetrics{Name: "cont2", Usage: buildResList(1310.0, 0)},
))
})
It("should be able to list metrics for nodes", func() {
fakeProm.QueryResults = map[prom.Selector]prom.QueryResult{
mustBuild(cpuQueries.nodeQuery.Build("", nodeResource, "", nil, labels.Everything(), "node1", "node2")): buildQueryRes("container_cpu_usage_seconds_total",
buildNodeSample("node1", 1100.0, 10),
buildNodeSample("node2", 1200.0, 14),
),
mustBuild(memQueries.nodeQuery.Build("", nodeResource, "", nil, labels.Everything(), "node1", "node2")): buildQueryRes("container_memory_working_set_bytes",
buildNodeSample("node1", 2100.0, 11),
buildNodeSample("node2", 2200.0, 12),
),
}
By("querying for metrics for some nodes")
nodeMetrics, err := prov.GetNodeMetrics(
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1"}},
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node2"}},
)
Expect(err).NotTo(HaveOccurred())
By("verifying that metrics have been fetched for all the nodes")
Expect(nodeMetrics).To(HaveLen(2))
By("verifying that the reported times for each are the earliest times for each node")
Expect(nodeMetrics[0].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(nodeMetrics[0].Window.Duration).To(Equal(time.Minute))
Expect(nodeMetrics[1].Timestamp.Time).To(Equal(pmodel.Time(12).Time()))
Expect(nodeMetrics[1].Window.Duration).To(Equal(time.Minute))
By("verifying that the right metrics were fetched")
Expect(nodeMetrics[0].Usage).To(Equal(buildResList(1100.0, 2100.0)))
Expect(nodeMetrics[1].Usage).To(Equal(buildResList(1200.0, 2200.0)))
})
It("should return nil metrics for missing nodes, but still return partial results", func() {
fakeProm.QueryResults = map[prom.Selector]prom.QueryResult{
mustBuild(cpuQueries.nodeQuery.Build("", nodeResource, "", nil, labels.Everything(), "node1", "node2", "node3")): buildQueryRes("container_cpu_usage_seconds_total",
buildNodeSample("node1", 1100.0, 10),
buildNodeSample("node2", 1200.0, 14),
),
mustBuild(memQueries.nodeQuery.Build("", nodeResource, "", nil, labels.Everything(), "node1", "node2", "node3")): buildQueryRes("container_memory_working_set_bytes",
buildNodeSample("node1", 2100.0, 11),
buildNodeSample("node2", 2200.0, 12),
),
}
By("querying for metrics for some nodes, one of which is missing")
nodeMetrics, err := prov.GetNodeMetrics(
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1"}},
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node2"}},
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node3"}},
)
Expect(err).NotTo(HaveOccurred())
By("verifying that the missing pod had no metrics")
Expect(nodeMetrics).To(HaveLen(2))
By("verifying that the rest of time metrics and times are correct")
Expect(nodeMetrics[0].Usage).To(Equal(buildResList(1100.0, 2100.0)))
Expect(nodeMetrics[0].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(nodeMetrics[0].Window.Duration).To(Equal(time.Minute))
Expect(nodeMetrics[1].Usage).To(Equal(buildResList(1200.0, 2200.0)))
Expect(nodeMetrics[1].Timestamp.Time).To(Equal(pmodel.Time(12).Time()))
Expect(nodeMetrics[1].Window.Duration).To(Equal(time.Minute))
})
It("should return metrics of value zero when node metrics have NaN or negative values", func() {
fakeProm.QueryResults = map[prom.Selector]prom.QueryResult{
mustBuild(cpuQueries.nodeQuery.Build("", nodeResource, "", nil, labels.Everything(), "node1", "node2")): buildQueryRes("container_cpu_usage_seconds_total",
buildNodeSample("node1", -1100.0, 10),
buildNodeSample("node2", 1200.0, 14),
),
mustBuild(memQueries.nodeQuery.Build("", nodeResource, "", nil, labels.Everything(), "node1", "node2")): buildQueryRes("container_memory_working_set_bytes",
buildNodeSample("node1", 2100.0, 11),
buildNodeSample("node2", math.NaN(), 12),
),
}
By("querying for metrics for some nodes")
nodeMetrics, err := prov.GetNodeMetrics(
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1"}},
&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node2"}},
)
Expect(err).NotTo(HaveOccurred())
By("verifying that metrics have been fetched for all the nodes")
Expect(nodeMetrics).To(HaveLen(2))
By("verifying that the reported times for each are the earliest times for each pod")
Expect(nodeMetrics[0].Timestamp.Time).To(Equal(pmodel.Time(10).Time()))
Expect(nodeMetrics[0].Window.Duration).To(Equal(time.Minute))
Expect(nodeMetrics[1].Timestamp.Time).To(Equal(pmodel.Time(12).Time()))
Expect(nodeMetrics[1].Window.Duration).To(Equal(time.Minute))
By("verifying that NaN and negative values were replaced by zero")
Expect(nodeMetrics[0].Usage).To(Equal(buildResList(0, 2100.0)))
Expect(nodeMetrics[1].Usage).To(Equal(buildResList(1200.0, 0)))
})
})

41
test/README.md Normal file
View file

@ -0,0 +1,41 @@
# End-to-end tests
## With [kind](https://kind.sigs.k8s.io/)
[`kind`](https://kind.sigs.k8s.io/) and `kubectl` are automatically downloaded
except if `SKIP_INSTALL=true` is set.
A `kind` cluster is automatically created before the tests, and deleted after
the tests.
The `prometheus-adapter` container image is build locally and imported
into the cluster.
```bash
KIND_E2E=true make test-e2e
```
## With an existing Kubernetes cluster
If you already have a Kubernetes cluster, you can use:
```bash
KUBECONFIG="/path/to/kube/config" REGISTRY="my.registry/prefix" make test-e2e
```
- The cluster should not have a namespace `prometheus-adapter-e2e`.
The namespace will be created and deleted as part of the E2E tests.
- `KUBECONFIG` is the path of the [`kubeconfig` file].
**Optional**, defaults to `${HOME}/.kube/config`
- `REGISTRY` is the image registry where the container image should be pushed.
**Required**.
[`kubeconfig` file]: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
## Additional environment variables
These environment variables may also be used (with any non-empty value):
- `SKIP_INSTALL`: skip the installation of `kind` and `kubectl` binaries;
- `SKIP_CLEAN_AFTER`: skip the deletion of resources (`Kind` cluster or
Kubernetes namespace) and of the temporary directory `.e2e`;
- `CLEAN_BEFORE`: clean before running the tests, e.g. if `SKIP_CLEAN_AFTER`
was used on the previous run.

213
test/e2e/e2e_test.go Normal file
View file

@ -0,0 +1,213 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e
import (
"context"
"fmt"
"log"
"os"
"testing"
"time"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
monitoring "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
metrics "k8s.io/metrics/pkg/client/clientset/versioned"
)
const (
ns = "prometheus-adapter-e2e"
prometheusInstance = "prometheus"
deployment = "prometheus-adapter"
)
var (
client clientset.Interface
promOpClient monitoring.Interface
metricsClient metrics.Interface
)
func TestMain(m *testing.M) {
kubeconfig := os.Getenv("KUBECONFIG")
if len(kubeconfig) == 0 {
log.Fatal("KUBECONFIG not provided")
}
var err error
client, promOpClient, metricsClient, err = initializeClients(kubeconfig)
if err != nil {
log.Fatalf("Cannot create clients: %v", err)
}
ctx := context.Background()
err = waitForPrometheusReady(ctx, ns, prometheusInstance)
if err != nil {
log.Fatalf("Prometheus instance 'prometheus' not ready: %v", err)
}
err = waitForDeploymentReady(ctx, ns, deployment)
if err != nil {
log.Fatalf("Deployment prometheus-adapter not ready: %v", err)
}
exitVal := m.Run()
os.Exit(exitVal)
}
func initializeClients(kubeconfig string) (clientset.Interface, monitoring.Interface, metrics.Interface, error) {
cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error during client configuration with %v", err)
}
clientSet, err := clientset.NewForConfig(cfg)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error during client creation with %v", err)
}
promOpClient, err := monitoring.NewForConfig(cfg)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error during dynamic client creation with %v", err)
}
metricsClientSet, err := metrics.NewForConfig(cfg)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error during metrics client creation with %v", err)
}
return clientSet, promOpClient, metricsClientSet, nil
}
func waitForPrometheusReady(ctx context.Context, namespace string, name string) error {
return wait.PollUntilContextTimeout(ctx, 5*time.Second, 120*time.Second, true, func(ctx context.Context) (bool, error) {
prom, err := promOpClient.MonitoringV1().Prometheuses(ns).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false, err
}
var reconciled, available *monitoringv1.Condition
for _, condition := range prom.Status.Conditions {
cond := condition
if cond.Type == monitoringv1.Reconciled {
reconciled = &cond
} else if cond.Type == monitoringv1.Available {
available = &cond
}
}
if reconciled == nil {
log.Printf("Prometheus instance '%s': Waiting for reconciliation status...", name)
return false, nil
}
if reconciled.Status != monitoringv1.ConditionTrue {
log.Printf("Prometheus instance '%s': Reconciiled = %v. Waiting for reconciliation (reason %s, %q)...", name, reconciled.Status, reconciled.Reason, reconciled.Message)
return false, nil
}
specReplicas := *prom.Spec.Replicas
availableReplicas := prom.Status.AvailableReplicas
if specReplicas != availableReplicas {
log.Printf("Prometheus instance '%s': %v/%v pods are ready. Waiting for all pods to be ready...", name, availableReplicas, specReplicas)
return false, err
}
if available == nil {
log.Printf("Prometheus instance '%s': Waiting for Available status...", name)
return false, nil
}
if available.Status != monitoringv1.ConditionTrue {
log.Printf("Prometheus instance '%s': Available = %v. Waiting for Available status... (reason %s, %q)", name, available.Status, available.Reason, available.Message)
return false, nil
}
log.Printf("Prometheus instance '%s': Ready.", name)
return true, nil
})
}
func waitForDeploymentReady(ctx context.Context, namespace string, name string) error {
return wait.PollUntilContextTimeout(ctx, 5*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
sts, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false, err
}
if sts.Status.ReadyReplicas == *sts.Spec.Replicas {
log.Printf("Deployment %s: %v/%v pods are ready.", name, sts.Status.ReadyReplicas, *sts.Spec.Replicas)
return true, nil
}
log.Printf("Deployment %s: %v/%v pods are ready. Waiting for all pods to be ready...", name, sts.Status.ReadyReplicas, *sts.Spec.Replicas)
return false, nil
})
}
func TestNodeMetrics(t *testing.T) {
ctx := context.Background()
var nodeMetrics *metricsv1beta1.NodeMetricsList
err := wait.PollUntilContextTimeout(ctx, 2*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
var err error
nodeMetrics, err = metricsClient.MetricsV1beta1().NodeMetricses().List(ctx, metav1.ListOptions{})
if err != nil {
return false, err
}
nonEmptyNodeMetrics := len(nodeMetrics.Items) > 0
if !nonEmptyNodeMetrics {
t.Logf("Node metrics empty... Retrying.")
}
return nonEmptyNodeMetrics, nil
})
require.NoErrorf(t, err, "Node metrics should not be empty")
for _, nodeMetric := range nodeMetrics.Items {
positiveMemory := nodeMetric.Usage.Memory().CmpInt64(0)
assert.Positivef(t, positiveMemory, "Memory usage for node %s is %v, should be > 0", nodeMetric.Name, nodeMetric.Usage.Memory())
positiveCPU := nodeMetric.Usage.Cpu().CmpInt64(0)
assert.Positivef(t, positiveCPU, "CPU usage for node %s is %v, should be > 0", nodeMetric.Name, nodeMetric.Usage.Cpu())
}
}
func TestPodMetrics(t *testing.T) {
ctx := context.Background()
var podMetrics *metricsv1beta1.PodMetricsList
err := wait.PollUntilContextTimeout(ctx, 2*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
var err error
podMetrics, err = metricsClient.MetricsV1beta1().PodMetricses(ns).List(ctx, metav1.ListOptions{})
if err != nil {
return false, err
}
nonEmptyNodeMetrics := len(podMetrics.Items) > 0
if !nonEmptyNodeMetrics {
t.Logf("Pod metrics empty... Retrying.")
}
return nonEmptyNodeMetrics, nil
})
require.NoErrorf(t, err, "Pod metrics should not be empty")
for _, pod := range podMetrics.Items {
for _, containerMetric := range pod.Containers {
positiveMemory := containerMetric.Usage.Memory().CmpInt64(0)
assert.Positivef(t, positiveMemory, "Memory usage for pod %s/%s is %v, should be > 0", pod.Name, containerMetric.Name, containerMetric.Usage.Memory())
}
}
}

View file

@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: prometheus-adapter-e2e

View file

@ -0,0 +1,24 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/metrics
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources:
- configmaps
verbs: ["get"]
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]

View file

@ -0,0 +1,9 @@
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus
namespace: prometheus-adapter-e2e
spec:
replicas: 2
serviceAccountName: prometheus
serviceMonitorSelector: {}

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: prometheus-adapter-e2e

View file

@ -0,0 +1,25 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
app.kubernetes.io/name: kubelet
name: kubelet
namespace: prometheus-adapter-e2e
spec:
endpoints:
- bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
honorLabels: true
honorTimestamps: false
interval: 10s
path: /metrics/resource
port: https-metrics
scheme: https
tlsConfig:
insecureSkipVerify: true
jobLabel: app.kubernetes.io/name
namespaceSelector:
matchNames:
- kube-system
selector:
matchLabels:
app.kubernetes.io/name: kubelet

View file

@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: prometheus
namespace: prometheus-adapter-e2e
spec:
ports:
- name: web
port: 9090
targetPort: web
selector:
app.kubernetes.io/instance: prometheus
app.kubernetes.io/name: prometheus
sessionAffinity: ClientIP

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