├── .dockerignore
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ ├── enhancement.md
│ └── support.md
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yaml
└── workflows
│ ├── gh-workflow-approve.yaml
│ ├── lint-test-chart.yaml
│ ├── release-chart.yaml
│ └── release.yaml
├── .gitignore
├── .golangci.yml
├── CONTRIBUTING.md
├── Dockerfile
├── FAQ.md
├── KNOWN_ISSUES.md
├── LICENSE
├── Makefile
├── OWNERS
├── README.md
├── RELEASE.md
├── SECURITY.md
├── SECURITY_CONTACTS
├── charts
├── OWNERS
└── metrics-server
│ ├── .helmignore
│ ├── CHANGELOG.md
│ ├── Chart.yaml
│ ├── README.md
│ ├── ci
│ ├── ci-values.yaml
│ ├── tls-certManager-values.yaml
│ ├── tls-existingSecret-values.yaml
│ └── tls-helm-values.yaml
│ ├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── apiservice.yaml
│ ├── certificate.yaml
│ ├── clusterrole-aggregated-reader.yaml
│ ├── clusterrole-nanny.yaml
│ ├── clusterrole.yaml
│ ├── clusterrolebinding-auth-delegator.yaml
│ ├── clusterrolebinding-nanny.yaml
│ ├── clusterrolebinding.yaml
│ ├── configmaps-nanny.yaml
│ ├── deployment.yaml
│ ├── pdb.yaml
│ ├── psp.yaml
│ ├── role-nanny.yaml
│ ├── rolebinding-nanny.yaml
│ ├── rolebinding.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── servicemonitor.yaml
│ └── values.yaml
├── cloudbuild.yaml
├── cmd
└── metrics-server
│ ├── app
│ ├── options
│ │ ├── kubelet_client.go
│ │ ├── kubelet_client_test.go
│ │ ├── options.go
│ │ └── options_test.go
│ └── start.go
│ └── metrics-server.go
├── code-of-conduct.md
├── docs
└── command-line-flags.txt
├── go.mod
├── go.sum
├── manifests
├── base
│ ├── apiservice.yaml
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ ├── rbac.yaml
│ └── service.yaml
├── components
│ ├── autoscale
│ │ ├── kustomization.yaml
│ │ ├── patch.yaml
│ │ └── rbac.yaml
│ ├── high-availability-1.21+
│ │ ├── kustomization.yaml
│ │ └── patch-pdb-version.yaml
│ ├── high-availability
│ │ ├── kustomization.yaml
│ │ ├── patch.yaml
│ │ └── pdb.yaml
│ ├── release
│ │ └── kustomization.yaml
│ └── test
│ │ └── kustomization.yaml
└── overlays
│ ├── release-ha-1.21+
│ └── kustomization.yaml
│ ├── release-ha
│ └── kustomization.yaml
│ ├── release
│ └── kustomization.yaml
│ ├── test-ha
│ └── kustomization.yaml
│ └── test
│ └── kustomization.yaml
├── pkg
├── api
│ ├── filter.go
│ ├── generated
│ │ └── openapi
│ │ │ ├── doc.go
│ │ │ └── zz_generated.openapi.go
│ ├── install.go
│ ├── interfaces.go
│ ├── monitoring.go
│ ├── node.go
│ ├── node_test.go
│ ├── pod.go
│ ├── pod_test.go
│ ├── table.go
│ ├── table_test.go
│ └── time.go
├── scraper
│ ├── client
│ │ ├── configs.go
│ │ ├── interface.go
│ │ └── resource
│ │ │ ├── client.go
│ │ │ ├── client_test.go
│ │ │ ├── decode.go
│ │ │ └── decode_test.go
│ ├── interface.go
│ ├── scraper.go
│ └── scraper_test.go
├── server
│ ├── config.go
│ ├── health.go
│ ├── informer.go
│ ├── metrics.go
│ ├── server.go
│ └── server_test.go
├── storage
│ ├── interface.go
│ ├── main_test.go
│ ├── monitoring.go
│ ├── node.go
│ ├── node_test.go
│ ├── pod.go
│ ├── pod_test.go
│ ├── storage.go
│ ├── storage_benchmark_test.go
│ ├── storage_test.go
│ ├── types.go
│ └── types_test.go
└── utils
│ ├── address_resolver.go
│ ├── monitoring.go
│ └── monitoring_test.go
├── scripts
├── boilerplate.go.txt
├── go.mod
├── go.sum
└── tools.go
├── skaffold.yaml
└── test
├── chart-values.yaml
├── e2e_test.go
├── kind-config-with-sidecar-containers.yaml
├── kind-config.yaml
├── test-e2e.sh
└── test-image.sh
/.dockerignore:
--------------------------------------------------------------------------------
1 | /_output
2 | /metrics-server
3 | /.git
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Report a bug encountered while operating Metrics Server
4 |
5 | ---
6 |
7 |
19 |
20 | **What happened**:
21 |
22 | **What you expected to happen**:
23 |
24 | **Anything else we need to know?**:
25 |
26 | **Environment**:
27 |
28 | - Kubernetes distribution (GKE, EKS, Kubeadm, the hard way, etc.):
29 | - Container Network Setup (flannel, calico, etc.):
30 | - Kubernetes version (use `kubectl version`):
31 |
32 | - Metrics Server manifest
33 |
34 |
35 | spoiler for Metrics Server manifest:
36 |
37 |
38 |
39 |
40 |
41 | - Kubelet config:
42 |
43 |
44 | spoiler for Kubelet config:
45 |
46 |
47 |
48 |
49 |
50 | - Metrics server logs:
51 |
52 |
53 | spoiler for Metrics Server logs:
54 |
55 |
56 |
57 |
58 |
59 | - Status of Metrics API:
60 |
61 |
62 | spolier for Status of Metrics API:
63 |
64 | ```sh
65 | kubectl describe apiservice v1beta1.metrics.k8s.io
66 | ```
67 |
68 |
69 |
70 |
71 |
72 |
73 | /kind bug
74 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/enhancement.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Enhancement Request
3 | about: Suggest an enhancement to the Metrics Server
4 |
5 | ---
6 |
7 |
8 | **What would you like to be added**:
9 |
10 | **Why is this needed**:
11 |
12 |
13 | /kind feature
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/support.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Support Request
3 | about: Support request or question relating to Metrics Server
4 |
5 | ---
6 |
7 |
17 |
18 |
19 |
20 | /kind support
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | **What this PR does / why we need it**:
9 |
10 | **Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*:
11 | Fixes #
12 |
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: weekly
7 | - package-ecosystem: docker
8 | directory: /
9 | schedule:
10 | interval: weekly
11 | - package-ecosystem: gomod
12 | directory: /
13 | schedule:
14 | interval: weekly
15 | groups:
16 | gomod-dependencies:
17 | patterns:
18 | - "*"
19 | - package-ecosystem: gomod
20 | directory: /scripts
21 | schedule:
22 | interval: weekly
23 | groups:
24 | gomod-dependencies:
25 | patterns:
26 | - "*"
27 |
--------------------------------------------------------------------------------
/.github/workflows/gh-workflow-approve.yaml:
--------------------------------------------------------------------------------
1 | name: Approve GH Workflows
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - labeled
7 | - synchronize
8 | branches:
9 | - master
10 |
11 | permissions:
12 | contents: read
13 |
14 | jobs:
15 | approve:
16 | name: Approve ok-to-test
17 | if: contains(github.event.pull_request.labels.*.name, 'ok-to-test')
18 | runs-on: ubuntu-latest
19 | permissions:
20 | actions: write
21 | steps:
22 | - name: Update PR
23 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
24 | continue-on-error: true
25 | with:
26 | github-token: ${{ secrets.GITHUB_TOKEN }}
27 | debug: ${{ secrets.ACTIONS_RUNNER_DEBUG == 'true' }}
28 | script: |
29 | const result = await github.rest.actions.listWorkflowRunsForRepo({
30 | owner: context.repo.owner,
31 | repo: context.repo.repo,
32 | event: "pull_request",
33 | status: "action_required",
34 | head_sha: context.payload.pull_request.head.sha,
35 | per_page: 100
36 | });
37 |
38 | for (var run of result.data.workflow_runs) {
39 | await github.rest.actions.approveWorkflowRun({
40 | owner: context.repo.owner,
41 | repo: context.repo.repo,
42 | run_id: run.id
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/lint-test-chart.yaml:
--------------------------------------------------------------------------------
1 | name: Lint and Test Chart
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - .github/workflows/lint-test-chart.yaml
7 | - "charts/metrics-server/**"
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | lint-test:
14 | name: Lint & Test
15 | if: github.repository == 'kubernetes-sigs/metrics-server'
16 | runs-on: ubuntu-latest
17 | defaults:
18 | run:
19 | shell: bash
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
23 | with:
24 | fetch-depth: 0
25 |
26 | - name: Set-up Python
27 | uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
28 | with:
29 | python-version: "3.x"
30 |
31 | - name: Set-up Helm
32 | uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0
33 | with:
34 | token: ${{ secrets.GITHUB_TOKEN }}
35 | version: latest
36 |
37 | - name: Set-up chart-testing
38 | uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1
39 |
40 | - name: Check for changes
41 | id: changes
42 | run: |
43 | changed="$(ct list-changed)"
44 | if [[ -n "${changed}" ]]
45 | then
46 | echo "changed=true" >> "${GITHUB_OUTPUT}"
47 | else
48 | echo "changed=false" >> "${GITHUB_OUTPUT}"
49 | fi
50 |
51 | - name: Get chart version
52 | id: chart_version
53 | if: steps.changes.outputs.changed == 'true'
54 | uses: mikefarah/yq@bbdd97482f2d439126582a59689eb1c855944955 # v4.44.3
55 | with:
56 | cmd: yq eval '.version' './charts/metrics-server/Chart.yaml'
57 |
58 | - name: Get changelog entry
59 | if: steps.changes.outputs.changed == 'true'
60 | uses: mindsers/changelog-reader-action@32aa5b4c155d76c94e4ec883a223c947b2f02656 # v2.2.3
61 | with:
62 | path: charts/metrics-server/CHANGELOG.md
63 | version: ${{ steps.chart_version.outputs.result }}
64 |
65 | - name: Set-up Artifact Hub CLI
66 | if: steps.changes.outputs.changed == 'true'
67 | uses: action-stars/install-tool-from-github-release@ece2623611b240002e0dd73a0d685505733122f6 # v0.2.4
68 | with:
69 | github_token: ${{ github.token }}
70 | owner: artifacthub
71 | repository: hub
72 | name: ah
73 | check_command: ah version
74 | version: latest
75 |
76 | - name: Run Artifact Hub lint
77 | if: steps.changes.outputs.changed == 'true'
78 | run: ah lint --kind helm || exit 1
79 |
80 | - name: Run chart-testing lint
81 | if: steps.changes.outputs.changed == 'true'
82 | run: ct lint --check-version-increment=false
83 |
84 | - name: Create Kind cluster
85 | if: steps.changes.outputs.changed == 'true'
86 | uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
87 | with:
88 | wait: 120s
89 |
90 | - name: Install cert-manager dependency
91 | if: steps.changes.outputs.changed == 'true'
92 | run: |
93 | helm repo add jetstack https://charts.jetstack.io
94 | helm install cert-manager jetstack/cert-manager \
95 | --namespace cert-manager \
96 | --create-namespace \
97 | --wait \
98 | --set installCRDs=true \
99 | --set extraArgs='{--enable-certificate-owner-ref}'
100 |
101 | - name: Prepare existing secret test scenario
102 | if: steps.changes.outputs.changed == 'true'
103 | run: |
104 | openssl req -x509 -newkey rsa:2048 -sha256 -days 365 \
105 | -nodes -keyout ${{ runner.temp }}/tls.key -out ${{ runner.temp }}/tls.crt \
106 | -subj "/CN=metrics-server" \
107 | -addext "subjectAltName=DNS:metrics-server,DNS:metrics-server.kube-system.svc"
108 |
109 | kubectl -n kube-system create secret generic metrics-server-existing \
110 | --from-file=${{ runner.temp }}/tls.key \
111 | --from-file=${{ runner.temp }}/tls.crt
112 |
113 | cat <> charts/metrics-server/ci/tls-existingSecret-values.yaml
114 | apiService:
115 | insecureSkipTLSVerify: false
116 | caBundle: |
117 | $(cat ${{ runner.temp }}/tls.crt | sed -e "s/^/ /g")
118 | EOF
119 |
120 | rm ${{ runner.temp }}/tls.key ${{ runner.temp }}/tls.crt
121 |
122 | - name: Run chart-testing install
123 | if: steps.changes.outputs.changed == 'true'
124 | run: ct install --namespace kube-system
125 |
--------------------------------------------------------------------------------
/.github/workflows/release-chart.yaml:
--------------------------------------------------------------------------------
1 | name: Release Chart
2 |
3 | on:
4 | push:
5 | branches:
6 | - release-*
7 | paths:
8 | - charts/metrics-server/Chart.yaml
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | release:
15 | name: Release
16 | if: github.repository == 'kubernetes-sigs/metrics-server'
17 | runs-on: ubuntu-latest
18 | defaults:
19 | run:
20 | shell: bash
21 | permissions:
22 | contents: write
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
26 | with:
27 | fetch-depth: 0
28 |
29 | - name: Configure Git
30 | run: |
31 | git config user.name "$GITHUB_ACTOR"
32 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
33 |
34 | - name: Get chart version
35 | id: chart_version
36 | uses: mikefarah/yq@bbdd97482f2d439126582a59689eb1c855944955 # v4.44.3
37 | with:
38 | cmd: yq eval '.version' './charts/metrics-server/Chart.yaml'
39 |
40 | - name: Get chart app version
41 | id: chart_app_version
42 | uses: mikefarah/yq@bbdd97482f2d439126582a59689eb1c855944955 # v4.44.3
43 | with:
44 | cmd: yq eval '.appVersion' './charts/metrics-server/Chart.yaml'
45 |
46 | - name: Check can release
47 | id: check_can_release
48 | run: |
49 | set -euo pipefail
50 |
51 | branch_name="${GITHUB_REF##*/}"
52 | app_version_prefix="${branch_name##*-}"
53 |
54 | app_version_regex="^${app_version_prefix//./\.}"
55 |
56 | chart_version_match="$(echo "${{ steps.chart_version.outputs.result }}" | grep -Po "^\d+\.\d+\.\d+$" || true)"
57 | app_version_match="$(echo "${{ steps.chart_app_version.outputs.result }}" | grep -Po "^${app_version_prefix//./\.}" || true)"
58 |
59 | if [[ -z "${chart_version_match}" ]] || [[ -z "${app_version_match}" ]]
60 | then
61 | echo "continue=false" >> $GITHUB_OUTPUT
62 | else
63 | echo "continue=true" >> $GITHUB_OUTPUT
64 | fi
65 |
66 | - name: Set-up Helm
67 | if: steps.check_can_release.outputs.continue == 'true'
68 | uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0
69 | with:
70 | token: ${{ secrets.GITHUB_TOKEN }}
71 | version: latest
72 |
73 | - name: Get CHANGELOG entry
74 | id: changelog_reader
75 | if: steps.check_can_release.outputs.continue == 'true'
76 | uses: mindsers/changelog-reader-action@32aa5b4c155d76c94e4ec883a223c947b2f02656 # v2.2.3
77 | with:
78 | path: charts/metrics-server/CHANGELOG.md
79 | version: ${{ steps.chart_version.outputs.result }}
80 |
81 | - name: Create release notes
82 | if: steps.check_can_release.outputs.continue == 'true'
83 | run: |
84 | set -euo pipefail
85 | cat <<"EOF" > charts/metrics-server/RELEASE.md
86 | ${{ steps.changelog_reader.outputs.changes }}
87 | EOF
88 |
89 | - name: Run chart-releaser
90 | if: steps.check_can_release.outputs.continue == 'true'
91 | uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0
92 | env:
93 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
94 | CR_RELEASE_NAME_TEMPLATE: "metrics-server-helm-chart-{{ .Version }}"
95 | CR_RELEASE_NOTES_FILE: RELEASE.md
96 | CR_MAKE_RELEASE_LATEST: false
97 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | release:
5 | types:
6 | - published
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | build:
13 | name: build
14 | runs-on: ubuntu-latest
15 | defaults:
16 | run:
17 | shell: bash
18 | permissions:
19 | contents: write
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
23 | - name: Build manifests
24 | run: make release-manifests
25 | - name: Release manifests
26 | uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v0.1.15
27 | with:
28 | files: |
29 | _output/components.yaml
30 | _output/high-availability.yaml
31 | _output/high-availability-1.21+.yaml
32 | - name: Build binaries
33 | run: make build-all
34 | - name: Release binaries
35 | uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v0.1.15
36 | with:
37 | files: |
38 | _output/metrics-server-*
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /_output
2 | /metrics-server
3 | .cover
4 |
5 | # Vim-related files
6 | [._]*.s[a-w][a-z]
7 | [._]s[a-w][a-z]
8 | *~
9 | *.un~
10 | Session.vim
11 | .netrwhist
12 | .idea
13 | *manifest-tool
14 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | run:
2 | timeout: 10m
3 |
4 | linters:
5 | disable-all: true
6 | enable:
7 | - bodyclose
8 | - dogsled
9 | - dupl
10 | - errcheck
11 | - copyloopvar
12 | - exhaustive
13 | - gocritic
14 | - gocyclo
15 | - gofmt
16 | - goimports
17 | - goprintffuncname
18 | - gosimple
19 | - govet
20 | - ineffassign
21 | - misspell
22 | - nakedret
23 | - nolintlint
24 | - staticcheck
25 | - unconvert
26 | - unused
27 | - whitespace
28 |
29 | linters-settings:
30 | goimports:
31 | local-prefixes: sigs.k8s.io/metrics-server
32 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | 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:
4 |
5 | _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._
6 |
7 | ## Getting Started
8 |
9 | We have full documentation on how to get started contributing here:
10 |
11 |
14 |
15 | - [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
16 | - [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)
17 | - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
18 |
19 | ## Mentorship
20 |
21 | - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
22 |
23 | ## Chart Changes
24 |
25 | When contributing chart changes please follow the same process as when contributing other content but also please **DON'T** modify _Chart.yaml_ in the PR as this would result in a chart release when merged and will mean that your PR will need modifying before it can be accepted. The chart version will be updated as part of the PR to release the chart.
26 |
27 | ## Development
28 |
29 | Required tools:
30 |
31 | - [Docker](https://www.docker.com/)
32 | - [Kind](https://kind.sigs.k8s.io/)
33 | - [Skaffold](https://skaffold.dev/)
34 |
35 | ## Adding dependencies
36 |
37 | The project follows a standard Go project layout, see more about [dependency-management](https://github.com/kubernetes/community/blob/master/contributors/devel/development.md#dependency-management).
38 |
39 | ## Running static code validation
40 |
41 | ```sh
42 | make lint
43 | ```
44 |
45 | ## Running tests
46 |
47 | ```sh
48 | make test-unit
49 | make test-version
50 | make test-e2e
51 | ```
52 |
53 | ## Live reload
54 |
55 | To start local development just run:
56 |
57 | ```sh
58 | kind create cluster
59 | skaffold dev
60 | ```
61 |
62 | To execute e2e tests run:
63 |
64 | ```sh
65 | go test test/e2e_test.go -v -count=1
66 | ```
67 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Update the base image in Makefile when updating golang version. This has to
2 | # be pre-pulled in order to work on GCB.
3 | ARG ARCH
4 | FROM golang:1.24.2 as build
5 |
6 | WORKDIR /go/src/sigs.k8s.io/metrics-server
7 | COPY go.mod .
8 | COPY go.sum .
9 | RUN go mod download
10 |
11 | COPY pkg pkg
12 | COPY cmd cmd
13 | COPY Makefile Makefile
14 |
15 | ARG ARCH
16 | ARG GIT_COMMIT
17 | ARG GIT_TAG
18 | RUN make metrics-server
19 |
20 | FROM gcr.io/distroless/static:latest-$ARCH
21 | COPY --from=build /go/src/sigs.k8s.io/metrics-server/metrics-server /
22 | USER 65534
23 | ENTRYPOINT ["/metrics-server"]
24 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | ## Table of Contents
4 |
5 |
6 | - [What metrics are exposed by the metrics server?](#what-metrics-are-exposed-by-the-metrics-server)
7 | - [How CPU usage is calculated?](#how-cpu-usage-is-calculated)
8 | - [How memory usage is calculated?](#how-memory-usage-is-calculated)
9 | - [How does the metrics server calculate metrics?](#how-does-the-metrics-server-calculate-metrics)
10 | - [How often is metrics server released?](#how-often-is-metrics-server-released)
11 | - [Can I run more than one instance of metrics-server?](#can-i-run-more-than-one-instance-of-metrics-server)
12 | - [How to run metrics-server securely?](#how-to-run-metrics-server-securely)
13 | - [How to run metric-server on different architecture?](#how-to-run-metric-server-on-different-architecture)
14 | - [What Kubernetes versions are supported?](#what-kubernetes-versions-are-supported)
15 | - [How is resource utilization calculated?](#how-is-resource-utilization-calculated)
16 | - [How to autoscale Metrics Server?](#how-to-autoscale-metrics-server)
17 | - [Can I get other metrics beside CPU/Memory using Metrics Server?](#can-i-get-other-metrics-beside-cpumemory-using-metrics-server)
18 | - [How large can clusters be?](#how-large-can-clusters-be)
19 | - [How often metrics are scraped?](#how-often-metrics-are-scraped)
20 |
21 |
22 | ### What metrics are exposed by the metrics server?
23 |
24 | Metrics server collects resource usage metrics needed for autoscaling: CPU & Memory.
25 | Metrics values use [Metric System prefixes] (`n` = 10-9 and `Ki` = 210),
26 | the same as those used to define pod requests and limits.
27 | Metrics server itself is not responsible for calculating metric values, this is done by Kubelet.
28 |
29 | [Metric System prefixes]: https://en.wikipedia.org/wiki/Metric_prefix
30 |
31 | ### How CPU usage is calculated?
32 |
33 | CPU is reported as the average core usage measured in cpu units.
34 | One cpu, in Kubernetes, is equivalent to 1 vCPU/Core for cloud providers and 1 hyperthread on bare-metal Intel processors.
35 |
36 | This value is derived by taking a rate over a cumulative CPU counter provided by the kernel (in both Linux and Windows kernels).
37 | Time window used to calculate CPU is exposed under `window` field in Metrics API.
38 |
39 | Read more about [Meaning of CPU].
40 |
41 | [Meaning of CPU]: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
42 |
43 | ### How memory usage is calculated?
44 |
45 | Memory is reported as the working set at the instant the metric was collected, measured in bytes.
46 |
47 | In an ideal world, the "working set" is the amount of memory in-use that cannot be freed under memory pressure.
48 | However, calculation of the working set varies by host OS, and generally makes heavy use of heuristics to produce an estimate.
49 | It includes all anonymous (non-file-backed) memory since Kubernetes does not support swap.
50 | The metric typically also includes some cached (file-backed) memory, because the host OS cannot always reclaim such pages.
51 |
52 | Read more about [Meaning of memory].
53 |
54 | [Meaning of memory]: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
55 |
56 | ### How does the metrics server calculate metrics?
57 |
58 | Metrics Server itself doesn't calculate any metrics, it aggregates values exposed by Kubelet and exposes them in API
59 | to be used for autoscaling. For any problem with metric values please contact SIG-Node.
60 |
61 | ### How often is metrics server released?
62 |
63 | There is no hard release schedule. A release is done after an important feature is implemented or upon request.
64 |
65 | ### Can I run more than one instance of metrics-server?
66 |
67 | Yes, more than one instance can be deployed for High Availability. Each instance of the metrics server will scrape all nodes to collect metrics, but only one instance will be actively serving metrics API. it is **recommended** to add the `--enable-aggregator-routing=true` flag to the `kube-apiserver` so that requests sent to the Metrics Server are load balanced. [More Info.](./README.md?#high-availability)
68 |
69 | ### How to run metrics-server securely?
70 |
71 | Suggested configuration:
72 |
73 | - Cluster with [RBAC] enabled
74 | - Kubelet [read-only port] port disabled
75 | - Validate kubelet certificate by mounting CA file and providing `--kubelet-certificate-authority` flag to metrics server
76 | - Avoid passing insecure flags to metrics server (`--deprecated-kubelet-completely-insecure`, `--kubelet-insecure-tls`)
77 | - Consider using your own certificates (`--tls-cert-file`, `--tls-private-key-file`)
78 |
79 | ### How to run metric-server on different architecture?
80 |
81 | Starting from `v0.3.7` docker image `registry.k8s.io/metrics-server/metrics-server` should support multiple architectures via Manifests List.
82 | List of supported architectures: `amd64`, `arm`, `arm64`, `ppc64le`, `s390x`.
83 |
84 | ### What Kubernetes versions are supported?
85 |
86 | Metrics server is tested against the last 3 Kubernetes versions.
87 |
88 | ### How is resource utilization calculated?
89 |
90 | Metrics server doesn't provide resource utilization metrics (e.g. percent of CPU used).
91 | Utilization presented by `kubectl top` and HPA is calculated client side based on pod resource requests or node capacity.
92 |
93 | ### How to autoscale Metrics Server?
94 |
95 | Metrics server scales linearly vertically according to the number of nodes and pods in a cluster. This can be automated using [addon-resizer].
96 |
97 | ### Can I get other metrics beside CPU/Memory using Metrics Server?
98 |
99 | No, metrics server was designed to provide metrics for [resource metrics pipeline] used for autoscaling.
100 |
101 | ### How large can clusters be?
102 |
103 | Metrics Server was tested to run within clusters up to 5000 nodes with an average pod density of 30 pods per node.
104 |
105 | ### How often metrics are scraped?
106 |
107 | Default 60 seconds, can be changed using `metric-resolution` flag. We are not recommending setting values below 15s, as this is the resolution of metrics calculated by Kubelet.
108 |
109 | [RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
110 | [read-only port]: https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/#options
111 | [addon-resizer]: https://github.com/kubernetes/autoscaler/tree/master/addon-resizer
112 | [resource metrics pipeline]: https://kubernetes.io/docs/tasks/debug-application-cluster/resource-metrics-pipeline/
113 |
--------------------------------------------------------------------------------
/OWNERS:
--------------------------------------------------------------------------------
1 | # See the OWNERS docs: https://go.k8s.io/owners
2 | approvers:
3 | - s-urbaniak
4 | - serathius
5 | - dgrisonnet
6 | - logicalhan
7 | reviewers:
8 | - dgrisonnet
9 | - s-urbaniak
10 | - serathius
11 | - yangjunmyfm192085
12 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release Process
2 |
3 | The Metrics Server is released on an as-needed basis. The process is as follows:
4 |
5 | 1. An issue is proposing a new release with a changelog since the last release https://github.com/kubernetes-sigs/metrics-server/compare
6 | 1. At least one [OWNER](OWNERS) must LGTM this release
7 | 1. A PR that bumps version hardcoded in code is created and merged
8 | 1. An OWNER creates a draft GitHub release
9 | 1. An OWNER creates an issue to release the corresponding Helm chart via the chart release process (below)
10 | 1. An OWNER creates a release tag using `GIT_TAG=$VERSION make release-tag` and waits for [prow.k8s.io](prow.k8s.io) to build and push new images to [gcr.io/k8s-staging-metrics-server](https://gcr.io/k8s-staging-metrics-server)
11 | 1. A PR in [kubernetes/k8s.io](https://github.com/kubernetes/k8s.io/blob/main/k8s.gcr.io/images/k8s-staging-metrics-server/images.yaml) is created to release images to `k8s.gcr.io`
12 | 1. An OWNER publishes the GitHub release. Once published, release manifests will be automatically added to the release by CI.
13 | 1. An announcement email is sent to `kubernetes-sig-instrumentation@googlegroups.com` with the subject `[ANNOUNCE] metrics-server $VERSION is released`
14 | 1. The release issue is closed
15 |
16 | ## Chart Release Process
17 |
18 | The chart needs to be released in response to a Metrics Server release or on an as-needed basis. The process is as follows:
19 |
20 | 1. An issue is proposing a new chart release
21 | 1. A PR is opened to update _Chart.yaml_ with the `appVersion`, `version` (based on the release changes) and `annotations`
22 | 1. The PR triggers the chart linting and testing GitHub action to validate the chart
23 | 1. The PR is merged and a GitHub action releases the chart
24 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Security Announcements
4 |
5 | Join the [kubernetes-security-announce] group for security and vulnerability announcements.
6 |
7 | You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss].
8 |
9 | ## Reporting a Vulnerability
10 |
11 | Instructions for reporting a vulnerability can be found on the
12 | [Kubernetes Security and Disclosure Information] page.
13 |
14 | ## Supported Versions
15 |
16 | Information about supported Kubernetes versions can be found on the
17 | [Kubernetes version and version skew support policy] page on the Kubernetes website.
18 |
19 | [kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce
20 | [kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50
21 | [Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions
22 | [Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability
23 |
--------------------------------------------------------------------------------
/SECURITY_CONTACTS:
--------------------------------------------------------------------------------
1 | # Defined below are the security contacts for this repo.
2 | #
3 | # They are the contact point for the Product Security Committee to reach out
4 | # to for triaging and handling of incoming issues.
5 | #
6 | # The below names agree to abide by the
7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
8 | # and will be removed and replaced if they violate that agreement.
9 | #
10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
11 | # INSTRUCTIONS AT https://kubernetes.io/security/
12 |
13 | s-urbaniak
14 | serathius
15 |
--------------------------------------------------------------------------------
/charts/OWNERS:
--------------------------------------------------------------------------------
1 | labels:
2 | - chart
3 | approvers:
4 | - stevehipwell
5 | reviewers:
6 | - stevehipwell
7 |
--------------------------------------------------------------------------------
/charts/metrics-server/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/charts/metrics-server/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Metrics Server Helm Chart Changelog
2 |
3 | > [!NOTE]
4 | > All notable changes to this project will be documented in this file; the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5 |
6 |
14 |
15 | ## [UNRELEASED]
16 |
17 | ### Added
18 |
19 | - Add options on howto secure the connection between metrics-server and the kube-apiserver. ([#1288](https://github.com/kubernetes-sigs/metrics-server/pull/1288)) _@mkilchhofer_
20 | - Added `unhealthyPodEvictionPolicy` to the metrics-server PDB as a user enabled feature. ([#1574](https://github.com/kubernetes-sigs/metrics-server/pull/1574)) @peterabarr
21 |
22 | ### Changed
23 |
24 | - Updated the _Addon Resizer_ OCI image to [`1.8.23`](https://github.com/kubernetes/autoscaler/releases/tag/addon-resizer-1.8.23). ([#1626](https://github.com/kubernetes-sigs/metrics-server/pull/1626)) _@stevehipwell_
25 |
26 | ## [3.12.2] - TBC
27 |
28 | ### Added
29 |
30 | - Explicitly added the app protocol to the service. ([#1540](https://github.com/kubernetes-sigs/metrics-server/pull/1540)) _@
31 | seankhliao_
32 |
33 | ### Changed
34 |
35 | - Updated the _Metrics Server_ OCI image to [v0.7.2](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.7.2). ([#1568](https://github.com/kubernetes-sigs/metrics-server/pull/1568)) _@stevehipwell_
36 | - Updated the _addonResizer_ OCI image to [1.8.21](https://github.com/kubernetes/autoscaler/releases/tag/addon-resizer-1.8.21). ([#1504](https://github.com/kubernetes-sigs/metrics-server/pull/1504)) _@jimmy-ungerman_
37 |
38 | ### Fixed
39 |
40 | - Fixed nanny's RoleBinding which contained a hard-coded namespace instead of the Helm's release namespace. ([#1479](https://github.com/kubernetes-sigs/metrics-server/pull/1479)) _@the-technat_
41 | - Fixed the `ServiceMonitor` job label. ([#1568](https://github.com/kubernetes-sigs/metrics-server/pull/1568)) _@stevehipwell_
42 |
43 | ## [3.12.1] - 2024-04-05
44 |
45 | ### Changed
46 |
47 | - Updated the _Metrics Server_ OCI image to [v0.7.1](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.7.1). ([#1461](https://github.com/kubernetes-sigs/metrics-server/pull/1461)) _@stevehipwell_
48 | - Changed `Deployment` templating to ignore `schedulerName` when value is empty. ([#1475](https://github.com/kubernetes-sigs/metrics-server/pull/1475)) _@senges_
49 |
50 | ## [3.12.0] - 2024-02-07
51 |
52 | ### Changed
53 |
54 | - Updated the _Metrics Server_ OCI image to [v0.7.0](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.7.0). ([#1414](https://github.com/kubernetes-sigs/metrics-server/pull/1414)) [@stevehipwell](https://github.com/stevehipwell)
55 | - Updated the _addon-resizer_ OCI image to [v1.8.20](https://github.com/kubernetes/autoscaler/releases/tag/addon-resizer-1.8.20). ([#1414](https://github.com/kubernetes-sigs/metrics-server/pull/1414)) [@stevehipwell](https://github.com/stevehipwell)
56 |
57 | ## [3.11.0] - 2023-08-03
58 |
59 | ### Added
60 |
61 | - Added default _Metrics Server_ resource requests.
62 |
63 | ### Changed
64 |
65 | - Updated the _Metrics Server_ OCI image to [v0.6.4](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.6.4).
66 | - Updated the _addon-resizer_ OCI image to [v1.8.19](https://github.com/kubernetes/autoscaler/releases/tag/addon-resizer-1.8.19).
67 |
68 | ## [3.10.0] - 2023-04-12
69 |
70 | ### Added
71 |
72 | - Added support for running under PodSecurity restricted.
73 |
74 | ### Fixed
75 |
76 | - Fixed `auth-reader` role binding namespace to always use `kube-system`.
77 | - Fixed addon-resizer configuration.
78 | - Fixed container port default not having been updated to `10250`.
79 |
80 | ## [3.9.0] - 2023-03-28
81 |
82 | ### Added
83 |
84 | - Added autoscaling support via the addon-resizer.
85 |
86 | ### Changed
87 |
88 | - Updated the _Metrics Server_ OCI image to [v0.6.3](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.6.3).
89 |
90 | ### Fixed
91 |
92 | - Fixed service labels/annotations.
93 |
94 | ## [3.8.4] - 2023-03-06
95 |
96 | ### Changed
97 |
98 | - Changed the image registry location to `registry.k8s.io`.
99 |
100 | ## [3.8.3] - 2022-12-08
101 |
102 | ### Added
103 |
104 | - Added support for topologySpreadConstraints.
105 | - Always set resource namespaces explicitly.
106 | - Allow configuring TLS on the APIService.
107 | - Enabled service monitor relabelling.
108 | - Added ability to set the scheduler name.
109 | - Added support for common labels.
110 |
111 | ### Changed
112 |
113 | - Updated the _Metrics Server_ OCI image to [v0.6.2](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.6.2).
114 |
115 | ## [3.8.2] - 2022-02-23
116 |
117 | ### Changed
118 |
119 | - Changed chart to allow probes to be turned off completely (this is not advised unless you know what you're doing).
120 |
121 | ## [3.8.1] - 2022-02-09
122 |
123 | ### Changed
124 |
125 | - Updated the _Metrics Server_ OCI image to [v0.6.1](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.6.1).
126 |
127 | ## [3.8.0] - 2022-02-08
128 |
129 | ### Added
130 |
131 | - Added support for unauthenticated access to the /metrics endpoint.
132 | - Added optional _Prometheus Operator_ `ServiceMonitor`.
133 |
134 | ### Changed
135 |
136 | - Updated the _Metrics Server_ OCI image to [v0.6.0](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.6.0).
137 |
138 | ## [3.7.0] - 2021-11-18
139 |
140 | ### Changed
141 |
142 | - Updated the _Metrics Server_ OCI image to [v0.5.2](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.5.2).
143 |
144 | ## [3.6.0] - 2021-10-18
145 |
146 | ### Added
147 |
148 | - Added new `defaultArgs`` value to enable overriding the default arguments.
149 |
150 | ### Changed
151 |
152 | - Updated the _Metrics Server_ OCI image to [v0.5.1](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.5.1).
153 |
154 | ## [3.5.0] - 2021-10-07
155 |
156 | ### Added
157 |
158 | - Added initial Helm chart release from official repo.
159 |
160 |
163 | [UNRELEASED]: https://github.com/kubernetes-sigs/metrics-server/tree/master/charts/metrics-server
164 | [3.12.2]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.12.2
165 | [3.12.1]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.12.1
166 | [3.12.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.12.0
167 | [3.11.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.11.0
168 | [3.10.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.10.0
169 | [3.9.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.9.0
170 | [3.8.4]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.8.4
171 | [3.8.3]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.8.3
172 | [3.8.2]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.8.2
173 | [3.8.1]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.8.1
174 | [3.8.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.8.0
175 | [3.7.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.7.0
176 | [3.6.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.6.0
177 | [3.5.0]: https://github.com/kubernetes-sigs/metrics-server/releases/tag/metrics-server-helm-chart-3.5.0
178 |
--------------------------------------------------------------------------------
/charts/metrics-server/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: metrics-server
3 | description: Metrics Server is a scalable, efficient source of container resource metrics for Kubernetes built-in autoscaling pipelines.
4 | type: application
5 | version: 3.12.2
6 | appVersion: 0.7.2
7 | keywords:
8 | - kubernetes
9 | - metrics-server
10 | - metrics
11 | home: https://github.com/kubernetes-sigs/metrics-server
12 | icon: https://avatars.githubusercontent.com/u/36015203?s=400&v=4
13 | sources:
14 | - https://github.com/kubernetes-sigs/metrics-server
15 | maintainers:
16 | - name: stevehipwell
17 | url: https://github.com/stevehipwell
18 | - name: krmichel
19 | url: https://github.com/krmichel
20 | - name: endrec
21 | url: https://github.com/endrec
22 | annotations:
23 | artifacthub.io/changes: |
24 | - kind: added
25 | description: "Explicitly added the app protocol to the service."
26 | - kind: changed
27 | description: "Updated the _Metrics Server_ OCI image to [v0.7.2](https://github.com/kubernetes-sigs/metrics-server/releases/tag/v0.7.2)."
28 | - kind: changed
29 | description: "Updated the _addonResizer_ OCI image to [1.8.21](https://github.com/kubernetes/autoscaler/releases/tag/addon-resizer-1.8.21)"
30 | - kind: fixed
31 | description: "Fixed nanny's RoleBinding which contained a hard-coded namespace instead of the Helm's release namespace."
32 |
--------------------------------------------------------------------------------
/charts/metrics-server/ci/ci-values.yaml:
--------------------------------------------------------------------------------
1 | args:
2 | - --kubelet-insecure-tls
3 |
--------------------------------------------------------------------------------
/charts/metrics-server/ci/tls-certManager-values.yaml:
--------------------------------------------------------------------------------
1 | args:
2 | - --kubelet-insecure-tls
3 |
4 | apiService:
5 | insecureSkipTLSVerify: false
6 |
7 | tls:
8 | type: cert-manager
9 |
--------------------------------------------------------------------------------
/charts/metrics-server/ci/tls-existingSecret-values.yaml:
--------------------------------------------------------------------------------
1 | args:
2 | - --kubelet-insecure-tls
3 |
4 | ## Set via GH action (step "Prepare existing secret test scenario")
5 | # apiService:
6 | # insecureSkipTLSVerify: false
7 | # caBundle: |
8 |
9 | tls:
10 | type: existingSecret
11 | existingSecret:
12 | name: metrics-server-existing
13 |
--------------------------------------------------------------------------------
/charts/metrics-server/ci/tls-helm-values.yaml:
--------------------------------------------------------------------------------
1 | args:
2 | - --kubelet-insecure-tls
3 |
4 | apiService:
5 | insecureSkipTLSVerify: false
6 |
7 | tls:
8 | type: helm
9 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | ***********************************************************************
2 | * Metrics Server *
3 | ***********************************************************************
4 | Chart version: {{ .Chart.Version }}
5 | App version: {{ .Chart.AppVersion }}
6 | Image tag: {{ include "metrics-server.image" . }}
7 | ***********************************************************************
8 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/*
2 | Expand the name of the chart.
3 | */}}
4 | {{- define "metrics-server.name" -}}
5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6 | {{- end }}
7 |
8 | {{/*
9 | Create a default fully qualified app name.
10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
11 | If release name contains chart name it will be used as a full name.
12 | */}}
13 | {{- define "metrics-server.fullname" -}}
14 | {{- if .Values.fullnameOverride }}
15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
16 | {{- else }}
17 | {{- $name := default .Chart.Name .Values.nameOverride }}
18 | {{- if contains $name .Release.Name }}
19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }}
20 | {{- else }}
21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
22 | {{- end }}
23 | {{- end }}
24 | {{- end }}
25 |
26 | {{/*
27 | Create chart name and version as used by the chart label.
28 | */}}
29 | {{- define "metrics-server.chart" -}}
30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
31 | {{- end }}
32 |
33 | {{/*
34 | Common labels
35 | */}}
36 | {{- define "metrics-server.labels" -}}
37 | helm.sh/chart: {{ include "metrics-server.chart" . }}
38 | {{ include "metrics-server.selectorLabels" . }}
39 | {{- if .Chart.AppVersion }}
40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
41 | {{- end }}
42 | app.kubernetes.io/managed-by: {{ .Release.Service }}
43 | {{- if .Values.commonLabels }}
44 | {{ toYaml .Values.commonLabels }}
45 | {{- end }}
46 | {{- end }}
47 |
48 | {{/*
49 | Selector labels
50 | */}}
51 | {{- define "metrics-server.selectorLabels" -}}
52 | app.kubernetes.io/name: {{ include "metrics-server.name" . }}
53 | app.kubernetes.io/instance: {{ .Release.Name }}
54 | {{- end }}
55 |
56 | {{/*
57 | Create the name of the service account to use
58 | */}}
59 | {{- define "metrics-server.serviceAccountName" -}}
60 | {{- if .Values.serviceAccount.create }}
61 | {{- default (include "metrics-server.fullname" .) .Values.serviceAccount.name }}
62 | {{- else }}
63 | {{- default "default" .Values.serviceAccount.name }}
64 | {{- end }}
65 | {{- end }}
66 |
67 | {{/*
68 | The image to use
69 | */}}
70 | {{- define "metrics-server.image" -}}
71 | {{- printf "%s:%s" .Values.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.image.tag) }}
72 | {{- end }}
73 |
74 | {{/*
75 | The image to use for the addon resizer
76 | */}}
77 | {{- define "metrics-server.addonResizer.image" -}}
78 | {{- printf "%s:%s" .Values.addonResizer.image.repository .Values.addonResizer.image.tag }}
79 | {{- end }}
80 |
81 | {{/*
82 | ConfigMap name of addon resizer
83 | */}}
84 | {{- define "metrics-server.addonResizer.configMap" -}}
85 | {{- printf "%s-%s" (include "metrics-server.fullname" .) "nanny-config" }}
86 | {{- end }}
87 |
88 | {{/*
89 | Role name of addon resizer
90 | */}}
91 | {{- define "metrics-server.addonResizer.role" -}}
92 | {{ printf "system:%s-nanny" (include "metrics-server.fullname" .) }}
93 | {{- end }}
94 |
95 | {{/* Get PodDisruptionBudget API Version */}}
96 | {{- define "metrics-server.pdb.apiVersion" -}}
97 | {{- if and (.Capabilities.APIVersions.Has "policy/v1") (semverCompare ">= 1.21-0" .Capabilities.KubeVersion.Version) -}}
98 | {{- print "policy/v1" -}}
99 | {{- else -}}
100 | {{- print "policy/v1beta1" -}}
101 | {{- end -}}
102 | {{- end -}}
103 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/apiservice.yaml:
--------------------------------------------------------------------------------
1 | {{- $altNames := list }}
2 | {{- $certs := dict }}
3 | {{- $previous := dict }}
4 |
5 | {{- if eq .Values.tls.type "helm" }}
6 | {{- $previous = lookup "v1" "Secret" .Release.Namespace (include "metrics-server.fullname" .) }}
7 | {{- $commonName := include "metrics-server.fullname" . }}
8 | {{- $ns := .Release.Namespace }}
9 | {{- $altNames = append $altNames (printf "%s.%s" $commonName $ns) }}
10 | {{- $altNames = append $altNames (printf "%s.%s.svc" $commonName $ns) }}
11 | {{- $altNames = append $altNames (printf "%s.%s.svc.%s" $commonName $ns .Values.tls.clusterDomain) }}
12 | {{- $certs = genSelfSignedCert $commonName nil $altNames (int .Values.tls.helm.certDurationDays) }}
13 | apiVersion: v1
14 | kind: Secret
15 | metadata:
16 | name: {{ include "metrics-server.fullname" . }}
17 | labels:
18 | {{- include "metrics-server.labels" . | nindent 4 }}
19 | type: Opaque
20 | data:
21 | {{- if and $previous .Values.tls.helm.lookup }}
22 | tls.crt: {{ index $previous.data "tls.crt" }}
23 | tls.key: {{ index $previous.data "tls.key" }}
24 | {{- else }}
25 | tls.crt: {{ $certs.Cert| b64enc | quote }}
26 | tls.key: {{ $certs.Key | b64enc | quote }}
27 | {{- end }}
28 | {{- end }}
29 | ---
30 | {{- $existing := dict }}
31 | {{- if .Values.apiService.create }}
32 | {{- if and (eq .Values.tls.type "existingSecret") .Values.tls.existingSecret.lookup }}
33 | {{- $existing := lookup "v1" "Secret" .Release.Namespace .Values.tls.existingSecret.name }}
34 | {{- end }}
35 | apiVersion: apiregistration.k8s.io/v1
36 | kind: APIService
37 | metadata:
38 | name: v1beta1.metrics.k8s.io
39 | labels:
40 | {{- include "metrics-server.labels" . | nindent 4 }}
41 | {{- if or .Values.apiService.annotations .Values.tls.certManager.addInjectorAnnotations }}
42 | annotations:
43 | {{- if and (eq .Values.tls.type "cert-manager") .Values.tls.certManager.addInjectorAnnotations }}
44 | cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "metrics-server.fullname" . }}
45 | {{- end }}
46 | {{- with .Values.apiService.annotations }}
47 | {{- toYaml . | nindent 4 }}
48 | {{- end }}
49 | {{- end }}
50 | spec:
51 | {{- if eq .Values.tls.type "helm" }}
52 | {{- if and $previous .Values.tls.helm.lookup }}
53 | caBundle: {{ index $previous.data "tls.crt" }}
54 | {{- else }}
55 | caBundle: {{ $certs.Cert | b64enc }}
56 | {{- end }}
57 | {{- else if $existing }}
58 | caBundle: {{ index $existing.data "tls.crt" }}
59 | {{- else if and .Values.apiService.caBundle (ne .Values.tls.type "cert-manager") }}
60 | caBundle: {{ .Values.apiService.caBundle | b64enc }}
61 | {{- end }}
62 | group: metrics.k8s.io
63 | groupPriorityMinimum: 100
64 | insecureSkipTLSVerify: {{ .Values.apiService.insecureSkipTLSVerify }}
65 | service:
66 | name: {{ include "metrics-server.fullname" . }}
67 | namespace: {{ .Release.Namespace }}
68 | port: {{ .Values.service.port }}
69 | version: v1beta1
70 | versionPriority: 100
71 | {{- end }}
72 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/certificate.yaml:
--------------------------------------------------------------------------------
1 | {{- if eq .Values.tls.type "cert-manager" }}
2 | {{- if not .Values.tls.certManager.existingIssuer.enabled }}
3 | apiVersion: cert-manager.io/v1
4 | kind: Issuer
5 | metadata:
6 | annotations:
7 | {{- toYaml .Values.additionalAnnotations | nindent 4 }}
8 | name: {{ include "metrics-server.fullname" . }}-issuer
9 | namespace: {{ .Release.Namespace }}
10 | spec:
11 | selfSigned: {}
12 | {{- end }}
13 | ---
14 | apiVersion: cert-manager.io/v1
15 | kind: Certificate
16 | metadata:
17 | name: {{ include "metrics-server.fullname" . }}
18 | namespace: {{ .Release.Namespace }}
19 | spec:
20 | commonName: {{ include "metrics-server.fullname" . }}
21 | dnsNames:
22 | - {{ include "metrics-server.fullname" . }}.{{ .Release.Namespace }}
23 | - {{ include "metrics-server.fullname" . }}.{{ .Release.Namespace }}.svc
24 | - {{ include "metrics-server.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.tls.clusterDomain }}
25 | secretName: {{ include "metrics-server.fullname" . }}
26 | usages:
27 | - server auth
28 | - client auth
29 | privateKey:
30 | algorithm: RSA
31 | size: 2048
32 | {{- with .Values.tls.certManager.duration }}
33 | duration: {{ . }}
34 | {{- end }}
35 | {{- with .Values.tls.certManager.renewBefore }}
36 | renewBefore: {{ . }}
37 | {{- end }}
38 | issuerRef:
39 | {{- if .Values.tls.certManager.existingIssuer.enabled }}
40 | name: {{ .Values.tls.certManager.existingIssuer.name }}
41 | kind: {{ .Values.tls.certManager.existingIssuer.kind }}
42 | {{- else }}
43 | name: {{ include "metrics-server.fullname" . }}-issuer
44 | kind: Issuer
45 | {{- end }}
46 | group: cert-manager.io
47 | {{- end }}
48 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/clusterrole-aggregated-reader.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: {{ printf "system:%s-aggregated-reader" (include "metrics-server.name" .) }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | rbac.authorization.k8s.io/aggregate-to-admin: "true"
9 | rbac.authorization.k8s.io/aggregate-to-edit: "true"
10 | rbac.authorization.k8s.io/aggregate-to-view: "true"
11 | rules:
12 | - apiGroups:
13 | - metrics.k8s.io
14 | resources:
15 | - pods
16 | - nodes
17 | verbs:
18 | - get
19 | - list
20 | - watch
21 | {{- end -}}
22 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/clusterrole-nanny.yaml:
--------------------------------------------------------------------------------
1 | {{- if and .Values.rbac.create .Values.addonResizer.enabled -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: {{ printf "system:%s-nanny" (include "metrics-server.fullname" .) }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | rules:
9 | - nonResourceURLs:
10 | - /metrics
11 | verbs:
12 | - get
13 | {{- end -}}
14 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/clusterrole.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: {{ printf "system:%s" (include "metrics-server.fullname" .) }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | rules:
9 | - apiGroups:
10 | - ""
11 | resources:
12 | - nodes/metrics
13 | verbs:
14 | - get
15 | - apiGroups:
16 | - ""
17 | resources:
18 | - pods
19 | - nodes
20 | - namespaces
21 | - configmaps
22 | verbs:
23 | - get
24 | - list
25 | - watch
26 | {{- if .Values.rbac.pspEnabled }}
27 | - apiGroups:
28 | - extensions
29 | - policy
30 | resources:
31 | - podsecuritypolicies
32 | resourceNames:
33 | - {{ printf "privileged-%s" (include "metrics-server.fullname" .) }}
34 | verbs:
35 | - use
36 | {{- end -}}
37 | {{- end -}}
38 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/clusterrolebinding-auth-delegator.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRoleBinding
4 | metadata:
5 | name: {{ printf "%s:system:auth-delegator" (include "metrics-server.fullname" .) }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | roleRef:
9 | apiGroup: rbac.authorization.k8s.io
10 | kind: ClusterRole
11 | name: system:auth-delegator
12 | subjects:
13 | - kind: ServiceAccount
14 | name: {{ include "metrics-server.serviceAccountName" . }}
15 | namespace: {{ .Release.Namespace }}
16 | {{- end -}}
17 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/clusterrolebinding-nanny.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | {{- if .Values.addonResizer.enabled -}}
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | kind: ClusterRoleBinding
5 | metadata:
6 | name: {{ printf "system:%s-nanny" (include "metrics-server.fullname" .) }}
7 | labels:
8 | {{- include "metrics-server.labels" . | nindent 4 }}
9 | roleRef:
10 | apiGroup: rbac.authorization.k8s.io
11 | kind: ClusterRole
12 | name: system:{{ template "metrics-server.fullname" . }}-nanny
13 | subjects:
14 | - kind: ServiceAccount
15 | name: {{ include "metrics-server.serviceAccountName" . }}
16 | namespace: {{ .Release.Namespace }}
17 | {{- end -}}
18 | {{- end -}}
19 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRoleBinding
4 | metadata:
5 | name: {{ printf "system:%s" (include "metrics-server.fullname" .) }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | roleRef:
9 | apiGroup: rbac.authorization.k8s.io
10 | kind: ClusterRole
11 | name: system:{{ template "metrics-server.fullname" . }}
12 | subjects:
13 | - kind: ServiceAccount
14 | name: {{ include "metrics-server.serviceAccountName" . }}
15 | namespace: {{ .Release.Namespace }}
16 | {{- end -}}
17 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/configmaps-nanny.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.addonResizer.enabled -}}
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: {{ include "metrics-server.addonResizer.configMap" . }}
6 | namespace: {{ .Release.Namespace }}
7 | labels:
8 | {{- include "metrics-server.labels" . | nindent 4 }}
9 | data:
10 | NannyConfiguration: |-
11 | apiVersion: nannyconfig/v1alpha1
12 | kind: NannyConfiguration
13 | baseCPU: {{ .Values.addonResizer.nanny.cpu }}
14 | cpuPerNode: {{ .Values.addonResizer.nanny.extraCpu }}
15 | baseMemory: {{ .Values.addonResizer.nanny.memory }}
16 | memoryPerNode: {{ .Values.addonResizer.nanny.extraMemory }}
17 | {{- end -}}
18 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "metrics-server.fullname" . }}
5 | namespace: {{ .Release.Namespace }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | {{- with .Values.deploymentAnnotations }}
9 | annotations:
10 | {{- toYaml . | nindent 4 }}
11 | {{- end }}
12 | spec:
13 | replicas: {{ .Values.replicas }}
14 | {{- if not (has (quote .Values.revisionHistoryLimit) (list "" (quote ""))) }}
15 | revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
16 | {{- end }}
17 | {{- with .Values.updateStrategy }}
18 | strategy:
19 | {{- toYaml . | nindent 4 }}
20 | {{- end }}
21 | selector:
22 | matchLabels:
23 | {{- include "metrics-server.selectorLabels" . | nindent 6 }}
24 | template:
25 | metadata:
26 | labels:
27 | {{- include "metrics-server.selectorLabels" . | nindent 8 }}
28 | {{- with .Values.podLabels }}
29 | {{- toYaml . | nindent 8 }}
30 | {{- end }}
31 | {{- with .Values.podAnnotations }}
32 | annotations:
33 | {{- toYaml . | nindent 8 }}
34 | {{- end }}
35 | spec:
36 | {{- with .Values.schedulerName }}
37 | schedulerName: {{ . }}
38 | {{- end }}
39 | {{- with .Values.imagePullSecrets }}
40 | imagePullSecrets:
41 | {{- toYaml . | nindent 8 }}
42 | {{- end }}
43 | serviceAccountName: {{ include "metrics-server.serviceAccountName" . }}
44 | {{- with .Values.podSecurityContext }}
45 | securityContext:
46 | {{- toYaml . | nindent 8 }}
47 | {{- end }}
48 | {{- with .Values.priorityClassName }}
49 | priorityClassName: {{ . | quote }}
50 | {{- end }}
51 | {{- if .Values.hostNetwork.enabled }}
52 | hostNetwork: true
53 | {{- end }}
54 | {{- with .Values.dnsConfig }}
55 | dnsConfig:
56 | {{- toYaml . | nindent 8 }}
57 | {{- end }}
58 | containers:
59 | - name: metrics-server
60 | {{- with .Values.securityContext }}
61 | securityContext:
62 | {{- toYaml . | nindent 12 }}
63 | {{- end }}
64 | image: {{ include "metrics-server.image" . }}
65 | imagePullPolicy: {{ .Values.image.pullPolicy }}
66 | args:
67 | - {{ printf "--secure-port=%d" (int .Values.containerPort) }}
68 | {{- range .Values.defaultArgs }}
69 | - {{ . }}
70 | {{- end }}
71 | {{- if .Values.metrics.enabled }}
72 | - --authorization-always-allow-paths=/metrics
73 | {{- end }}
74 | {{- if ne .Values.tls.type "metrics-server" }}
75 | - --tls-cert-file=/tmp/tls-certs/tls.crt
76 | - --tls-private-key-file=/tmp/tls-certs/tls.key
77 | {{- end }}
78 | {{- range .Values.args }}
79 | - {{ . }}
80 | {{- end }}
81 | ports:
82 | - name: https
83 | protocol: TCP
84 | containerPort: {{ .Values.containerPort }}
85 | {{- with .Values.livenessProbe }}
86 | livenessProbe:
87 | {{- toYaml . | nindent 12 }}
88 | {{- end }}
89 | {{- with .Values.readinessProbe }}
90 | readinessProbe:
91 | {{- toYaml . | nindent 12 }}
92 | {{- end }}
93 | volumeMounts:
94 | - name: tmp
95 | mountPath: /tmp
96 | {{- if ne .Values.tls.type "metrics-server" }}
97 | - mountPath: /tmp/tls-certs
98 | name: certs
99 | readOnly: true
100 | {{- end }}
101 | {{- with .Values.extraVolumeMounts }}
102 | {{- toYaml . | nindent 12 }}
103 | {{- end }}
104 | {{- with .Values.resources }}
105 | resources:
106 | {{- toYaml . | nindent 12 }}
107 | {{- end }}
108 | {{- if .Values.addonResizer.enabled }}
109 | - name: metrics-server-nanny
110 | {{- with .Values.addonResizer.securityContext }}
111 | securityContext:
112 | {{- toYaml . | nindent 12 }}
113 | {{- end }}
114 | image: {{ include "metrics-server.addonResizer.image" . }}
115 | env:
116 | - name: MY_POD_NAME
117 | valueFrom:
118 | fieldRef:
119 | fieldPath: metadata.name
120 | - name: MY_POD_NAMESPACE
121 | valueFrom:
122 | fieldRef:
123 | fieldPath: metadata.namespace
124 | command:
125 | - /pod_nanny
126 | - --config-dir=/etc/config
127 | - --deployment={{ include "metrics-server.fullname" . }}
128 | - --container=metrics-server
129 | - --threshold={{ .Values.addonResizer.nanny.threshold }}
130 | - --poll-period={{ .Values.addonResizer.nanny.pollPeriod }}
131 | - --estimator=exponential
132 | - --minClusterSize={{ .Values.addonResizer.nanny.minClusterSize }}
133 | - --use-metrics=true
134 | volumeMounts:
135 | - name: nanny-config-volume
136 | mountPath: /etc/config
137 | {{- with .Values.addonResizer.resources }}
138 | resources:
139 | {{- toYaml . | nindent 12 }}
140 | {{- end }}
141 | {{- end }}
142 | volumes:
143 | - name: tmp
144 | {{- toYaml .Values.tmpVolume | nindent 10 }}
145 | {{- if .Values.addonResizer.enabled }}
146 | - name: nanny-config-volume
147 | configMap:
148 | name: {{ include "metrics-server.addonResizer.configMap" . }}
149 | {{- end }}
150 | {{- if ne .Values.tls.type "metrics-server" }}
151 | - name: certs
152 | secret:
153 | {{- if and (eq .Values.tls.type "existingSecret") .Values.tls.existingSecret.name }}
154 | secretName: {{ .Values.tls.existingSecret.name }}
155 | {{- else }}
156 | secretName: {{ include "metrics-server.fullname" . }}
157 | {{- end }}
158 | {{- end }}
159 | {{- with .Values.extraVolumes }}
160 | {{- toYaml . | nindent 8 }}
161 | {{- end }}
162 | {{- with .Values.nodeSelector }}
163 | nodeSelector:
164 | {{- toYaml . | nindent 8 }}
165 | {{- end }}
166 | {{- with .Values.affinity }}
167 | affinity:
168 | {{- toYaml . | nindent 8 }}
169 | {{- end }}
170 | {{- with .Values.tolerations }}
171 | tolerations:
172 | {{- toYaml . | nindent 8 }}
173 | {{- end }}
174 | {{- with .Values.topologySpreadConstraints }}
175 | topologySpreadConstraints:
176 | {{- toYaml . | nindent 8 }}
177 | {{- end }}
178 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/pdb.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.podDisruptionBudget.enabled -}}
2 | apiVersion: {{ include "metrics-server.pdb.apiVersion" . }}
3 | kind: PodDisruptionBudget
4 | metadata:
5 | name: {{ include "metrics-server.fullname" . }}
6 | namespace: {{ .Release.Namespace }}
7 | labels:
8 | {{- include "metrics-server.labels" . | nindent 4 }}
9 | spec:
10 | {{- if .Values.podDisruptionBudget.minAvailable }}
11 | minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
12 | {{- end }}
13 | {{- if .Values.podDisruptionBudget.maxUnavailable }}
14 | maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
15 | {{- end }}
16 | {{- if (semverCompare ">= 1.27-0" .Capabilities.KubeVersion.Version) }}
17 | {{- with .Values.podDisruptionBudget.unhealthyPodEvictionPolicy }}
18 | unhealthyPodEvictionPolicy: {{ . }}
19 | {{- end }}
20 | {{- end }}
21 |
22 | selector:
23 | matchLabels:
24 | {{- include "metrics-server.selectorLabels" . | nindent 6 }}
25 | {{- end -}}
26 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/psp.yaml:
--------------------------------------------------------------------------------
1 | {{- if and (.Values.rbac.pspEnabled) (semverCompare "<1.25-0" .Capabilities.KubeVersion.GitVersion) }}
2 | apiVersion: policy/v1beta1
3 | kind: PodSecurityPolicy
4 | metadata:
5 | name: {{ printf "privileged-%s" (include "metrics-server.fullname" .) }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | spec:
9 | allowedCapabilities:
10 | - '*'
11 | fsGroup:
12 | rule: RunAsAny
13 | privileged: true
14 | runAsUser:
15 | rule: RunAsAny
16 | seLinux:
17 | rule: RunAsAny
18 | supplementalGroups:
19 | rule: RunAsAny
20 | volumes:
21 | - '*'
22 | hostPID: true
23 | hostIPC: true
24 | hostNetwork: true
25 | hostPorts:
26 | - min: 1
27 | max: 65536
28 | {{- end }}
29 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/role-nanny.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | {{- if .Values.addonResizer.enabled -}}
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | kind: Role
5 | metadata:
6 | name: {{ include "metrics-server.addonResizer.role" . }}
7 | namespace: {{ .Release.Namespace }}
8 | labels:
9 | {{- include "metrics-server.labels" . | nindent 4 }}
10 | rules:
11 | - apiGroups:
12 | - ""
13 | resources:
14 | - pods
15 | verbs:
16 | - get
17 | - apiGroups:
18 | - apps
19 | resources:
20 | - deployments
21 | resourceNames:
22 | - {{ include "metrics-server.fullname" . }}
23 | verbs:
24 | - get
25 | - patch
26 | {{- end -}}
27 | {{- end -}}
28 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/rolebinding-nanny.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | {{- if .Values.addonResizer.enabled -}}
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | kind: RoleBinding
5 | metadata:
6 | name: {{ printf "%s-nanny" (include "metrics-server.fullname" .) }}
7 | namespace: {{ .Release.Namespace }}
8 | labels:
9 | {{- include "metrics-server.labels" . | nindent 4 }}
10 | roleRef:
11 | apiGroup: rbac.authorization.k8s.io
12 | kind: Role
13 | name: {{ include "metrics-server.addonResizer.role" . }}
14 | subjects:
15 | - kind: ServiceAccount
16 | name: {{ include "metrics-server.serviceAccountName" . }}
17 | namespace: {{ .Release.Namespace }}
18 | {{- end -}}
19 | {{- end -}}
20 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/rolebinding.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.rbac.create -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: RoleBinding
4 | metadata:
5 | name: {{ printf "%s-auth-reader" (include "metrics-server.fullname" .) }}
6 | namespace: kube-system
7 | labels:
8 | {{- include "metrics-server.labels" . | nindent 4 }}
9 | roleRef:
10 | apiGroup: rbac.authorization.k8s.io
11 | kind: Role
12 | name: extension-apiserver-authentication-reader
13 | subjects:
14 | - kind: ServiceAccount
15 | name: {{ include "metrics-server.serviceAccountName" . }}
16 | namespace: {{ .Release.Namespace }}
17 | {{- end -}}
18 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ include "metrics-server.fullname" . }}
5 | namespace: {{ .Release.Namespace }}
6 | labels:
7 | {{- include "metrics-server.labels" . | nindent 4 }}
8 | {{- with .Values.service.labels -}}
9 | {{- toYaml . | nindent 4 }}
10 | {{- end }}
11 | {{- with .Values.service.annotations }}
12 | annotations:
13 | {{- toYaml . | nindent 4 }}
14 | {{- end }}
15 | spec:
16 | type: {{ .Values.service.type }}
17 | ports:
18 | - name: https
19 | port: {{ .Values.service.port }}
20 | protocol: TCP
21 | targetPort: https
22 | appProtocol: https
23 | selector:
24 | {{- include "metrics-server.selectorLabels" . | nindent 4 }}
25 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.serviceAccount.create -}}
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: {{ template "metrics-server.serviceAccountName" . }}
6 | namespace: {{ .Release.Namespace }}
7 | labels:
8 | {{- include "metrics-server.labels" . | nindent 4 }}
9 | {{- with .Values.serviceAccount.annotations }}
10 | annotations:
11 | {{- toYaml . | nindent 4 }}
12 | {{- end }}
13 | {{- with .Values.serviceAccount.secrets }}
14 | secrets:
15 | {{- toYaml . | nindent 2 }}
16 | {{- end }}
17 | {{- end -}}
18 |
--------------------------------------------------------------------------------
/charts/metrics-server/templates/servicemonitor.yaml:
--------------------------------------------------------------------------------
1 | {{- if and .Values.serviceMonitor.enabled .Values.metrics.enabled -}}
2 | apiVersion: monitoring.coreos.com/v1
3 | kind: ServiceMonitor
4 | metadata:
5 | name: {{ include "metrics-server.fullname" . }}
6 | namespace: {{ .Release.Namespace }}
7 | labels:
8 | {{- include "metrics-server.labels" . | nindent 4 }}
9 | {{- with .Values.serviceMonitor.additionalLabels }}
10 | {{- toYaml . | nindent 4 }}
11 | {{- end }}
12 | spec:
13 | jobLabel: app.kubernetes.io/instance
14 | namespaceSelector:
15 | matchNames:
16 | - {{ .Release.Namespace }}
17 | selector:
18 | matchLabels:
19 | {{- include "metrics-server.selectorLabels" . | nindent 6 }}
20 | endpoints:
21 | - port: https
22 | path: /metrics
23 | scheme: https
24 | tlsConfig:
25 | insecureSkipVerify: true
26 | {{- with .Values.serviceMonitor.interval }}
27 | interval: {{ . }}
28 | {{- end }}
29 | {{- with .Values.serviceMonitor.scrapeTimeout }}
30 | scrapeTimeout: {{ . }}
31 | {{- end }}
32 | {{- with .Values.serviceMonitor.metricRelabelings }}
33 | metricRelabelings:
34 | {{- toYaml . | nindent 8 }}
35 | {{- end }}
36 | {{- with .Values.serviceMonitor.relabelings }}
37 | relabelings:
38 | {{- toYaml . | nindent 8 }}
39 | {{- end }}
40 | {{- end -}}
41 |
--------------------------------------------------------------------------------
/charts/metrics-server/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for metrics-server.
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | image:
6 | repository: registry.k8s.io/metrics-server/metrics-server
7 | # Overrides the image tag whose default is v{{ .Chart.AppVersion }}
8 | tag: ""
9 | pullPolicy: IfNotPresent
10 |
11 | imagePullSecrets: []
12 | # - name: registrySecretName
13 |
14 | nameOverride: ""
15 | fullnameOverride: ""
16 |
17 | serviceAccount:
18 | # Specifies whether a service account should be created
19 | create: true
20 | # Annotations to add to the service account
21 | annotations: {}
22 | # The name of the service account to use.
23 | # If not set and create is true, a name is generated using the fullname template
24 | name: ""
25 | # The list of secrets mountable by this service account.
26 | # See https://kubernetes.io/docs/reference/labels-annotations-taints/#enforce-mountable-secrets
27 | secrets: []
28 |
29 | rbac:
30 | # Specifies whether RBAC resources should be created
31 | create: true
32 | # Note: PodSecurityPolicy will not be created when Kubernetes version is 1.25 or later.
33 | pspEnabled: false
34 |
35 | apiService:
36 | # Specifies if the v1beta1.metrics.k8s.io API service should be created.
37 | #
38 | # You typically want this enabled! If you disable API service creation you have to
39 | # manage it outside of this chart for e.g horizontal pod autoscaling to
40 | # work with this release.
41 | create: true
42 | # Annotations to add to the API service
43 | annotations: {}
44 | # Specifies whether to skip TLS verification
45 | insecureSkipTLSVerify: true
46 | # The PEM encoded CA bundle for TLS verification
47 | caBundle: ""
48 |
49 | commonLabels: {}
50 | podLabels: {}
51 | podAnnotations: {}
52 |
53 | podSecurityContext: {}
54 |
55 | securityContext:
56 | allowPrivilegeEscalation: false
57 | readOnlyRootFilesystem: true
58 | runAsNonRoot: true
59 | runAsUser: 1000
60 | seccompProfile:
61 | type: RuntimeDefault
62 | capabilities:
63 | drop:
64 | - ALL
65 |
66 | priorityClassName: system-cluster-critical
67 |
68 | containerPort: 10250
69 |
70 | hostNetwork:
71 | # Specifies if metrics-server should be started in hostNetwork mode.
72 | #
73 | # You would require this enabled if you use alternate overlay networking for pods and
74 | # API server unable to communicate with metrics-server. As an example, this is required
75 | # if you use Weave network on EKS
76 | enabled: false
77 |
78 | replicas: 1
79 |
80 | revisionHistoryLimit:
81 |
82 | updateStrategy: {}
83 | # type: RollingUpdate
84 | # rollingUpdate:
85 | # maxSurge: 0
86 | # maxUnavailable: 1
87 |
88 | podDisruptionBudget:
89 | # https://kubernetes.io/docs/tasks/run-application/configure-pdb/
90 | enabled: false
91 | minAvailable:
92 | maxUnavailable:
93 | unhealthyPodEvictionPolicy:
94 |
95 | defaultArgs:
96 | - --cert-dir=/tmp
97 | - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
98 | - --kubelet-use-node-status-port
99 | - --metric-resolution=15s
100 |
101 | args: []
102 |
103 | livenessProbe:
104 | httpGet:
105 | path: /livez
106 | port: https
107 | scheme: HTTPS
108 | initialDelaySeconds: 0
109 | periodSeconds: 10
110 | failureThreshold: 3
111 |
112 | readinessProbe:
113 | httpGet:
114 | path: /readyz
115 | port: https
116 | scheme: HTTPS
117 | initialDelaySeconds: 20
118 | periodSeconds: 10
119 | failureThreshold: 3
120 |
121 | service:
122 | type: ClusterIP
123 | port: 443
124 | annotations: {}
125 | labels: {}
126 | # Add these labels to have metrics-server show up in `kubectl cluster-info`
127 | # kubernetes.io/cluster-service: "true"
128 | # kubernetes.io/name: "Metrics-server"
129 |
130 | addonResizer:
131 | enabled: false
132 | image:
133 | repository: registry.k8s.io/autoscaling/addon-resizer
134 | tag: 1.8.23
135 | securityContext:
136 | allowPrivilegeEscalation: false
137 | readOnlyRootFilesystem: true
138 | runAsNonRoot: true
139 | runAsUser: 1000
140 | seccompProfile:
141 | type: RuntimeDefault
142 | capabilities:
143 | drop:
144 | - ALL
145 | resources:
146 | requests:
147 | cpu: 40m
148 | memory: 25Mi
149 | limits:
150 | cpu: 40m
151 | memory: 25Mi
152 | nanny:
153 | cpu: 0m
154 | extraCpu: 1m
155 | memory: 0Mi
156 | extraMemory: 2Mi
157 | minClusterSize: 100
158 | pollPeriod: 300000
159 | threshold: 5
160 |
161 | metrics:
162 | enabled: false
163 |
164 | serviceMonitor:
165 | enabled: false
166 | additionalLabels: {}
167 | interval: 1m
168 | scrapeTimeout: 10s
169 | metricRelabelings: []
170 | relabelings: []
171 |
172 | # See https://github.com/kubernetes-sigs/metrics-server#scaling
173 | resources:
174 | requests:
175 | cpu: 100m
176 | memory: 200Mi
177 | # limits:
178 | # cpu:
179 | # memory:
180 |
181 | extraVolumeMounts: []
182 |
183 | extraVolumes: []
184 |
185 | nodeSelector: {}
186 |
187 | tolerations: []
188 |
189 | affinity: {}
190 |
191 | topologySpreadConstraints: []
192 |
193 | dnsConfig: {}
194 |
195 | # Annotations to add to the deployment
196 | deploymentAnnotations: {}
197 |
198 | schedulerName: ""
199 |
200 | tmpVolume:
201 | emptyDir: {}
202 |
203 | tls:
204 | # Set the TLS method to use. Supported values:
205 | # - `metrics-server` : Metrics-server will generate a self-signed certificate
206 | # - `helm` : Helm will generate a self-signed certificate
207 | # - `cert-manager` : Use cert-manager.io to create and maintain the certificate
208 | # - `existingSecret` : Reuse an existing secret. No new secret will be created
209 | type: "metrics-server"
210 | # Kubernetes cluster domain. Used to configure Subject Alt Names for the certificate
211 | clusterDomain: cluster.local
212 |
213 | certManager:
214 | # Automatically add the cert-manager.io/inject-ca-from annotation to the APIService resource.
215 | # See https://cert-manager.io/docs/concepts/ca-injector
216 | addInjectorAnnotations: true
217 | existingIssuer:
218 | # Use an existing cert-manager issuer
219 | enabled: false
220 | # Kind of the existing cert-manager issuer
221 | kind: "Issuer"
222 | # Name of the existing cert-manager issuer
223 | name: "my-issuer"
224 | # Set the requested duration (i.e. lifetime) of the Certificate.
225 | # See https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec
226 | duration: ""
227 | # How long before the currently issued certificate’s expiry cert-manager should renew the certificate.
228 | # See https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec
229 | renewBefore: ""
230 | # Add extra annotations to the Certificate resource
231 | annotations: {}
232 | # Add extra labels to the Certificate resource
233 | labels: {}
234 |
235 | helm:
236 | # Use helm lookup function to reuse Secret created in previous helm install
237 | lookup: true
238 | # Cert validity duration in days
239 | certDurationDays: 365
240 |
241 | existingSecret:
242 | # Name of the existing Secret to use for TLS
243 | name: ""
244 | # Use helm lookup function to provision `apiService.caBundle`
245 | lookup: true
246 |
--------------------------------------------------------------------------------
/cloudbuild.yaml:
--------------------------------------------------------------------------------
1 | # See https://cloud.google.com/cloud-build/docs/build-config
2 | timeout: 3600s
3 | options:
4 | substitution_option: ALLOW_LOOSE
5 | steps:
6 | - name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20231105-52c482caa0'
7 | entrypoint: make
8 | env:
9 | - GIT_TAG=$_PULL_BASE_REF
10 | - GIT_COMMIT=$_PULL_BASE_SHA
11 | args:
12 | - push-all
13 |
--------------------------------------------------------------------------------
/cmd/metrics-server/app/options/kubelet_client.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | package options
15 |
16 | import (
17 | "fmt"
18 | "time"
19 |
20 | "github.com/spf13/pflag"
21 |
22 | corev1 "k8s.io/api/core/v1"
23 | "k8s.io/client-go/rest"
24 |
25 | "sigs.k8s.io/metrics-server/pkg/scraper/client"
26 | "sigs.k8s.io/metrics-server/pkg/utils"
27 | )
28 |
29 | type KubeletClientOptions struct {
30 | KubeletUseNodeStatusPort bool
31 | KubeletPort int
32 | InsecureKubeletTLS bool
33 | KubeletPreferredAddressTypes []string
34 | KubeletCAFile string
35 | KubeletClientKeyFile string
36 | KubeletClientCertFile string
37 | DeprecatedCompletelyInsecureKubelet bool
38 | KubeletRequestTimeout time.Duration
39 | NodeSelector string
40 | }
41 |
42 | func (o *KubeletClientOptions) Validate() []error {
43 | errors := []error{}
44 | if (o.KubeletCAFile != "") && o.InsecureKubeletTLS {
45 | errors = append(errors, fmt.Errorf("cannot use both --kubelet-certificate-authority and --kubelet-insecure-tls"))
46 | }
47 |
48 | if (o.KubeletClientKeyFile != "") != (o.KubeletClientCertFile != "") {
49 | errors = append(errors, fmt.Errorf("need both --kubelet-client-key and --kubelet-client-certificate"))
50 | }
51 |
52 | if (o.KubeletClientKeyFile != "") && o.DeprecatedCompletelyInsecureKubelet {
53 | errors = append(errors, fmt.Errorf("cannot use both --kubelet-client-key and --deprecated-kubelet-completely-insecure"))
54 | }
55 |
56 | if (o.KubeletClientCertFile != "") && o.DeprecatedCompletelyInsecureKubelet {
57 | errors = append(errors, fmt.Errorf("cannot use both --kubelet-client-certificate and --deprecated-kubelet-completely-insecure"))
58 | }
59 |
60 | if o.InsecureKubeletTLS && o.DeprecatedCompletelyInsecureKubelet {
61 | errors = append(errors, fmt.Errorf("cannot use both --kubelet-insecure-tls and --deprecated-kubelet-completely-insecure"))
62 | }
63 | if (o.KubeletCAFile != "") && o.DeprecatedCompletelyInsecureKubelet {
64 | errors = append(errors, fmt.Errorf("cannot use both --kubelet-certificate-authority and --deprecated-kubelet-completely-insecure"))
65 | }
66 | if o.KubeletRequestTimeout <= 0 {
67 | errors = append(errors, fmt.Errorf("kubelet-request-timeout should be positive"))
68 | }
69 | return errors
70 | }
71 |
72 | func (o *KubeletClientOptions) AddFlags(fs *pflag.FlagSet) {
73 | fs.BoolVar(&o.InsecureKubeletTLS, "kubelet-insecure-tls", o.InsecureKubeletTLS, "Do not verify CA of serving certificates presented by Kubelets. For testing purposes only.")
74 | fs.BoolVar(&o.KubeletUseNodeStatusPort, "kubelet-use-node-status-port", o.KubeletUseNodeStatusPort, "Use the port in the node status. Takes precedence over --kubelet-port flag.")
75 | fs.IntVar(&o.KubeletPort, "kubelet-port", o.KubeletPort, "The port to use to connect to Kubelets.")
76 | fs.StringSliceVar(&o.KubeletPreferredAddressTypes, "kubelet-preferred-address-types", o.KubeletPreferredAddressTypes, "The priority of node address types to use when determining which address to use to connect to a particular node")
77 | fs.StringVar(&o.KubeletCAFile, "kubelet-certificate-authority", "", "Path to the CA to use to validate the Kubelet's serving certificates.")
78 | fs.StringVar(&o.KubeletClientKeyFile, "kubelet-client-key", "", "Path to a client key file for TLS.")
79 | fs.StringVar(&o.KubeletClientCertFile, "kubelet-client-certificate", "", "Path to a client cert file for TLS.")
80 | fs.DurationVar(&o.KubeletRequestTimeout, "kubelet-request-timeout", o.KubeletRequestTimeout, "The length of time to wait before giving up on a single request to Kubelet. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
81 | fs.StringVarP(&o.NodeSelector, "node-selector", "l", o.NodeSelector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2).")
82 | // MarkDeprecated hides the flag from the help. We don't want that.
83 | fs.BoolVar(&o.DeprecatedCompletelyInsecureKubelet, "deprecated-kubelet-completely-insecure", o.DeprecatedCompletelyInsecureKubelet, "DEPRECATED: Do not use any encryption, authorization, or authentication when communicating with the Kubelet. This is rarely the right option, since it leaves kubelet communication completely insecure. If you encounter auth errors, make sure you've enabled token webhook auth on the Kubelet, and if you're in a test cluster with self-signed Kubelet certificates, consider using kubelet-insecure-tls instead.")
84 | }
85 |
86 | // NewKubeletClientOptions constructs a new set of default options for metrics-server.
87 | func NewKubeletClientOptions() *KubeletClientOptions {
88 | o := &KubeletClientOptions{
89 | KubeletPort: 10250,
90 | KubeletPreferredAddressTypes: make([]string, len(utils.DefaultAddressTypePriority)),
91 | KubeletRequestTimeout: 10 * time.Second,
92 | }
93 |
94 | for i, addrType := range utils.DefaultAddressTypePriority {
95 | o.KubeletPreferredAddressTypes[i] = string(addrType)
96 | }
97 |
98 | return o
99 | }
100 |
101 | func (o KubeletClientOptions) Config(restConfig *rest.Config) *client.KubeletClientConfig {
102 | config := &client.KubeletClientConfig{
103 | Scheme: "https",
104 | DefaultPort: o.KubeletPort,
105 | AddressTypePriority: o.addressResolverConfig(),
106 | UseNodeStatusPort: o.KubeletUseNodeStatusPort,
107 | Client: *rest.CopyConfig(restConfig),
108 | }
109 | if o.DeprecatedCompletelyInsecureKubelet {
110 | config.Scheme = "http"
111 | config.Client = *rest.AnonymousClientConfig(&config.Client) // don't use auth to avoid leaking auth details to insecure endpoints
112 | config.Client.TLSClientConfig = rest.TLSClientConfig{} // empty TLS config --> no TLS
113 | }
114 | if o.InsecureKubeletTLS {
115 | config.Client.TLSClientConfig.Insecure = true
116 | config.Client.TLSClientConfig.CAData = nil
117 | config.Client.TLSClientConfig.CAFile = ""
118 | }
119 | if len(o.KubeletCAFile) > 0 {
120 | config.Client.TLSClientConfig.CAFile = o.KubeletCAFile
121 | config.Client.TLSClientConfig.CAData = nil
122 | }
123 | if len(o.KubeletClientCertFile) > 0 {
124 | config.Client.TLSClientConfig.CertFile = o.KubeletClientCertFile
125 | config.Client.TLSClientConfig.CertData = nil
126 | }
127 | if len(o.KubeletClientKeyFile) > 0 {
128 | config.Client.TLSClientConfig.KeyFile = o.KubeletClientKeyFile
129 | config.Client.TLSClientConfig.KeyData = nil
130 | }
131 | return config
132 | }
133 |
134 | func (o KubeletClientOptions) addressResolverConfig() []corev1.NodeAddressType {
135 | addrPriority := make([]corev1.NodeAddressType, len(o.KubeletPreferredAddressTypes))
136 | for i, addrType := range o.KubeletPreferredAddressTypes {
137 | addrPriority[i] = corev1.NodeAddressType(addrType)
138 | }
139 | return addrPriority
140 | }
141 |
--------------------------------------------------------------------------------
/cmd/metrics-server/app/options/options_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | package options
15 |
16 | import (
17 | "testing"
18 | "time"
19 |
20 | "k8s.io/component-base/logs"
21 | )
22 |
23 | func TestOptions_validate(t *testing.T) {
24 | for _, tc := range []struct {
25 | name string
26 | options *Options
27 | expectedErrorCount int
28 | }{
29 | {
30 | name: "can give --metric-resolution larger than --kubelet-request-timeout",
31 | options: &Options{
32 | MetricResolution: 10 * time.Second,
33 | KubeletClient: &KubeletClientOptions{KubeletRequestTimeout: 9 * time.Second},
34 | Logging: logs.NewOptions(),
35 | },
36 | expectedErrorCount: 0,
37 | },
38 | {
39 | name: "can not give --metric-resolution * 9/10 less than --kubelet-request-timeout",
40 | options: &Options{
41 | MetricResolution: 10 * time.Second,
42 | KubeletClient: &KubeletClientOptions{KubeletRequestTimeout: 10 * time.Second},
43 | Logging: logs.NewOptions(),
44 | },
45 | expectedErrorCount: 1,
46 | },
47 | } {
48 | t.Run(tc.name, func(t *testing.T) {
49 | errors := tc.options.validate()
50 | if len(errors) != tc.expectedErrorCount {
51 | t.Errorf("options.Validate() = %q, expected length %d", errors, tc.expectedErrorCount)
52 | }
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/cmd/metrics-server/app/start.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package app
16 |
17 | import (
18 | "flag"
19 | "fmt"
20 | "os"
21 |
22 | "github.com/spf13/cobra"
23 |
24 | "k8s.io/client-go/pkg/version"
25 | cliflag "k8s.io/component-base/cli/flag"
26 | "k8s.io/component-base/logs"
27 | "k8s.io/component-base/term"
28 |
29 | "sigs.k8s.io/metrics-server/cmd/metrics-server/app/options"
30 | )
31 |
32 | // NewMetricsServerCommand provides a CLI handler for the metrics server entrypoint
33 | func NewMetricsServerCommand(stopCh <-chan struct{}) *cobra.Command {
34 | opts := options.NewOptions()
35 | cmd := &cobra.Command{
36 | Short: "Launch metrics-server",
37 | Long: "Launch metrics-server",
38 | RunE: func(c *cobra.Command, args []string) error {
39 | if err := runCommand(opts, stopCh); err != nil {
40 | return err
41 | }
42 | return nil
43 | },
44 | }
45 | fs := cmd.Flags()
46 | nfs := opts.Flags()
47 | for _, f := range nfs.FlagSets {
48 | fs.AddFlagSet(f)
49 | }
50 | local := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
51 | logs.AddGoFlags(local)
52 | nfs.FlagSet("logging").AddGoFlagSet(local)
53 |
54 | usageFmt := "Usage:\n %s\n"
55 | cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
56 | cmd.SetUsageFunc(func(cmd *cobra.Command) error {
57 | fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine())
58 | cliflag.PrintSections(cmd.OutOrStderr(), nfs, cols)
59 | return nil
60 | })
61 | cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
62 | fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
63 | cliflag.PrintSections(cmd.OutOrStdout(), nfs, cols)
64 | })
65 | fs.AddGoFlagSet(local)
66 | return cmd
67 | }
68 |
69 | func runCommand(o *options.Options, stopCh <-chan struct{}) error {
70 | if o.ShowVersion {
71 | fmt.Println(version.Get().GitVersion)
72 | os.Exit(0)
73 | }
74 |
75 | errors := o.Validate()
76 | if len(errors) > 0 {
77 | return errors[0]
78 | }
79 |
80 | config, err := o.ServerConfig()
81 |
82 | if err != nil {
83 | return err
84 | }
85 |
86 | s, err := config.Complete()
87 |
88 | if err != nil {
89 | return err
90 | }
91 |
92 | return s.RunUntil(stopCh)
93 | }
94 |
--------------------------------------------------------------------------------
/cmd/metrics-server/metrics-server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "os"
19 | "runtime"
20 |
21 | genericapiserver "k8s.io/apiserver/pkg/server"
22 | "k8s.io/component-base/logs"
23 |
24 | "sigs.k8s.io/metrics-server/cmd/metrics-server/app"
25 | )
26 |
27 | func main() {
28 | logs.InitLogs()
29 | defer logs.FlushLogs()
30 |
31 | if len(os.Getenv("GOMAXPROCS")) == 0 {
32 | runtime.GOMAXPROCS(runtime.NumCPU())
33 | }
34 |
35 | cmd := app.NewMetricsServerCommand(genericapiserver.SetupSignalHandler())
36 | if err := cmd.Execute(); err != nil {
37 | panic(err)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/code-of-conduct.md:
--------------------------------------------------------------------------------
1 | # Kubernetes Community Code of Conduct
2 |
3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
4 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module sigs.k8s.io/metrics-server
2 |
3 | go 1.24.2
4 |
5 | require (
6 | github.com/google/go-cmp v0.7.0
7 | github.com/onsi/ginkgo/v2 v2.21.0
8 | github.com/onsi/gomega v1.35.1
9 | github.com/prometheus/common v0.63.0
10 | github.com/prometheus/prometheus v0.303.1
11 | github.com/spf13/cobra v1.8.1
12 | github.com/spf13/pflag v1.0.5
13 | k8s.io/api v0.33.0
14 | k8s.io/apimachinery v0.33.0
15 | k8s.io/apiserver v0.33.0
16 | k8s.io/client-go v0.33.0
17 | k8s.io/component-base v0.33.0
18 | k8s.io/klog/v2 v2.130.1
19 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
20 | k8s.io/metrics v0.33.0
21 | )
22 |
23 | require (
24 | cel.dev/expr v0.19.1 // indirect
25 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
26 | github.com/NYTimes/gziphandler v1.1.1 // indirect
27 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
28 | github.com/beorn7/perks v1.0.1 // indirect
29 | github.com/blang/semver/v4 v4.0.0 // indirect
30 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect
31 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
32 | github.com/coreos/go-semver v0.3.1 // indirect
33 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect
34 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
35 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect
36 | github.com/felixge/httpsnoop v1.0.4 // indirect
37 | github.com/fsnotify/fsnotify v1.8.0 // indirect
38 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
39 | github.com/go-logr/logr v1.4.2 // indirect
40 | github.com/go-logr/stdr v1.2.2 // indirect
41 | github.com/go-logr/zapr v1.3.0 // indirect
42 | github.com/go-openapi/jsonpointer v0.21.0 // indirect
43 | github.com/go-openapi/jsonreference v0.21.0 // indirect
44 | github.com/go-openapi/swag v0.23.0 // indirect
45 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
46 | github.com/gogo/protobuf v1.3.2 // indirect
47 | github.com/golang/protobuf v1.5.4 // indirect
48 | github.com/google/btree v1.1.3 // indirect
49 | github.com/google/cel-go v0.23.2 // indirect
50 | github.com/google/gnostic-models v0.6.9 // indirect
51 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
52 | github.com/google/uuid v1.6.0 // indirect
53 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
54 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
55 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
56 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
57 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
58 | github.com/josharian/intern v1.0.0 // indirect
59 | github.com/json-iterator/go v1.1.12 // indirect
60 | github.com/kylelemons/godebug v1.1.0 // indirect
61 | github.com/mailru/easyjson v0.7.7 // indirect
62 | github.com/moby/spdystream v0.5.0 // indirect
63 | github.com/moby/term v0.5.0 // indirect
64 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
65 | github.com/modern-go/reflect2 v1.0.2 // indirect
66 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
67 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
68 | github.com/pkg/errors v0.9.1 // indirect
69 | github.com/prometheus/client_golang v1.22.0 // indirect
70 | github.com/prometheus/client_model v0.6.1 // indirect
71 | github.com/prometheus/procfs v0.15.1 // indirect
72 | github.com/stoewer/go-strcase v1.3.0 // indirect
73 | github.com/x448/float16 v0.8.4 // indirect
74 | go.etcd.io/etcd/api/v3 v3.5.21 // indirect
75 | go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
76 | go.etcd.io/etcd/client/v3 v3.5.21 // indirect
77 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
78 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
79 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
80 | go.opentelemetry.io/otel v1.35.0 // indirect
81 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
82 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
83 | go.opentelemetry.io/otel/metric v1.35.0 // indirect
84 | go.opentelemetry.io/otel/sdk v1.35.0 // indirect
85 | go.opentelemetry.io/otel/trace v1.35.0 // indirect
86 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect
87 | go.uber.org/multierr v1.11.0 // indirect
88 | go.uber.org/zap v1.27.0 // indirect
89 | golang.org/x/crypto v0.36.0 // indirect
90 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
91 | golang.org/x/net v0.38.0 // indirect
92 | golang.org/x/oauth2 v0.27.0 // indirect
93 | golang.org/x/sync v0.12.0 // indirect
94 | golang.org/x/sys v0.31.0 // indirect
95 | golang.org/x/term v0.30.0 // indirect
96 | golang.org/x/text v0.23.0 // indirect
97 | golang.org/x/time v0.10.0 // indirect
98 | golang.org/x/tools v0.30.0 // indirect
99 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
100 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e // indirect
101 | google.golang.org/grpc v1.71.0 // indirect
102 | google.golang.org/protobuf v1.36.5 // indirect
103 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
104 | gopkg.in/inf.v0 v0.9.1 // indirect
105 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
106 | gopkg.in/yaml.v3 v3.0.1 // indirect
107 | k8s.io/kms v0.33.0 // indirect
108 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
109 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
110 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
111 | sigs.k8s.io/randfill v1.0.0 // indirect
112 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
113 | sigs.k8s.io/yaml v1.4.0 // indirect
114 | )
115 |
--------------------------------------------------------------------------------
/manifests/base/apiservice.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apiregistration.k8s.io/v1
3 | kind: APIService
4 | metadata:
5 | name: v1beta1.metrics.k8s.io
6 | spec:
7 | service:
8 | name: metrics-server
9 | namespace: kube-system
10 | group: metrics.k8s.io
11 | version: v1beta1
12 | insecureSkipTLSVerify: true
13 | groupPriorityMinimum: 100
14 | versionPriority: 100
15 |
--------------------------------------------------------------------------------
/manifests/base/deployment.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: metrics-server
6 | namespace: kube-system
7 | spec:
8 | strategy:
9 | rollingUpdate:
10 | maxUnavailable: 0
11 | template:
12 | spec:
13 | serviceAccountName: metrics-server
14 | volumes:
15 | # mount in tmp so we can safely use from-scratch images and/or read-only containers
16 | - name: tmp-dir
17 | emptyDir: {}
18 | priorityClassName: system-cluster-critical
19 | containers:
20 | - name: metrics-server
21 | image: gcr.io/k8s-staging-metrics-server/metrics-server:master
22 | imagePullPolicy: IfNotPresent
23 | args:
24 | - --cert-dir=/tmp
25 | - --secure-port=10250
26 | - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
27 | - --kubelet-use-node-status-port
28 | - --metric-resolution=15s
29 | resources:
30 | requests:
31 | cpu: 100m
32 | memory: 200Mi
33 | ports:
34 | - name: https
35 | containerPort: 10250
36 | protocol: TCP
37 | readinessProbe:
38 | httpGet:
39 | path: /readyz
40 | port: https
41 | scheme: HTTPS
42 | periodSeconds: 10
43 | failureThreshold: 3
44 | initialDelaySeconds: 20
45 | livenessProbe:
46 | httpGet:
47 | path: /livez
48 | port: https
49 | scheme: HTTPS
50 | periodSeconds: 10
51 | failureThreshold: 3
52 | securityContext:
53 | readOnlyRootFilesystem: true
54 | runAsNonRoot: true
55 | runAsUser: 1000
56 | allowPrivilegeEscalation: false
57 | seccompProfile:
58 | type: RuntimeDefault
59 | capabilities:
60 | drop:
61 | - ALL
62 | volumeMounts:
63 | - name: tmp-dir
64 | mountPath: /tmp
65 | nodeSelector:
66 | kubernetes.io/os: linux
67 |
--------------------------------------------------------------------------------
/manifests/base/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | commonLabels:
4 | k8s-app: metrics-server
5 | resources:
6 | - apiservice.yaml
7 | - deployment.yaml
8 | - rbac.yaml
9 | - service.yaml
--------------------------------------------------------------------------------
/manifests/base/rbac.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: system:aggregated-metrics-reader
6 | labels:
7 | rbac.authorization.k8s.io/aggregate-to-view: "true"
8 | rbac.authorization.k8s.io/aggregate-to-edit: "true"
9 | rbac.authorization.k8s.io/aggregate-to-admin: "true"
10 | rules:
11 | - apiGroups: ["metrics.k8s.io"]
12 | resources: ["pods", "nodes"]
13 | verbs: ["get", "list", "watch"]
14 | ---
15 | apiVersion: v1
16 | kind: ServiceAccount
17 | metadata:
18 | name: metrics-server
19 | namespace: kube-system
20 | ---
21 | apiVersion: rbac.authorization.k8s.io/v1
22 | kind: RoleBinding
23 | metadata:
24 | name: metrics-server-auth-reader
25 | namespace: kube-system
26 | roleRef:
27 | apiGroup: rbac.authorization.k8s.io
28 | kind: Role
29 | name: extension-apiserver-authentication-reader
30 | subjects:
31 | - kind: ServiceAccount
32 | name: metrics-server
33 | namespace: kube-system
34 | ---
35 | apiVersion: rbac.authorization.k8s.io/v1
36 | kind: ClusterRoleBinding
37 | metadata:
38 | name: metrics-server:system:auth-delegator
39 | roleRef:
40 | apiGroup: rbac.authorization.k8s.io
41 | kind: ClusterRole
42 | name: system:auth-delegator
43 | subjects:
44 | - kind: ServiceAccount
45 | name: metrics-server
46 | namespace: kube-system
47 | ---
48 | apiVersion: rbac.authorization.k8s.io/v1
49 | kind: ClusterRole
50 | metadata:
51 | name: system:metrics-server
52 | rules:
53 | - apiGroups: [""]
54 | resources:
55 | - nodes/metrics
56 | verbs:
57 | - get
58 | - apiGroups: [""]
59 | resources:
60 | - pods
61 | - nodes
62 | verbs:
63 | - get
64 | - list
65 | - watch
66 | ---
67 | apiVersion: rbac.authorization.k8s.io/v1
68 | kind: ClusterRoleBinding
69 | metadata:
70 | name: system:metrics-server
71 | roleRef:
72 | apiGroup: rbac.authorization.k8s.io
73 | kind: ClusterRole
74 | name: system:metrics-server
75 | subjects:
76 | - kind: ServiceAccount
77 | name: metrics-server
78 | namespace: kube-system
79 |
--------------------------------------------------------------------------------
/manifests/base/service.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: metrics-server
6 | namespace: kube-system
7 | spec:
8 | ports:
9 | - name: https
10 | port: 443
11 | protocol: TCP
12 | targetPort: https
13 | appProtocol: https
14 |
--------------------------------------------------------------------------------
/manifests/components/autoscale/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1alpha1
2 | kind: Component
3 | resources:
4 | - rbac.yaml
5 | patchesStrategicMerge:
6 | - patch.yaml
7 |
--------------------------------------------------------------------------------
/manifests/components/autoscale/patch.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: metrics-server
5 | namespace: kube-system
6 | spec:
7 | template:
8 | spec:
9 | containers:
10 | - name: metrics-server
11 | # Resources are set by the nanny sidecar
12 | resources:
13 | $patch: delete
14 | - name: metrics-server-nanny
15 | securityContext:
16 | allowPrivilegeEscalation: false
17 | readOnlyRootFilesystem: true
18 | runAsNonRoot: true
19 | runAsUser: 1000
20 | seccompProfile:
21 | type: RuntimeDefault
22 | capabilities:
23 | drop:
24 | - ALL
25 | image: registry.k8s.io/autoscaling/addon-resizer:1.8.23
26 | resources:
27 | limits:
28 | cpu: 40m
29 | memory: 25Mi
30 | requests:
31 | cpu: 40m
32 | memory: 25Mi
33 | env:
34 | - name: MY_POD_NAME
35 | valueFrom:
36 | fieldRef:
37 | fieldPath: metadata.name
38 | - name: MY_POD_NAMESPACE
39 | valueFrom:
40 | fieldRef:
41 | fieldPath: metadata.namespace
42 | command:
43 | - /pod_nanny
44 | - --cpu=0m
45 | - --extra-cpu=1m
46 | - --memory=0Mi
47 | - --extra-memory=2Mi
48 | - --threshold=5
49 | - --deployment=metrics-server
50 | - --container=metrics-server
51 | - --poll-period=300000
52 | - --estimator=exponential
53 | - --minClusterSize=100
54 | - --use-metrics=true
55 |
--------------------------------------------------------------------------------
/manifests/components/autoscale/rbac.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: system:metrics-server-nanny
6 | labels:
7 | k8s-app: metrics-server
8 | rules:
9 | - nonResourceURLs:
10 | - /metrics
11 | verbs:
12 | - get
13 | ---
14 | apiVersion: rbac.authorization.k8s.io/v1
15 | kind: ClusterRoleBinding
16 | metadata:
17 | name: system:metrics-server-nanny
18 | labels:
19 | k8s-app: metrics-server
20 | roleRef:
21 | apiGroup: rbac.authorization.k8s.io
22 | kind: ClusterRole
23 | name: system:metrics-server-nanny
24 | subjects:
25 | - kind: ServiceAccount
26 | name: metrics-server
27 | namespace: kube-system
28 | ---
29 | apiVersion: rbac.authorization.k8s.io/v1
30 | kind: Role
31 | metadata:
32 | name: metrics-server-nanny
33 | namespace: kube-system
34 | labels:
35 | k8s-app: metrics-server
36 | rules:
37 | - apiGroups:
38 | - ""
39 | resources:
40 | - pods
41 | verbs:
42 | - get
43 | - apiGroups:
44 | - apps
45 | resources:
46 | - deployments
47 | resourceNames:
48 | - metrics-server
49 | verbs:
50 | - get
51 | - patch
52 | ---
53 | apiVersion: rbac.authorization.k8s.io/v1
54 | kind: RoleBinding
55 | metadata:
56 | name: metrics-server-nanny
57 | namespace: kube-system
58 | labels:
59 | k8s-app: metrics-server
60 | roleRef:
61 | apiGroup: rbac.authorization.k8s.io
62 | kind: Role
63 | name: metrics-server-nanny
64 | subjects:
65 | - kind: ServiceAccount
66 | name: metrics-server
67 | namespace: kube-system
68 |
--------------------------------------------------------------------------------
/manifests/components/high-availability-1.21+/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1alpha1
2 | kind: Component
3 | components:
4 | - ../high-availability
5 | patches:
6 | - path: patch-pdb-version.yaml
7 | target:
8 | kind: PodDisruptionBudget
9 |
--------------------------------------------------------------------------------
/manifests/components/high-availability-1.21+/patch-pdb-version.yaml:
--------------------------------------------------------------------------------
1 | - op: replace
2 | path: "/apiVersion"
3 | value: policy/v1
4 |
--------------------------------------------------------------------------------
/manifests/components/high-availability/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1alpha1
2 | kind: Component
3 | resources:
4 | - pdb.yaml
5 | patchesStrategicMerge:
6 | - patch.yaml
7 |
--------------------------------------------------------------------------------
/manifests/components/high-availability/patch.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: metrics-server
6 | namespace: kube-system
7 | spec:
8 | replicas: 2
9 | strategy:
10 | rollingUpdate:
11 | maxUnavailable: 1
12 | template:
13 | spec:
14 | affinity:
15 | podAntiAffinity:
16 | requiredDuringSchedulingIgnoredDuringExecution:
17 | - labelSelector:
18 | matchLabels:
19 | k8s-app: metrics-server
20 | namespaces:
21 | - kube-system
22 | topologyKey: kubernetes.io/hostname
23 |
--------------------------------------------------------------------------------
/manifests/components/high-availability/pdb.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: policy/v1beta1
3 | kind: PodDisruptionBudget
4 | metadata:
5 | name: metrics-server
6 | namespace: kube-system
7 | labels:
8 | k8s-app: metrics-server
9 | spec:
10 | minAvailable: 1
11 | selector:
12 | matchLabels:
13 | k8s-app: metrics-server
14 |
--------------------------------------------------------------------------------
/manifests/components/release/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1alpha1
2 | kind: Component
3 | images:
4 | - name: gcr.io/k8s-staging-metrics-server/metrics-server
5 | newName: registry.k8s.io/metrics-server/metrics-server
6 | newTag: v0.7.0
7 |
--------------------------------------------------------------------------------
/manifests/components/test/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1alpha1
2 | kind: Component
3 | patchesJson6902:
4 | - target:
5 | group: apps
6 | version: v1
7 | kind: Deployment
8 | name: metrics-server
9 | namespace: kube-system
10 | patch: |
11 | - op: add
12 | path: /spec/template/spec/containers/0/args/-
13 | value: --kubelet-insecure-tls
14 | - op: add
15 | path: /spec/template/spec/containers/0/args/-
16 | value: --v=4
17 | - op: add
18 | path: /spec/template/spec/containers/0/imagePullPolicy
19 | value: Never
20 | - op: add
21 | path: /spec/template/spec/containers/0/args/-
22 | value: --node-selector=metrics-server-skip!=true
23 |
--------------------------------------------------------------------------------
/manifests/overlays/release-ha-1.21+/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | resources:
4 | - ../../base
5 | components:
6 | - ../../components/high-availability-1.21+
7 | - ../../components/release
8 |
--------------------------------------------------------------------------------
/manifests/overlays/release-ha/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | resources:
4 | - ../../base
5 | components:
6 | - ../../components/high-availability
7 | - ../../components/release
8 |
--------------------------------------------------------------------------------
/manifests/overlays/release/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | resources:
4 | - ../../base
5 | components:
6 | - ../../components/release
7 |
--------------------------------------------------------------------------------
/manifests/overlays/test-ha/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | resources:
4 | - ../../base
5 | components:
6 | - ../../components/high-availability-1.21+
7 | - ../../components/test
8 |
--------------------------------------------------------------------------------
/manifests/overlays/test/kustomization.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kustomize.config.k8s.io/v1beta1
2 | kind: Kustomization
3 | resources:
4 | - ../../base
5 | components:
6 | - ../../components/test
7 |
--------------------------------------------------------------------------------
/pkg/api/filter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package api
16 |
17 | import (
18 | v1 "k8s.io/api/core/v1"
19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20 | "k8s.io/apimachinery/pkg/fields"
21 | "k8s.io/apimachinery/pkg/runtime"
22 | "k8s.io/apiserver/pkg/registry/generic"
23 | )
24 |
25 | func filterNodes(nodes []*v1.Node, selector fields.Selector) []*v1.Node {
26 | newNodes := make([]*v1.Node, 0, len(nodes))
27 | fields := make(fields.Set, 2)
28 | for _, node := range nodes {
29 | for k := range fields {
30 | delete(fields, k)
31 | }
32 | fieldsSet := generic.AddObjectMetaFieldsSet(fields, &node.ObjectMeta, false)
33 | if !selector.Matches(fieldsSet) {
34 | continue
35 | }
36 | newNodes = append(newNodes, node)
37 | }
38 | return newNodes
39 | }
40 |
41 | func filterPartialObjectMetadata(objs []runtime.Object, selector fields.Selector) []runtime.Object {
42 | newObjs := make([]runtime.Object, 0, len(objs))
43 | fields := make(fields.Set, 2)
44 | for _, obj := range objs {
45 | for k := range fields {
46 | delete(fields, k)
47 | }
48 | fieldsSet := generic.AddObjectMetaFieldsSet(fields, &obj.(*metav1.PartialObjectMetadata).ObjectMeta, true)
49 | if !selector.Matches(fieldsSet) {
50 | continue
51 | }
52 | newObjs = append(newObjs, obj)
53 | }
54 | return newObjs
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/api/generated/openapi/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // package openapi contains autogenerated openapi schema definitions
16 | // for the metrics.k8s.io API.
17 | package openapi
18 |
--------------------------------------------------------------------------------
/pkg/api/install.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package api
16 |
17 | import (
18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 | "k8s.io/apimachinery/pkg/labels"
20 | "k8s.io/apimachinery/pkg/runtime"
21 | "k8s.io/apimachinery/pkg/runtime/schema"
22 | "k8s.io/apimachinery/pkg/runtime/serializer"
23 | "k8s.io/apiserver/pkg/registry/rest"
24 | genericapiserver "k8s.io/apiserver/pkg/server"
25 | corev1 "k8s.io/client-go/listers/core/v1"
26 | "k8s.io/client-go/tools/cache"
27 | "k8s.io/metrics/pkg/apis/metrics"
28 | "k8s.io/metrics/pkg/apis/metrics/install"
29 | "k8s.io/metrics/pkg/apis/metrics/v1beta1"
30 | )
31 |
32 | var (
33 | // Scheme contains the types needed by the resource metrics API.
34 | Scheme = runtime.NewScheme()
35 | // Codecs is a codec factory for serving the resource metrics API.
36 | Codecs = serializer.NewCodecFactory(Scheme)
37 | )
38 |
39 | func init() {
40 | install.Install(Scheme)
41 | metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
42 | }
43 |
44 | // Build constructs APIGroupInfo the metrics.k8s.io API group using the given getters.
45 | func Build(pod, node rest.Storage) genericapiserver.APIGroupInfo {
46 | apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(metrics.GroupName, Scheme, metav1.ParameterCodec, Codecs)
47 | metricsServerResources := map[string]rest.Storage{
48 | "nodes": node,
49 | "pods": pod,
50 | }
51 | apiGroupInfo.VersionedResourcesStorageMap[v1beta1.SchemeGroupVersion.Version] = metricsServerResources
52 |
53 | return apiGroupInfo
54 | }
55 |
56 | // Install builds the metrics for the metrics.k8s.io API, and then installs it into the given API metrics-server.
57 | func Install(m MetricsGetter, podMetadataLister cache.GenericLister, nodeLister corev1.NodeLister, server *genericapiserver.GenericAPIServer, nodeSelector []labels.Requirement) error {
58 | node := newNodeMetrics(metrics.Resource("nodemetrics"), m, nodeLister, nodeSelector)
59 | pod := newPodMetrics(metrics.Resource("podmetrics"), m, podMetadataLister)
60 | info := Build(pod, node)
61 | return server.InstallAPIGroup(&info)
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/api/interfaces.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package api
16 |
17 | import (
18 | "time"
19 |
20 | corev1 "k8s.io/api/core/v1"
21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22 | "k8s.io/metrics/pkg/apis/metrics"
23 | )
24 |
25 | // MetricsGetter is both a PodMetricsGetter and a NodeMetricsGetter
26 | type MetricsGetter interface {
27 | PodMetricsGetter
28 | NodeMetricsGetter
29 | }
30 |
31 | // TimeInfo represents the timing information for a metric, which was
32 | // potentially calculated over some window of time (e.g. for CPU usage rate).
33 | type TimeInfo struct {
34 | // NB: we consider the earliest timestamp amongst multiple containers
35 | // for the purposes of determining if a metric is tained by a time
36 | // period, like pod startup (used by things like the HPA).
37 |
38 | // Timestamp is the time at which the metrics were initially collected.
39 | // In the case of a rate metric, it should be the timestamp of the last
40 | // data point used in the calculation. If it represents multiple metric
41 | // points, it should be the earliest such timestamp from all of the points.
42 | Timestamp time.Time
43 |
44 | // Window represents the window used to calculate rate metrics associated
45 | // with this timestamp.
46 | Window time.Duration
47 | }
48 |
49 | // PodMetricsGetter knows how to fetch metrics for the containers in a pod.
50 | type PodMetricsGetter interface {
51 | // GetPodMetrics gets the latest metrics for all containers in each listed pod,
52 | // returning both the metrics and the associated collection timestamp.
53 | GetPodMetrics(pods ...*metav1.PartialObjectMetadata) ([]metrics.PodMetrics, error)
54 | }
55 |
56 | // NodeMetricsGetter knows how to fetch metrics for a node.
57 | type NodeMetricsGetter interface {
58 | // GetNodeMetrics gets the latest metrics for the given nodes,
59 | // returning both the metrics and the associated collection timestamp.
60 | GetNodeMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error)
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/api/monitoring.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package api
16 |
17 | import (
18 | "k8s.io/component-base/metrics"
19 | )
20 |
21 | var (
22 | metricFreshness = metrics.NewHistogramVec(
23 | &metrics.HistogramOpts{
24 | Namespace: "metrics_server",
25 | Subsystem: "api",
26 | Name: "metric_freshness_seconds",
27 | Help: "Freshness of metrics exported",
28 | Buckets: metrics.ExponentialBuckets(1, 1.364, 20),
29 | },
30 | []string{},
31 | )
32 | )
33 |
34 | // RegisterAPIMetrics registers a histogram metric for the freshness of
35 | // exported metrics.
36 | func RegisterAPIMetrics(registrationFunc func(metrics.Registerable) error) error {
37 | return registrationFunc(metricFreshness)
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/api/node.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package api
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "sort"
21 |
22 | corev1 "k8s.io/api/core/v1"
23 | "k8s.io/apimachinery/pkg/api/errors"
24 | metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
27 | "k8s.io/apimachinery/pkg/labels"
28 | "k8s.io/apimachinery/pkg/runtime"
29 | "k8s.io/apimachinery/pkg/runtime/schema"
30 | "k8s.io/apiserver/pkg/registry/rest"
31 | v1listers "k8s.io/client-go/listers/core/v1"
32 | "k8s.io/klog/v2"
33 | "k8s.io/metrics/pkg/apis/metrics"
34 | _ "k8s.io/metrics/pkg/apis/metrics/install"
35 | )
36 |
37 | type nodeMetrics struct {
38 | groupResource schema.GroupResource
39 | metrics NodeMetricsGetter
40 | nodeLister v1listers.NodeLister
41 | nodeSelector []labels.Requirement
42 | }
43 |
44 | var _ rest.KindProvider = &nodeMetrics{}
45 | var _ rest.Storage = &nodeMetrics{}
46 | var _ rest.Getter = &nodeMetrics{}
47 | var _ rest.Lister = &nodeMetrics{}
48 | var _ rest.Scoper = &nodeMetrics{}
49 | var _ rest.TableConvertor = &nodeMetrics{}
50 | var _ rest.SingularNameProvider = &nodeMetrics{}
51 |
52 | func newNodeMetrics(groupResource schema.GroupResource, metrics NodeMetricsGetter, nodeLister v1listers.NodeLister, nodeSelector []labels.Requirement) *nodeMetrics {
53 | return &nodeMetrics{
54 | groupResource: groupResource,
55 | metrics: metrics,
56 | nodeLister: nodeLister,
57 | nodeSelector: nodeSelector,
58 | }
59 | }
60 |
61 | // New implements rest.Storage interface
62 | func (m *nodeMetrics) New() runtime.Object {
63 | return &metrics.NodeMetrics{}
64 | }
65 |
66 | // Destroy implements rest.Storage interface
67 | func (m *nodeMetrics) Destroy() {
68 | }
69 |
70 | // Kind implements rest.KindProvider interface
71 | func (m *nodeMetrics) Kind() string {
72 | return "NodeMetrics"
73 | }
74 |
75 | // NewList implements rest.Lister interface
76 | func (m *nodeMetrics) NewList() runtime.Object {
77 | return &metrics.NodeMetricsList{}
78 | }
79 |
80 | // List implements rest.Lister interface
81 | func (m *nodeMetrics) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
82 | nodes, err := m.nodes(ctx, options)
83 | if err != nil {
84 | return &metrics.NodeMetricsList{}, err
85 | }
86 |
87 | ms, err := m.getMetrics(nodes...)
88 | if err != nil {
89 | klog.ErrorS(err, "Failed reading nodes metrics")
90 | return &metrics.NodeMetricsList{}, fmt.Errorf("failed reading nodes metrics: %w", err)
91 | }
92 | return &metrics.NodeMetricsList{Items: ms}, nil
93 | }
94 |
95 | func (m *nodeMetrics) nodes(ctx context.Context, options *metainternalversion.ListOptions) ([]*corev1.Node, error) {
96 | labelSelector := labels.Everything()
97 | if options != nil && options.LabelSelector != nil {
98 | labelSelector = options.LabelSelector
99 | }
100 | if m.nodeSelector != nil {
101 | labelSelector = labelSelector.Add(m.nodeSelector...)
102 | }
103 | nodes, err := m.nodeLister.List(labelSelector)
104 | if err != nil {
105 | klog.ErrorS(err, "Failed listing nodes", "labelSelector", labelSelector)
106 | return nil, fmt.Errorf("failed listing nodes: %w", err)
107 | }
108 | if options != nil && options.FieldSelector != nil {
109 | nodes = filterNodes(nodes, options.FieldSelector)
110 | }
111 | return nodes, nil
112 | }
113 |
114 | // Get implements rest.Getter interface
115 | func (m *nodeMetrics) Get(ctx context.Context, name string, opts *metav1.GetOptions) (runtime.Object, error) {
116 | node, err := m.nodeLister.Get(name)
117 | if err != nil {
118 | if errors.IsNotFound(err) {
119 | // return not-found errors directly
120 | return nil, err
121 | }
122 | klog.ErrorS(err, "Failed getting node", "node", klog.KRef("", name))
123 | return nil, fmt.Errorf("failed getting node: %w", err)
124 | }
125 | if node == nil {
126 | return nil, errors.NewNotFound(m.groupResource, name)
127 | }
128 | ms, err := m.getMetrics(node)
129 | if err != nil {
130 | klog.ErrorS(err, "Failed reading node metrics", "node", klog.KRef("", name))
131 | return nil, fmt.Errorf("failed reading node metrics: %w", err)
132 | }
133 | if len(ms) == 0 {
134 | return nil, errors.NewNotFound(m.groupResource, name)
135 | }
136 | return &ms[0], nil
137 | }
138 |
139 | // ConvertToTable implements rest.TableConvertor interface
140 | func (m *nodeMetrics) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
141 | var table metav1beta1.Table
142 |
143 | switch t := object.(type) {
144 | case *metrics.NodeMetrics:
145 | table.ResourceVersion = t.ResourceVersion
146 | table.SelfLink = t.SelfLink //nolint:staticcheck // keep deprecated field to be backward compatible
147 | addNodeMetricsToTable(&table, *t)
148 | case *metrics.NodeMetricsList:
149 | table.ResourceVersion = t.ResourceVersion
150 | table.SelfLink = t.SelfLink //nolint:staticcheck // keep deprecated field to be backward compatible
151 | table.Continue = t.Continue
152 | addNodeMetricsToTable(&table, t.Items...)
153 | default:
154 | }
155 |
156 | return &table, nil
157 | }
158 |
159 | func (m *nodeMetrics) getMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error) {
160 | ms, err := m.metrics.GetNodeMetrics(nodes...)
161 | if err != nil {
162 | return nil, err
163 | }
164 | for _, m := range ms {
165 | metricFreshness.WithLabelValues().Observe(myClock.Since(m.Timestamp.Time).Seconds())
166 | }
167 | // maintain the same ordering invariant as the Kube API would over nodes
168 | sort.Slice(ms, func(i, j int) bool {
169 | return ms[i].Name < ms[j].Name
170 | })
171 | return ms, nil
172 | }
173 |
174 | // NamespaceScoped implements rest.Scoper interface
175 | func (m *nodeMetrics) NamespaceScoped() bool {
176 | return false
177 | }
178 |
179 | // GetSingularName implements rest.SingularNameProvider interface
180 | func (m *nodeMetrics) GetSingularName() string {
181 | return ""
182 | }
183 |
--------------------------------------------------------------------------------
/pkg/api/pod.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package api
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "sort"
21 |
22 | corev1 "k8s.io/api/core/v1"
23 | "k8s.io/apimachinery/pkg/api/errors"
24 | metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
27 | "k8s.io/apimachinery/pkg/labels"
28 | "k8s.io/apimachinery/pkg/runtime"
29 | "k8s.io/apimachinery/pkg/runtime/schema"
30 | genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
31 | "k8s.io/apiserver/pkg/registry/rest"
32 | "k8s.io/client-go/tools/cache"
33 | "k8s.io/klog/v2"
34 | "k8s.io/metrics/pkg/apis/metrics"
35 | _ "k8s.io/metrics/pkg/apis/metrics/install"
36 | )
37 |
38 | type podMetrics struct {
39 | groupResource schema.GroupResource
40 | metrics PodMetricsGetter
41 | podLister cache.GenericLister
42 | }
43 |
44 | var _ rest.KindProvider = &podMetrics{}
45 | var _ rest.Storage = &podMetrics{}
46 | var _ rest.Getter = &podMetrics{}
47 | var _ rest.Lister = &podMetrics{}
48 | var _ rest.TableConvertor = &podMetrics{}
49 | var _ rest.Scoper = &podMetrics{}
50 | var _ rest.SingularNameProvider = &podMetrics{}
51 |
52 | func newPodMetrics(groupResource schema.GroupResource, metrics PodMetricsGetter, podLister cache.GenericLister) *podMetrics {
53 | return &podMetrics{
54 | groupResource: groupResource,
55 | metrics: metrics,
56 | podLister: podLister,
57 | }
58 | }
59 |
60 | // New implements rest.Storage interface
61 | func (m *podMetrics) New() runtime.Object {
62 | return &metrics.PodMetrics{}
63 | }
64 |
65 | // Destroy implements rest.Storage interface
66 | func (m *podMetrics) Destroy() {
67 | }
68 |
69 | // Kind implements rest.KindProvider interface
70 | func (m *podMetrics) Kind() string {
71 | return "PodMetrics"
72 | }
73 |
74 | // NewList implements rest.Lister interface
75 | func (m *podMetrics) NewList() runtime.Object {
76 | return &metrics.PodMetricsList{}
77 | }
78 |
79 | // List implements rest.Lister interface
80 | func (m *podMetrics) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
81 | pods, err := m.pods(ctx, options)
82 | if err != nil {
83 | return &metrics.PodMetricsList{}, err
84 | }
85 | ms, err := m.getMetrics(pods...)
86 | if err != nil {
87 | namespace := genericapirequest.NamespaceValue(ctx)
88 | klog.ErrorS(err, "Failed reading pods metrics", "namespace", klog.KRef("", namespace))
89 | return &metrics.PodMetricsList{}, fmt.Errorf("failed reading pods metrics: %w", err)
90 | }
91 | return &metrics.PodMetricsList{Items: ms}, nil
92 | }
93 |
94 | func (m *podMetrics) pods(ctx context.Context, options *metainternalversion.ListOptions) ([]runtime.Object, error) {
95 | labelSelector := labels.Everything()
96 | if options != nil && options.LabelSelector != nil {
97 | labelSelector = options.LabelSelector
98 | }
99 |
100 | namespace := genericapirequest.NamespaceValue(ctx)
101 | pods, err := m.podLister.ByNamespace(namespace).List(labelSelector)
102 | if err != nil {
103 | klog.ErrorS(err, "Failed listing pods", "labelSelector", labelSelector, "namespace", klog.KRef("", namespace))
104 | return nil, fmt.Errorf("failed listing pods: %w", err)
105 | }
106 | if options != nil && options.FieldSelector != nil {
107 | pods = filterPartialObjectMetadata(pods, options.FieldSelector)
108 | }
109 | return pods, err
110 | }
111 |
112 | // Get implements rest.Getter interface
113 | func (m *podMetrics) Get(ctx context.Context, name string, opts *metav1.GetOptions) (runtime.Object, error) {
114 | namespace := genericapirequest.NamespaceValue(ctx)
115 |
116 | pod, err := m.podLister.ByNamespace(namespace).Get(name)
117 | if err != nil {
118 | if errors.IsNotFound(err) {
119 | // return not-found errors directly
120 | return &metrics.PodMetrics{}, err
121 | }
122 | klog.ErrorS(err, "Failed getting pod", "pod", klog.KRef(namespace, name))
123 | return &metrics.PodMetrics{}, fmt.Errorf("failed getting pod: %w", err)
124 | }
125 | if pod == nil {
126 | return &metrics.PodMetrics{}, errors.NewNotFound(corev1.Resource("pods"), fmt.Sprintf("%s/%s", namespace, name))
127 | }
128 |
129 | ms, err := m.getMetrics(pod)
130 | if err != nil {
131 | klog.ErrorS(err, "Failed reading pod metrics", "pod", klog.KRef(namespace, name))
132 | return nil, fmt.Errorf("failed pod metrics: %w", err)
133 | }
134 | if len(ms) == 0 {
135 | return nil, errors.NewNotFound(m.groupResource, fmt.Sprintf("%s/%s", namespace, name))
136 | }
137 | return &ms[0], nil
138 | }
139 |
140 | // ConvertToTable implements rest.TableConvertor interface
141 | func (m *podMetrics) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
142 | var table metav1beta1.Table
143 |
144 | switch t := object.(type) {
145 | case *metrics.PodMetrics:
146 | table.ResourceVersion = t.ResourceVersion
147 | table.SelfLink = t.SelfLink //nolint:staticcheck // keep deprecated field to be backward compatible
148 | addPodMetricsToTable(&table, *t)
149 | case *metrics.PodMetricsList:
150 | table.ResourceVersion = t.ResourceVersion
151 | table.SelfLink = t.SelfLink //nolint:staticcheck // keep deprecated field to be backward compatible
152 | table.Continue = t.Continue
153 | addPodMetricsToTable(&table, t.Items...)
154 | default:
155 | }
156 |
157 | return &table, nil
158 | }
159 |
160 | func (m *podMetrics) getMetrics(pods ...runtime.Object) ([]metrics.PodMetrics, error) {
161 | objs := make([]*metav1.PartialObjectMetadata, len(pods))
162 | for i, pod := range pods {
163 | objs[i] = pod.(*metav1.PartialObjectMetadata)
164 | }
165 | ms, err := m.metrics.GetPodMetrics(objs...)
166 | if err != nil {
167 | return nil, err
168 | }
169 | for _, m := range ms {
170 | metricFreshness.WithLabelValues().Observe(myClock.Since(m.Timestamp.Time).Seconds())
171 | }
172 | sort.Slice(ms, func(i, j int) bool {
173 | if ms[i].Namespace != ms[j].Namespace {
174 | return ms[i].Namespace < ms[j].Namespace
175 | }
176 | return ms[i].Name < ms[j].Name
177 | })
178 | return ms, nil
179 | }
180 |
181 | // NamespaceScoped implements rest.Scoper interface
182 | func (m *podMetrics) NamespaceScoped() bool {
183 | return true
184 | }
185 |
186 | // GetSingularName implements rest.SingularNameProvider interface
187 | func (m *podMetrics) GetSingularName() string {
188 | return ""
189 | }
190 |
--------------------------------------------------------------------------------
/pkg/api/table.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package api
16 |
17 | import (
18 | "sort"
19 |
20 | v1 "k8s.io/api/core/v1"
21 | metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
22 | "k8s.io/apimachinery/pkg/runtime"
23 | "k8s.io/metrics/pkg/apis/metrics"
24 | )
25 |
26 | func addPodMetricsToTable(table *metav1beta1.Table, pods ...metrics.PodMetrics) {
27 | usage := make(v1.ResourceList, 3)
28 | var names []string
29 | for i, pod := range pods {
30 | for k := range usage {
31 | delete(usage, k)
32 | }
33 | for _, container := range pod.Containers {
34 | for k, v := range container.Usage {
35 | u := usage[k]
36 | u.Add(v)
37 | usage[k] = u
38 | }
39 | }
40 | if names == nil {
41 | for k := range usage {
42 | names = append(names, string(k))
43 | }
44 | sort.Strings(names)
45 |
46 | table.ColumnDefinitions = []metav1beta1.TableColumnDefinition{
47 | {Name: "Name", Type: "string", Format: "name", Description: "Name of the resource"},
48 | }
49 | for _, name := range names {
50 | table.ColumnDefinitions = append(table.ColumnDefinitions, metav1beta1.TableColumnDefinition{
51 | Name: name,
52 | Type: "string",
53 | Format: "quantity",
54 | })
55 | }
56 | table.ColumnDefinitions = append(table.ColumnDefinitions, metav1beta1.TableColumnDefinition{
57 | Name: "Window",
58 | Type: "string",
59 | Format: "duration",
60 | })
61 | }
62 | row := make([]interface{}, 0, len(names)+1)
63 | row = append(row, pod.Name)
64 | for _, name := range names {
65 | v := usage[v1.ResourceName(name)]
66 | row = append(row, v.String())
67 | }
68 | row = append(row, pod.Window.Duration.String())
69 | table.Rows = append(table.Rows, metav1beta1.TableRow{
70 | Cells: row,
71 | Object: runtime.RawExtension{Object: &pods[i]},
72 | })
73 | }
74 | }
75 |
76 | func addNodeMetricsToTable(table *metav1beta1.Table, nodes ...metrics.NodeMetrics) {
77 | var names []string
78 | for i, node := range nodes {
79 | if names == nil {
80 | for k := range node.Usage {
81 | names = append(names, string(k))
82 | }
83 | sort.Strings(names)
84 |
85 | table.ColumnDefinitions = []metav1beta1.TableColumnDefinition{
86 | {Name: "Name", Type: "string", Format: "name", Description: "Name of the resource"},
87 | }
88 | for _, name := range names {
89 | table.ColumnDefinitions = append(table.ColumnDefinitions, metav1beta1.TableColumnDefinition{
90 | Name: name,
91 | Type: "string",
92 | Format: "quantity",
93 | })
94 | }
95 | table.ColumnDefinitions = append(table.ColumnDefinitions, metav1beta1.TableColumnDefinition{
96 | Name: "Window",
97 | Type: "string",
98 | Format: "duration",
99 | })
100 | }
101 | row := make([]interface{}, 0, len(names)+1)
102 | row = append(row, node.Name)
103 | for _, name := range names {
104 | v := node.Usage[v1.ResourceName(name)]
105 | row = append(row, v.String())
106 | }
107 | row = append(row, node.Window.Duration.String())
108 | table.Rows = append(table.Rows, metav1beta1.TableRow{
109 | Cells: row,
110 | Object: runtime.RawExtension{Object: &nodes[i]},
111 | })
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/pkg/api/table_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package api
18 |
19 | import (
20 | "testing"
21 |
22 | genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
23 | )
24 |
25 | func TestNodeList_ConvertToTable(t *testing.T) {
26 | // setup
27 | r := NewTestNodeStorage(nil)
28 |
29 | // execute
30 | got, err := r.List(genericapirequest.NewContext(), nil)
31 |
32 | // assert
33 | if err != nil {
34 | t.Fatalf("Unexpected error: %v", err)
35 | }
36 |
37 | res, err := r.ConvertToTable(genericapirequest.NewContext(), got, nil)
38 | if err != nil {
39 | t.Fatalf("Unexpected error: %v", err)
40 | }
41 |
42 | if len(res.Rows) != 3 ||
43 | res.ColumnDefinitions[1].Name != "res1" || res.ColumnDefinitions[2].Name != "Window" ||
44 | res.Rows[0].Cells[0] != "node1" ||
45 | res.Rows[0].Cells[1] != "10m" ||
46 | res.Rows[0].Cells[2] != "1µs" ||
47 | res.Rows[1].Cells[0] != "node2" ||
48 | res.Rows[1].Cells[1] != "5Mi" ||
49 | res.Rows[1].Cells[2] != "2µs" ||
50 | res.Rows[2].Cells[0] != "node3" ||
51 | res.Rows[2].Cells[1] != "1" ||
52 | res.Rows[2].Cells[2] != "3µs" {
53 | t.Errorf("Got unexpected object: %+v", res)
54 | }
55 | }
56 |
57 | func TestPodList_ConvertToTable(t *testing.T) {
58 | // setup
59 | r := NewPodTestStorage(nil)
60 |
61 | // execute
62 | got, err := r.List(genericapirequest.NewContext(), nil)
63 |
64 | // assert
65 | if err != nil {
66 | t.Fatalf("Unexpected error: %v", err)
67 | }
68 |
69 | res, err := r.ConvertToTable(genericapirequest.NewContext(), got, nil)
70 | if err != nil {
71 | t.Fatalf("Unexpected error: %v", err)
72 | }
73 |
74 | if len(res.Rows) != 3 ||
75 | res.ColumnDefinitions[1].Name != "cpu" || res.ColumnDefinitions[2].Name != "memory" || res.ColumnDefinitions[3].Name != "Window" ||
76 | res.Rows[0].Cells[0] != "pod1" ||
77 | res.Rows[0].Cells[1] != "10m" ||
78 | res.Rows[0].Cells[2] != "5Mi" ||
79 | res.Rows[0].Cells[3] != "1µs" ||
80 | res.Rows[1].Cells[0] != "pod2" ||
81 | res.Rows[1].Cells[1] != "20m" ||
82 | res.Rows[1].Cells[2] != "15Mi" ||
83 | res.Rows[1].Cells[3] != "2µs" ||
84 | res.Rows[2].Cells[0] != "pod3" ||
85 | res.Rows[2].Cells[1] != "20m" ||
86 | res.Rows[2].Cells[2] != "25Mi" ||
87 | res.Rows[2].Cells[3] != "3µs" {
88 | t.Errorf("Got unexpected object: %+v", res)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/pkg/api/time.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package api
17 |
18 | import (
19 | "time"
20 | )
21 |
22 | var myClock clock = &realClock{}
23 |
24 | type clock interface {
25 | Now() time.Time
26 | Since(time.Time) time.Duration
27 | }
28 |
29 | type realClock struct{}
30 |
31 | func (realClock) Now() time.Time { return time.Now() }
32 | func (realClock) Since(d time.Time) time.Duration { return time.Since(d) }
33 |
34 | type fakeClock struct {
35 | now time.Time
36 | }
37 |
38 | func (c fakeClock) Now() time.Time { return c.now }
39 | func (c fakeClock) Since(d time.Time) time.Duration { return c.now.Sub(d) }
40 |
--------------------------------------------------------------------------------
/pkg/scraper/client/configs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package client
16 |
17 | import (
18 | corev1 "k8s.io/api/core/v1"
19 | "k8s.io/client-go/rest"
20 | )
21 |
22 | // KubeletClientConfig represents configuration for connecting to Kubelets.
23 | type KubeletClientConfig struct {
24 | Client rest.Config
25 | AddressTypePriority []corev1.NodeAddressType
26 | Scheme string
27 | DefaultPort int
28 | UseNodeStatusPort bool
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/scraper/client/interface.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package client
16 |
17 | import (
18 | "context"
19 |
20 | v1 "k8s.io/api/core/v1"
21 |
22 | "sigs.k8s.io/metrics-server/pkg/storage"
23 | )
24 |
25 | // KubeletMetricsGetter knows how to fetch metrics from the Kubelet
26 | type KubeletMetricsGetter interface {
27 | // GetMetrics fetches Resource metrics from the given Kubelet
28 | GetMetrics(ctx context.Context, node *v1.Node) (*storage.MetricsBatch, error)
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/scraper/client/resource/client.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package resource
16 |
17 | import (
18 | "bytes"
19 | "context"
20 | "fmt"
21 | "io"
22 | "net"
23 | "net/http"
24 | "net/url"
25 | "strconv"
26 | "sync"
27 | "time"
28 |
29 | corev1 "k8s.io/api/core/v1"
30 | "k8s.io/client-go/rest"
31 |
32 | "sigs.k8s.io/metrics-server/pkg/scraper/client"
33 | "sigs.k8s.io/metrics-server/pkg/storage"
34 | "sigs.k8s.io/metrics-server/pkg/utils"
35 | )
36 |
37 | const (
38 | // AnnotationResourceMetricsPath is the annotation used to specify the path to the resource metrics endpoint.
39 | AnnotationResourceMetricsPath = "metrics.k8s.io/resource-metrics-path"
40 | )
41 |
42 | type kubeletClient struct {
43 | defaultPort int
44 | useNodeStatusPort bool
45 | client *http.Client
46 | scheme string
47 | addrResolver utils.NodeAddressResolver
48 | buffers sync.Pool
49 | }
50 |
51 | var _ client.KubeletMetricsGetter = (*kubeletClient)(nil)
52 |
53 | func NewForConfig(config *client.KubeletClientConfig) (*kubeletClient, error) {
54 | transport, err := rest.TransportFor(&config.Client)
55 | if err != nil {
56 | return nil, fmt.Errorf("unable to construct transport: %v", err)
57 | }
58 |
59 | c := &http.Client{
60 | Transport: transport,
61 | Timeout: config.Client.Timeout,
62 | }
63 | return newClient(c, utils.NewPriorityNodeAddressResolver(config.AddressTypePriority), config.DefaultPort, config.Scheme, config.UseNodeStatusPort), nil
64 | }
65 |
66 | func newClient(c *http.Client, resolver utils.NodeAddressResolver, defaultPort int, scheme string, useNodeStatusPort bool) *kubeletClient {
67 | return &kubeletClient{
68 | addrResolver: resolver,
69 | defaultPort: defaultPort,
70 | client: c,
71 | scheme: scheme,
72 | useNodeStatusPort: useNodeStatusPort,
73 | buffers: sync.Pool{
74 | New: func() interface{} {
75 | buf := make([]byte, 10e3)
76 | return &buf
77 | },
78 | },
79 | }
80 | }
81 |
82 | // GetMetrics implements client.KubeletMetricsGetter
83 | func (kc *kubeletClient) GetMetrics(ctx context.Context, node *corev1.Node) (*storage.MetricsBatch, error) {
84 | port := kc.defaultPort
85 | path := "/metrics/resource"
86 | nodeStatusPort := int(node.Status.DaemonEndpoints.KubeletEndpoint.Port)
87 | if kc.useNodeStatusPort && nodeStatusPort != 0 {
88 | port = nodeStatusPort
89 | }
90 | if metricsPath := node.Annotations[AnnotationResourceMetricsPath]; metricsPath != "" {
91 | path = metricsPath
92 | }
93 | addr, err := kc.addrResolver.NodeAddress(node)
94 | if err != nil {
95 | return nil, err
96 | }
97 | url := url.URL{
98 | Scheme: kc.scheme,
99 | Host: net.JoinHostPort(addr, strconv.Itoa(port)),
100 | Path: path,
101 | }
102 | return kc.getMetrics(ctx, url.String(), node.Name)
103 | }
104 |
105 | func (kc *kubeletClient) getMetrics(ctx context.Context, url, nodeName string) (*storage.MetricsBatch, error) {
106 | req, err := http.NewRequest("GET", url, nil)
107 | if err != nil {
108 | return nil, err
109 | }
110 | requestTime := time.Now()
111 | response, err := kc.client.Do(req.WithContext(ctx))
112 | if err != nil {
113 | return nil, err
114 | }
115 | defer response.Body.Close()
116 | if response.StatusCode != http.StatusOK {
117 | return nil, fmt.Errorf("request failed, status: %q", response.Status)
118 | }
119 | bp := kc.buffers.Get().(*[]byte)
120 | b := *bp
121 | defer func() {
122 | *bp = b
123 | kc.buffers.Put(bp)
124 | }()
125 | buf := bytes.NewBuffer(b)
126 | buf.Reset()
127 | _, err = io.Copy(buf, response.Body)
128 | if err != nil {
129 | return nil, fmt.Errorf("failed to read response body - %v", err)
130 | }
131 | b = buf.Bytes()
132 | ms, err := decodeBatch(b, requestTime, nodeName)
133 | if err != nil {
134 | return nil, err
135 | }
136 | return ms, nil
137 | }
138 |
--------------------------------------------------------------------------------
/pkg/scraper/interface.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package scraper
16 |
17 | import (
18 | "context"
19 |
20 | "sigs.k8s.io/metrics-server/pkg/storage"
21 | )
22 |
23 | type Scraper interface {
24 | Scrape(ctx context.Context) *storage.MetricsBatch
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/scraper/scraper.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package scraper
16 |
17 | import (
18 | "context"
19 | "errors"
20 | "math/rand"
21 | "time"
22 |
23 | corev1 "k8s.io/api/core/v1"
24 | "k8s.io/apimachinery/pkg/labels"
25 | apitypes "k8s.io/apimachinery/pkg/types"
26 | v1listers "k8s.io/client-go/listers/core/v1"
27 | "k8s.io/component-base/metrics"
28 | "k8s.io/klog/v2"
29 |
30 | "sigs.k8s.io/metrics-server/pkg/scraper/client"
31 | "sigs.k8s.io/metrics-server/pkg/storage"
32 | )
33 |
34 | const (
35 | maxDelayMs = 4 * 1000
36 | delayPerSourceMs = 8
37 | )
38 |
39 | var (
40 | requestDuration = metrics.NewHistogramVec(
41 | &metrics.HistogramOpts{
42 | Namespace: "metrics_server",
43 | Subsystem: "kubelet",
44 | Name: "request_duration_seconds",
45 | Help: "Duration of requests to Kubelet API in seconds",
46 | Buckets: metrics.DefBuckets,
47 | },
48 | []string{"node"},
49 | )
50 | requestTotal = metrics.NewCounterVec(
51 | &metrics.CounterOpts{
52 | Namespace: "metrics_server",
53 | Subsystem: "kubelet",
54 | Name: "request_total",
55 | Help: "Number of requests sent to Kubelet API",
56 | },
57 | []string{"success"},
58 | )
59 | lastRequestTime = metrics.NewGaugeVec(
60 | &metrics.GaugeOpts{
61 | Namespace: "metrics_server",
62 | Subsystem: "kubelet",
63 | Name: "last_request_time_seconds",
64 | Help: "Time of last request performed to Kubelet API since unix epoch in seconds",
65 | },
66 | []string{"node"},
67 | )
68 | )
69 |
70 | // RegisterScraperMetrics registers rate, errors, and duration metrics on
71 | // Kubelet API scrapes.
72 | func RegisterScraperMetrics(registrationFunc func(metrics.Registerable) error) error {
73 | for _, metric := range []metrics.Registerable{
74 | requestDuration,
75 | requestTotal,
76 | lastRequestTime,
77 | } {
78 | err := registrationFunc(metric)
79 | if err != nil {
80 | return err
81 | }
82 | }
83 | return nil
84 | }
85 |
86 | func NewScraper(nodeLister v1listers.NodeLister, client client.KubeletMetricsGetter, scrapeTimeout time.Duration, labelRequirement []labels.Requirement) *scraper {
87 | labelSelector := labels.Everything()
88 | if labelRequirement != nil {
89 | labelSelector = labelSelector.Add(labelRequirement...)
90 | }
91 | return &scraper{
92 | nodeLister: nodeLister,
93 | kubeletClient: client,
94 | scrapeTimeout: scrapeTimeout,
95 | labelSelector: labelSelector,
96 | }
97 | }
98 |
99 | type scraper struct {
100 | nodeLister v1listers.NodeLister
101 | kubeletClient client.KubeletMetricsGetter
102 | scrapeTimeout time.Duration
103 | labelSelector labels.Selector
104 | }
105 |
106 | var _ Scraper = (*scraper)(nil)
107 |
108 | // NodeInfo contains the information needed to identify and connect to a particular node
109 | // (node name and preferred address).
110 | type NodeInfo struct {
111 | Name string
112 | ConnectAddress string
113 | }
114 |
115 | func (c *scraper) Scrape(baseCtx context.Context) *storage.MetricsBatch {
116 | nodes, err := c.nodeLister.List(c.labelSelector)
117 | if err != nil {
118 | // report the error and continue on in case of partial results
119 | klog.ErrorS(err, "Failed to list nodes")
120 | }
121 | klog.V(1).InfoS("Scraping metrics from nodes", "nodes", klog.KObjSlice(nodes), "nodeCount", len(nodes), "nodeSelector", c.labelSelector)
122 |
123 | responseChannel := make(chan *storage.MetricsBatch, len(nodes))
124 | defer close(responseChannel)
125 |
126 | startTime := myClock.Now()
127 |
128 | // TODO(serathius): re-evaluate this code -- do we really need to stagger fetches like this?
129 | delayMs := delayPerSourceMs * len(nodes)
130 | if delayMs > maxDelayMs {
131 | delayMs = maxDelayMs
132 | }
133 |
134 | for _, node := range nodes {
135 | go func(node *corev1.Node) {
136 | // Prevents network congestion.
137 | sleepDuration := time.Duration(rand.Intn(delayMs)) * time.Millisecond
138 | time.Sleep(sleepDuration)
139 | // make the timeout a bit shorter to account for staggering, so we still preserve
140 | // the overall timeout
141 | ctx, cancelTimeout := context.WithTimeout(baseCtx, c.scrapeTimeout)
142 | defer cancelTimeout()
143 | klog.V(2).InfoS("Scraping node", "node", klog.KObj(node))
144 | m, err := c.collectNode(ctx, node)
145 | if err != nil {
146 | if errors.Is(err, context.DeadlineExceeded) {
147 | klog.ErrorS(err, "Failed to scrape node, timeout to access kubelet", "node", klog.KObj(node), "timeout", c.scrapeTimeout)
148 | } else {
149 | klog.ErrorS(err, "Failed to scrape node", "node", klog.KObj(node))
150 | }
151 | }
152 | responseChannel <- m
153 | }(node)
154 | }
155 |
156 | res := &storage.MetricsBatch{
157 | Nodes: map[string]storage.MetricsPoint{},
158 | Pods: map[apitypes.NamespacedName]storage.PodMetricsPoint{},
159 | }
160 |
161 | for range nodes {
162 | srcBatch := <-responseChannel
163 | if srcBatch == nil {
164 | continue
165 | }
166 | for nodeName, nodeMetricsPoint := range srcBatch.Nodes {
167 | if _, nodeFind := res.Nodes[nodeName]; nodeFind {
168 | klog.ErrorS(nil, "Got duplicate node point", "node", klog.KRef("", nodeName))
169 | continue
170 | }
171 | res.Nodes[nodeName] = nodeMetricsPoint
172 | }
173 | for podRef, podMetricsPoint := range srcBatch.Pods {
174 | if _, podFind := res.Pods[podRef]; podFind {
175 | klog.ErrorS(nil, "Got duplicate pod point", "pod", klog.KRef(podRef.Namespace, podRef.Name))
176 | continue
177 | }
178 | res.Pods[podRef] = podMetricsPoint
179 | }
180 | }
181 |
182 | klog.V(1).InfoS("Scrape finished", "duration", myClock.Since(startTime), "nodeCount", len(res.Nodes), "podCount", len(res.Pods))
183 | return res
184 | }
185 |
186 | func (c *scraper) collectNode(ctx context.Context, node *corev1.Node) (*storage.MetricsBatch, error) {
187 | startTime := myClock.Now()
188 | defer func() {
189 | requestDuration.WithLabelValues(node.Name).Observe(float64(myClock.Since(startTime)) / float64(time.Second))
190 | lastRequestTime.WithLabelValues(node.Name).Set(float64(myClock.Now().Unix()))
191 | }()
192 | ms, err := c.kubeletClient.GetMetrics(ctx, node)
193 |
194 | if err != nil {
195 | requestTotal.WithLabelValues("false").Inc()
196 | return nil, err
197 | }
198 | requestTotal.WithLabelValues("true").Inc()
199 | return ms, nil
200 | }
201 |
202 | type clock interface {
203 | Now() time.Time
204 | Since(time.Time) time.Duration
205 | }
206 |
207 | type realClock struct{}
208 |
209 | func (realClock) Now() time.Time { return time.Now() }
210 | func (realClock) Since(d time.Time) time.Duration { return time.Since(d) }
211 |
212 | var myClock clock = &realClock{}
213 |
--------------------------------------------------------------------------------
/pkg/server/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | package server
15 |
16 | import (
17 | "fmt"
18 | "net/http"
19 | "strings"
20 | "time"
21 |
22 | corev1 "k8s.io/api/core/v1"
23 | "k8s.io/apimachinery/pkg/labels"
24 | apimetrics "k8s.io/apiserver/pkg/endpoints/metrics"
25 | genericapiserver "k8s.io/apiserver/pkg/server"
26 | "k8s.io/client-go/rest"
27 | "k8s.io/component-base/metrics"
28 | "k8s.io/component-base/metrics/legacyregistry"
29 | _ "k8s.io/component-base/metrics/prometheus/restclient" // for client-go metrics registration
30 |
31 | "sigs.k8s.io/metrics-server/pkg/api"
32 | "sigs.k8s.io/metrics-server/pkg/scraper"
33 | "sigs.k8s.io/metrics-server/pkg/scraper/client"
34 | "sigs.k8s.io/metrics-server/pkg/scraper/client/resource"
35 | "sigs.k8s.io/metrics-server/pkg/storage"
36 | )
37 |
38 | type Config struct {
39 | Apiserver *genericapiserver.Config
40 | Rest *rest.Config
41 | Kubelet *client.KubeletClientConfig
42 | MetricResolution time.Duration
43 | ScrapeTimeout time.Duration
44 | NodeSelector string
45 | }
46 |
47 | func (c Config) Complete() (*server, error) {
48 | var labelRequirement []labels.Requirement
49 |
50 | podInformerFactory, err := runningPodMetadataInformer(c.Rest)
51 | if err != nil {
52 | return nil, err
53 | }
54 | podInformer := podInformerFactory.ForResource(corev1.SchemeGroupVersion.WithResource("pods"))
55 | informer, err := informerFactory(c.Rest)
56 | if err != nil {
57 | return nil, err
58 | }
59 | kubeletClient, err := resource.NewForConfig(c.Kubelet)
60 | if err != nil {
61 | return nil, fmt.Errorf("unable to construct a client to connect to the kubelets: %v", err)
62 | }
63 | nodes := informer.Core().V1().Nodes()
64 | ns := strings.TrimSpace(c.NodeSelector)
65 | if ns != "" {
66 | labelRequirement, err = labels.ParseToRequirements(ns)
67 | if err != nil {
68 | return nil, err
69 | }
70 | }
71 | scrape := scraper.NewScraper(nodes.Lister(), kubeletClient, c.ScrapeTimeout, labelRequirement)
72 |
73 | // Disable default metrics handler and create custom one
74 | c.Apiserver.EnableMetrics = false
75 | metricsHandler, err := c.metricsHandler()
76 | if err != nil {
77 | return nil, err
78 | }
79 | genericServer, err := c.Apiserver.Complete(nil).New("metrics-server", genericapiserver.NewEmptyDelegate())
80 | if err != nil {
81 | return nil, err
82 | }
83 | genericServer.Handler.NonGoRestfulMux.HandleFunc("/metrics", metricsHandler)
84 |
85 | store := storage.NewStorage(c.MetricResolution)
86 | if err := api.Install(store, podInformer.Lister(), nodes.Lister(), genericServer, labelRequirement); err != nil {
87 | return nil, err
88 | }
89 |
90 | s := NewServer(
91 | nodes.Informer(),
92 | podInformer.Informer(),
93 | genericServer,
94 | store,
95 | scrape,
96 | c.MetricResolution,
97 | )
98 | err = s.RegisterProbes(podInformerFactory)
99 | if err != nil {
100 | return nil, err
101 | }
102 | return s, nil
103 | }
104 |
105 | func (c Config) metricsHandler() (http.HandlerFunc, error) {
106 | // Create registry for Metrics Server metrics
107 | registry := metrics.NewKubeRegistry()
108 | err := RegisterMetrics(registry, c.MetricResolution)
109 | if err != nil {
110 | return nil, err
111 | }
112 | // Register apiserver metrics in legacy registry
113 | apimetrics.Register()
114 |
115 | // Return handler that serves metrics from both legacy and Metrics Server registry
116 | return func(w http.ResponseWriter, req *http.Request) {
117 | legacyregistry.Handler().ServeHTTP(w, req)
118 | metrics.HandlerFor(registry, metrics.HandlerOpts{}).ServeHTTP(w, req)
119 | }, nil
120 | }
121 |
--------------------------------------------------------------------------------
/pkg/server/health.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package server
16 |
17 | import (
18 | "fmt"
19 | "net/http"
20 |
21 | "k8s.io/apimachinery/pkg/runtime/schema"
22 | "k8s.io/apiserver/pkg/server/healthz"
23 | )
24 |
25 | type cacheSyncWaiter interface {
26 | WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool
27 | }
28 |
29 | type metadataInformerSync struct {
30 | name string
31 | cacheSyncWaiter cacheSyncWaiter
32 | }
33 |
34 | var _ healthz.HealthChecker = &metadataInformerSync{}
35 |
36 | // MetadataInformerSyncHealthz returns a new HealthChecker that will pass only if all informers in the given cacheSyncWaiter sync.
37 | func MetadataInformerSyncHealthz(name string, cacheSyncWaiter cacheSyncWaiter) healthz.HealthChecker {
38 | return &metadataInformerSync{
39 | name: name,
40 | cacheSyncWaiter: cacheSyncWaiter,
41 | }
42 | }
43 |
44 | func (i *metadataInformerSync) Name() string {
45 | return i.name
46 | }
47 |
48 | func (i *metadataInformerSync) Check(_ *http.Request) error {
49 | stopCh := make(chan struct{})
50 | // Close stopCh to force checking if informers are synced now.
51 | close(stopCh)
52 |
53 | informersByStarted := make(map[bool][]string)
54 | for informerType, started := range i.cacheSyncWaiter.WaitForCacheSync(stopCh) {
55 | informersByStarted[started] = append(informersByStarted[started], informerType.String())
56 | }
57 |
58 | if notStarted := informersByStarted[false]; len(notStarted) > 0 {
59 | return fmt.Errorf("%d informers not started yet: %v", len(notStarted), notStarted)
60 | }
61 | return nil
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/server/informer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package server
16 |
17 | import (
18 | "fmt"
19 |
20 | corev1 "k8s.io/api/core/v1"
21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22 | "k8s.io/client-go/informers"
23 | "k8s.io/client-go/kubernetes"
24 | "k8s.io/client-go/metadata"
25 | "k8s.io/client-go/metadata/metadatainformer"
26 | "k8s.io/client-go/rest"
27 | )
28 |
29 | const (
30 | // we should never need to resync, since we're not worried about missing events,
31 | // and resync is actually for regular interval-based reconciliation these days,
32 | // so set the default resync interval to 0
33 | defaultResync = 0
34 | )
35 |
36 | func informerFactory(rest *rest.Config) (informers.SharedInformerFactory, error) {
37 | client, err := kubernetes.NewForConfig(rest)
38 | if err != nil {
39 | return nil, fmt.Errorf("unable to construct lister client: %v", err)
40 | }
41 | return informers.NewSharedInformerFactory(client, defaultResync), nil
42 | }
43 |
44 | func runningPodMetadataInformer(rest *rest.Config) (metadatainformer.SharedInformerFactory, error) {
45 | client, err := metadata.NewForConfig(rest)
46 | if err != nil {
47 | return nil, fmt.Errorf("unable to construct lister client: %v", err)
48 | }
49 | return metadatainformer.NewFilteredSharedInformerFactory(client, defaultResync, corev1.NamespaceAll, func(options *metav1.ListOptions) {
50 | options.FieldSelector = "status.phase=Running"
51 | }), nil
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/server/metrics.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package server
16 |
17 | import (
18 | "fmt"
19 | "time"
20 |
21 | "k8s.io/component-base/metrics"
22 |
23 | "sigs.k8s.io/metrics-server/pkg/api"
24 | "sigs.k8s.io/metrics-server/pkg/scraper"
25 | "sigs.k8s.io/metrics-server/pkg/storage"
26 | )
27 |
28 | // RegisterMetrics registers
29 | func RegisterMetrics(r metrics.KubeRegistry, metricResolution time.Duration) error {
30 | // register metrics server components metrics
31 | err := RegisterServerMetrics(r.Register, metricResolution)
32 | if err != nil {
33 | return fmt.Errorf("unable to register server metrics: %v", err)
34 | }
35 | err = scraper.RegisterScraperMetrics(r.Register)
36 | if err != nil {
37 | return fmt.Errorf("unable to register scraper metrics: %v", err)
38 | }
39 | err = api.RegisterAPIMetrics(r.Register)
40 | if err != nil {
41 | return fmt.Errorf("unable to register API metrics: %v", err)
42 | }
43 | err = storage.RegisterStorageMetrics(r.Register)
44 | if err != nil {
45 | return fmt.Errorf("unable to register storage metrics: %v", err)
46 | }
47 |
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/server/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package server
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "net/http"
21 | "sync"
22 | "time"
23 |
24 | "k8s.io/apimachinery/pkg/util/wait"
25 | genericapiserver "k8s.io/apiserver/pkg/server"
26 | "k8s.io/apiserver/pkg/server/healthz"
27 | "k8s.io/client-go/tools/cache"
28 | "k8s.io/component-base/metrics"
29 | "k8s.io/klog/v2"
30 |
31 | "sigs.k8s.io/metrics-server/pkg/scraper"
32 | "sigs.k8s.io/metrics-server/pkg/storage"
33 | "sigs.k8s.io/metrics-server/pkg/utils"
34 | )
35 |
36 | var (
37 | // initialized below to an actual value by a call to RegisterTickDuration
38 | // (acts as a no-op by default), but we can't just register it in the constructor,
39 | // since it could be called multiple times during setup.
40 | tickDuration = metrics.NewHistogram(&metrics.HistogramOpts{})
41 | )
42 |
43 | // RegisterServerMetrics creates and registers a histogram metric for
44 | // scrape duration.
45 | func RegisterServerMetrics(registrationFunc func(metrics.Registerable) error, resolution time.Duration) error {
46 | tickDuration = metrics.NewHistogram(
47 | &metrics.HistogramOpts{
48 | Namespace: "metrics_server",
49 | Subsystem: "manager",
50 | Name: "tick_duration_seconds",
51 | Help: "The total time spent collecting and storing metrics in seconds.",
52 | Buckets: utils.BucketsForScrapeDuration(resolution),
53 | },
54 | )
55 | return registrationFunc(tickDuration)
56 | }
57 |
58 | func NewServer(
59 | nodes cache.Controller,
60 | pods cache.Controller,
61 | apiserver *genericapiserver.GenericAPIServer, storage storage.Storage,
62 | scraper scraper.Scraper, resolution time.Duration) *server {
63 | return &server{
64 | nodes: nodes,
65 | pods: pods,
66 | GenericAPIServer: apiserver,
67 | storage: storage,
68 | scraper: scraper,
69 | resolution: resolution,
70 | }
71 | }
72 |
73 | // server scrapes metrics and serves then using k8s api.
74 | type server struct {
75 | *genericapiserver.GenericAPIServer
76 |
77 | pods cache.Controller
78 | nodes cache.Controller
79 |
80 | storage storage.Storage
81 | scraper scraper.Scraper
82 | resolution time.Duration
83 |
84 | // tickStatusMux protects tick fields
85 | tickStatusMux sync.RWMutex
86 | // tickLastStart is equal to start time of last unfinished tick
87 | tickLastStart time.Time
88 | }
89 |
90 | // RunUntil starts background scraping goroutine and runs apiserver serving metrics.
91 | func (s *server) RunUntil(stopCh <-chan struct{}) error {
92 | ctx, cancel := context.WithCancel(context.Background())
93 | defer cancel()
94 |
95 | // Start informers
96 | go s.nodes.Run(stopCh)
97 | go s.pods.Run(stopCh)
98 |
99 | // Ensure cache is up to date
100 | ok := cache.WaitForCacheSync(stopCh, s.nodes.HasSynced)
101 | if !ok {
102 | return nil
103 | }
104 | ok = cache.WaitForCacheSync(stopCh, s.pods.HasSynced)
105 | if !ok {
106 | return nil
107 | }
108 |
109 | // Start serving API and scrape loop
110 | go s.runScrape(ctx)
111 | return s.GenericAPIServer.PrepareRun().RunWithContext(wait.ContextForChannel(stopCh))
112 | }
113 |
114 | func (s *server) runScrape(ctx context.Context) {
115 | ticker := time.NewTicker(s.resolution)
116 | defer ticker.Stop()
117 | s.tick(ctx, time.Now())
118 |
119 | for {
120 | select {
121 | case startTime := <-ticker.C:
122 | s.tick(ctx, startTime)
123 | case <-ctx.Done():
124 | return
125 | }
126 | }
127 | }
128 |
129 | func (s *server) tick(ctx context.Context, startTime time.Time) {
130 | s.tickStatusMux.Lock()
131 | s.tickLastStart = startTime
132 | s.tickStatusMux.Unlock()
133 |
134 | ctx, cancelTimeout := context.WithTimeout(ctx, s.resolution)
135 | defer cancelTimeout()
136 |
137 | klog.V(6).InfoS("Scraping metrics")
138 | data := s.scraper.Scrape(ctx)
139 |
140 | klog.V(6).InfoS("Storing metrics")
141 | s.storage.Store(data)
142 |
143 | collectTime := time.Since(startTime)
144 | tickDuration.Observe(float64(collectTime) / float64(time.Second))
145 | klog.V(6).InfoS("Scraping cycle complete")
146 | }
147 |
148 | func (s *server) RegisterProbes(waiter cacheSyncWaiter) error {
149 | err := s.AddReadyzChecks(s.probeMetricStorageReady("metric-storage-ready"))
150 | if err != nil {
151 | return err
152 | }
153 | err = s.AddReadyzChecks(s.probeMetricCacheHasSynced("metric-informer-sync"))
154 | if err != nil {
155 | return err
156 | }
157 | err = s.AddLivezChecks(0, s.probeMetricCollectionTimely("metric-collection-timely"))
158 | if err != nil {
159 | return err
160 | }
161 | err = s.AddHealthChecks(MetadataInformerSyncHealthz("metadata-informer-sync", waiter))
162 | if err != nil {
163 | return err
164 | }
165 | return nil
166 | }
167 |
168 | // Check if MS is alive by looking at last tick time.
169 | // If its deadlock or panic, tick wouldn't be happening on the tick interval
170 | func (s *server) probeMetricCollectionTimely(name string) healthz.HealthChecker {
171 | return healthz.NamedCheck(name, func(_ *http.Request) error {
172 | s.tickStatusMux.RLock()
173 | tickLastStart := s.tickLastStart
174 | s.tickStatusMux.RUnlock()
175 |
176 | maxTickWait := time.Duration(1.5 * float64(s.resolution))
177 | tickWait := time.Since(tickLastStart)
178 | if !tickLastStart.IsZero() && tickWait > maxTickWait {
179 | err := fmt.Errorf("metric collection didn't finish on time")
180 | klog.InfoS("Failed probe", "probe", name, "err", err, "duration", tickWait, "maxDuration", maxTickWait)
181 | return err
182 | }
183 | return nil
184 | })
185 | }
186 |
187 | // Check if MS is ready by checking if last tick was ok
188 | func (s *server) probeMetricStorageReady(name string) healthz.HealthChecker {
189 | return healthz.NamedCheck(name, func(r *http.Request) error {
190 | if !s.storage.Ready() {
191 | err := fmt.Errorf("no metrics to serve")
192 | klog.InfoS("Failed probe", "probe", name, "err", err)
193 | return err
194 | }
195 | return nil
196 | })
197 | }
198 |
199 | // Check if MS is ready by checking if cache has synced
200 | func (s *server) probeMetricCacheHasSynced(name string) healthz.HealthChecker {
201 | return healthz.NamedCheck(name, func(r *http.Request) error {
202 | if !s.nodes.HasSynced() {
203 | err := fmt.Errorf("cache for node informer has not synced")
204 | klog.InfoS("Failed probe", "probe", name, "err", err)
205 | return err
206 | }
207 | if !s.pods.HasSynced() {
208 | err := fmt.Errorf("cache for pod informer has not synced")
209 | klog.InfoS("Failed probe", "probe", name, "err", err)
210 | return err
211 | }
212 | return nil
213 | })
214 | }
215 |
--------------------------------------------------------------------------------
/pkg/server/server_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package server
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "testing"
21 | "time"
22 |
23 | . "github.com/onsi/ginkgo/v2"
24 | . "github.com/onsi/gomega"
25 |
26 | corev1 "k8s.io/api/core/v1"
27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 | "k8s.io/metrics/pkg/apis/metrics"
29 |
30 | "sigs.k8s.io/metrics-server/pkg/scraper"
31 | "sigs.k8s.io/metrics-server/pkg/storage"
32 | )
33 |
34 | func TestServer(t *testing.T) {
35 | RegisterFailHandler(Fail)
36 | RunSpecs(t, "Server Suite")
37 | }
38 |
39 | var _ = Describe("Server", func() {
40 | var (
41 | resolution time.Duration
42 | server *server
43 | scraper *scraperMock
44 | store *storageMock
45 | )
46 |
47 | BeforeEach(func() {
48 | resolution = 60 * time.Second
49 | scraper = &scraperMock{
50 | result: &storage.MetricsBatch{
51 | Nodes: map[string]storage.MetricsPoint{
52 | "node1": {
53 | Timestamp: time.Now(),
54 | CumulativeCpuUsed: 0,
55 | MemoryUsage: 0,
56 | },
57 | },
58 | },
59 | }
60 | store = &storageMock{}
61 | server = NewServer(nil, nil, nil, store, scraper, resolution)
62 | })
63 |
64 | It("metric-collection-timely probe should pass before first scrape tick finishes", func() {
65 | check := server.probeMetricCollectionTimely("")
66 | Expect(check.Check(nil)).To(Succeed())
67 | })
68 | It("metric-collection-timely probe should pass if scrape fails", func() {
69 | scraper.err = fmt.Errorf("failed to scrape")
70 | server.tick(context.Background(), time.Now())
71 | check := server.probeMetricCollectionTimely("")
72 | Expect(check.Check(nil)).To(Succeed())
73 | })
74 | It("metric-collection-timely probe should pass if scrape succeeds", func() {
75 | server.tick(context.Background(), time.Now().Add(-resolution))
76 | check := server.probeMetricCollectionTimely("")
77 | Expect(check.Check(nil)).To(Succeed())
78 | })
79 | It("metric-collection-timely probe should fail if last scrape took longer than expected", func() {
80 | server.tick(context.Background(), time.Now().Add(-2*resolution))
81 | check := server.probeMetricCollectionTimely("")
82 | Expect(check.Check(nil)).NotTo(Succeed())
83 | })
84 | It("metric-storage-ready probe should fail if store is not ready", func() {
85 | check := server.probeMetricStorageReady("")
86 | Expect(check.Check(nil)).NotTo(Succeed())
87 | })
88 | It("metric-storage-ready probe should pass if store is ready", func() {
89 | store.ready = true
90 | check := server.probeMetricStorageReady("")
91 | Expect(check.Check(nil)).To(Succeed())
92 | })
93 | })
94 |
95 | type scraperMock struct {
96 | result *storage.MetricsBatch
97 | err error
98 | }
99 |
100 | var _ scraper.Scraper = (*scraperMock)(nil)
101 |
102 | func (s *scraperMock) Scrape(ctx context.Context) *storage.MetricsBatch {
103 | return s.result
104 | }
105 |
106 | type storageMock struct {
107 | ready bool
108 | }
109 |
110 | var _ storage.Storage = (*storageMock)(nil)
111 |
112 | func (s *storageMock) Store(batch *storage.MetricsBatch) {}
113 |
114 | func (s *storageMock) GetPodMetrics(pods ...*metav1.PartialObjectMetadata) ([]metrics.PodMetrics, error) {
115 | return nil, nil
116 | }
117 |
118 | func (s *storageMock) GetNodeMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error) {
119 | return nil, nil
120 | }
121 |
122 | func (s *storageMock) Ready() bool {
123 | return s.ready
124 | }
125 |
--------------------------------------------------------------------------------
/pkg/storage/interface.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package storage
16 |
17 | import "sigs.k8s.io/metrics-server/pkg/api"
18 |
19 | type Storage interface {
20 | api.MetricsGetter
21 | Store(batch *MetricsBatch)
22 | Ready() bool
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/storage/main_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package storage
16 |
17 | import (
18 | "testing"
19 |
20 | . "github.com/onsi/ginkgo/v2"
21 | . "github.com/onsi/gomega"
22 | )
23 |
24 | func TestStorage(t *testing.T) {
25 | RegisterFailHandler(Fail)
26 | RunSpecs(t, "storage suite")
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/storage/monitoring.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package storage
16 |
17 | import (
18 | "k8s.io/component-base/metrics"
19 | )
20 |
21 | var (
22 | pointsStored = metrics.NewGaugeVec(
23 | &metrics.GaugeOpts{
24 | Namespace: "metrics_server",
25 | Subsystem: "storage",
26 | Name: "points",
27 | Help: "Number of metrics points stored.",
28 | },
29 | []string{"type"},
30 | )
31 | )
32 |
33 | // RegisterStorageMetrics registers a gauge metric for the number of metrics
34 | // points stored.
35 | func RegisterStorageMetrics(registrationFunc func(metrics.Registerable) error) error {
36 | return registrationFunc(pointsStored)
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/storage/node.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package storage
17 |
18 | import (
19 | "time"
20 |
21 | corev1 "k8s.io/api/core/v1"
22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | "k8s.io/klog/v2"
24 | "k8s.io/metrics/pkg/apis/metrics"
25 | )
26 |
27 | // nodeStorage stores last two node metric batches and calculates cpu & memory usage
28 | //
29 | // This implementation only stores metric points if they are newer than the
30 | // points already stored and the cpuUsageOverTime function used to handle
31 | // cumulative metrics assumes that the time window is different from 0.
32 | type nodeStorage struct {
33 | // last stores node metric points from last scrape
34 | last map[string]MetricsPoint
35 | // prev stores node metric points from scrape preceding the last one.
36 | // Points timestamp should proceed the corresponding points from last.
37 | prev map[string]MetricsPoint
38 | }
39 |
40 | func (s *nodeStorage) GetMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error) {
41 | results := make([]metrics.NodeMetrics, 0, len(nodes))
42 | for _, node := range nodes {
43 | last, found := s.last[node.Name]
44 | if !found {
45 | continue
46 | }
47 |
48 | prev, found := s.prev[node.Name]
49 | if !found {
50 | continue
51 | }
52 | rl, ti, err := resourceUsage(last, prev)
53 | if err != nil {
54 | klog.ErrorS(err, "Skipping node usage metric", "node", node)
55 | continue
56 | }
57 | results = append(results, metrics.NodeMetrics{
58 | ObjectMeta: metav1.ObjectMeta{
59 | Name: node.Name,
60 | Labels: node.Labels,
61 | CreationTimestamp: metav1.NewTime(time.Now()),
62 | },
63 | Timestamp: metav1.NewTime(ti.Timestamp),
64 | Window: metav1.Duration{Duration: ti.Window},
65 | Usage: rl,
66 | })
67 | }
68 | return results, nil
69 | }
70 |
71 | func (s *nodeStorage) Store(batch *MetricsBatch) {
72 | lastNodes := make(map[string]MetricsPoint, len(batch.Nodes))
73 | prevNodes := make(map[string]MetricsPoint, len(batch.Nodes))
74 | for nodeName, newPoint := range batch.Nodes {
75 | if _, exists := lastNodes[nodeName]; exists {
76 | klog.ErrorS(nil, "Got duplicate node point", "node", klog.KRef("", nodeName))
77 | continue
78 | }
79 | lastNodes[nodeName] = newPoint
80 |
81 | if lastNode, found := s.last[nodeName]; found {
82 | // If new point is different then one already stored
83 | if newPoint.Timestamp.After(lastNode.Timestamp) {
84 | // Move stored point to previous
85 | prevNodes[nodeName] = lastNode
86 | } else if prevPoint, found := s.prev[nodeName]; found {
87 | if prevPoint.Timestamp.Before(newPoint.Timestamp) {
88 | // Keep previous point
89 | prevNodes[nodeName] = prevPoint
90 | } else {
91 | klog.V(2).InfoS("Found new node metrics point is older than stored previous, drop previous",
92 | "node", nodeName,
93 | "previousTimestamp", prevPoint.Timestamp,
94 | "timestamp", newPoint.Timestamp)
95 | }
96 | }
97 | }
98 | }
99 | s.last = lastNodes
100 | s.prev = prevNodes
101 |
102 | // Only count last for which metrics can be returned.
103 | pointsStored.WithLabelValues("node").Set(float64(len(prevNodes)))
104 | }
105 |
--------------------------------------------------------------------------------
/pkg/storage/pod.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package storage
17 |
18 | import (
19 | "time"
20 |
21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22 | apitypes "k8s.io/apimachinery/pkg/types"
23 | "k8s.io/klog/v2"
24 | "k8s.io/metrics/pkg/apis/metrics"
25 |
26 | "sigs.k8s.io/metrics-server/pkg/api"
27 | )
28 |
29 | // fresh new container's minimum allowable time duration between start time and timestamp.
30 | // if time duration less than 10s, can produce inaccurate data
31 | const freshContainerMinMetricsResolution = 10 * time.Second
32 |
33 | // podStorage stores last two pod metric batches and calculates cpu & memory usage.
34 | //
35 | // This implementation only stores metric points if they are newer than the
36 | // points already stored and the cpuUsageOverTime function used to handle
37 | // cumulative metrics assumes that the time window is different from 0.
38 | type podStorage struct {
39 | // last stores pod metric points from last scrape
40 | last map[apitypes.NamespacedName]PodMetricsPoint
41 | // prev stores pod metric points from scrape preceding the last one.
42 | // Points timestamp should proceed the corresponding points from last and have same start time (no restart between them).
43 | prev map[apitypes.NamespacedName]PodMetricsPoint
44 | // scrape period of metrics server
45 | metricResolution time.Duration
46 | }
47 |
48 | func (s *podStorage) GetMetrics(pods ...*metav1.PartialObjectMetadata) ([]metrics.PodMetrics, error) {
49 | results := make([]metrics.PodMetrics, 0, len(pods))
50 | for _, pod := range pods {
51 | lastPod, found := s.last[apitypes.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}]
52 | if !found {
53 | continue
54 | }
55 |
56 | prevPod, found := s.prev[apitypes.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}]
57 | if !found {
58 | continue
59 | }
60 |
61 | var (
62 | cms = make([]metrics.ContainerMetrics, 0, len(lastPod.Containers))
63 | earliestTimeInfo api.TimeInfo
64 | )
65 | allContainersPresent := true
66 | for container, lastContainer := range lastPod.Containers {
67 | prevContainer, found := prevPod.Containers[container]
68 | if !found {
69 | allContainersPresent = false
70 | break
71 | }
72 | usage, ti, err := resourceUsage(lastContainer, prevContainer)
73 | if err != nil {
74 | klog.ErrorS(err, "Skipping container usage metric", "container", container, "pod", klog.KRef(pod.Namespace, pod.Name))
75 | continue
76 | }
77 | cms = append(cms, metrics.ContainerMetrics{
78 | Name: container,
79 | Usage: usage,
80 | })
81 | if earliestTimeInfo.Timestamp.IsZero() || earliestTimeInfo.Timestamp.After(ti.Timestamp) {
82 | earliestTimeInfo = ti
83 | }
84 | }
85 | if allContainersPresent {
86 | results = append(results, metrics.PodMetrics{
87 | ObjectMeta: metav1.ObjectMeta{
88 | Name: pod.Name,
89 | Namespace: pod.Namespace,
90 | Labels: pod.Labels,
91 | CreationTimestamp: metav1.NewTime(time.Now()),
92 | },
93 | Timestamp: metav1.NewTime(earliestTimeInfo.Timestamp),
94 | Window: metav1.Duration{Duration: earliestTimeInfo.Window},
95 | Containers: cms,
96 | })
97 | }
98 | }
99 | return results, nil
100 | }
101 |
102 | func (s *podStorage) Store(newPods *MetricsBatch) {
103 | lastPods := make(map[apitypes.NamespacedName]PodMetricsPoint, len(newPods.Pods))
104 | prevPods := make(map[apitypes.NamespacedName]PodMetricsPoint, len(newPods.Pods))
105 | var containerCount int
106 | for podRef, newPod := range newPods.Pods {
107 | podRef := apitypes.NamespacedName{Name: podRef.Name, Namespace: podRef.Namespace}
108 | if _, found := lastPods[podRef]; found {
109 | klog.ErrorS(nil, "Got duplicate pod point", "pod", klog.KRef(podRef.Namespace, podRef.Name))
110 | continue
111 | }
112 |
113 | newLastPod := PodMetricsPoint{Containers: make(map[string]MetricsPoint, len(newPod.Containers))}
114 | newPrevPod := PodMetricsPoint{Containers: make(map[string]MetricsPoint, len(newPod.Containers))}
115 | for containerName, newPoint := range newPod.Containers {
116 | if _, exists := newLastPod.Containers[containerName]; exists {
117 | klog.ErrorS(nil, "Got duplicate Container point", "container", containerName, "pod", klog.KRef(podRef.Namespace, podRef.Name))
118 | continue
119 | }
120 | newLastPod.Containers[containerName] = newPoint
121 | if newPoint.StartTime.Before(newPoint.Timestamp) && newPoint.Timestamp.Sub(newPoint.StartTime) < s.metricResolution && newPoint.Timestamp.Sub(newPoint.StartTime) >= freshContainerMinMetricsResolution {
122 | copied := newPoint
123 | copied.Timestamp = newPoint.StartTime
124 | copied.CumulativeCpuUsed = 0
125 | newPrevPod.Containers[containerName] = copied
126 | } else if lastPod, found := s.last[podRef]; found {
127 | // Keep previous metric point if newPoint has not restarted (new metric start time < stored timestamp)
128 | if lastContainer, found := lastPod.Containers[containerName]; found && newPoint.StartTime.Before(lastContainer.Timestamp) {
129 | // If new point is different then one already stored
130 | if newPoint.Timestamp.After(lastContainer.Timestamp) {
131 | // Move stored point to previous
132 | newPrevPod.Containers[containerName] = lastContainer
133 | } else if prevPod, found := s.prev[podRef]; found {
134 | if prevPod.Containers[containerName].Timestamp.Before(newPoint.Timestamp) {
135 | // Keep previous point
136 | newPrevPod.Containers[containerName] = prevPod.Containers[containerName]
137 | } else {
138 | klog.V(2).InfoS("Found new containerName metrics point is older than stored previous , drop previous",
139 | "containerName", containerName,
140 | "pod", klog.KRef(podRef.Namespace, podRef.Name),
141 | "previousTimestamp", prevPod.Containers[containerName].Timestamp,
142 | "timestamp", newPoint.Timestamp)
143 | }
144 | }
145 | }
146 | }
147 | }
148 | containerPoints := len(newPrevPod.Containers)
149 | if containerPoints > 0 {
150 | prevPods[podRef] = newPrevPod
151 | }
152 | lastPods[podRef] = newLastPod
153 |
154 | // Only count containers for which metrics can be returned.
155 | containerCount += containerPoints
156 | }
157 | s.last = lastPods
158 | s.prev = prevPods
159 |
160 | pointsStored.WithLabelValues("container").Set(float64(containerCount))
161 | }
162 |
--------------------------------------------------------------------------------
/pkg/storage/storage.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package storage
17 |
18 | import (
19 | "sync"
20 | "time"
21 |
22 | corev1 "k8s.io/api/core/v1"
23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24 | "k8s.io/metrics/pkg/apis/metrics"
25 | )
26 |
27 | // nodeStorage is a thread save nodeStorage for node and pod metrics.
28 | type storage struct {
29 | mu sync.RWMutex
30 | pods podStorage
31 | nodes nodeStorage
32 | }
33 |
34 | var _ Storage = (*storage)(nil)
35 |
36 | func NewStorage(metricResolution time.Duration) *storage {
37 | return &storage{pods: podStorage{metricResolution: metricResolution}}
38 | }
39 |
40 | // Ready returns true if metrics-server's storage has accumulated enough metric
41 | // points to serve NodeMetrics.
42 | func (s *storage) Ready() bool {
43 | s.mu.RLock()
44 | defer s.mu.RUnlock()
45 | return len(s.nodes.prev) != 0 || len(s.pods.prev) != 0
46 | }
47 |
48 | func (s *storage) GetNodeMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error) {
49 | s.mu.RLock()
50 | defer s.mu.RUnlock()
51 | return s.nodes.GetMetrics(nodes...)
52 | }
53 |
54 | func (s *storage) GetPodMetrics(pods ...*metav1.PartialObjectMetadata) ([]metrics.PodMetrics, error) {
55 | s.mu.RLock()
56 | defer s.mu.RUnlock()
57 | return s.pods.GetMetrics(pods...)
58 | }
59 |
60 | func (s *storage) Store(batch *MetricsBatch) {
61 | s.mu.Lock()
62 | defer s.mu.Unlock()
63 | s.nodes.Store(batch)
64 | s.pods.Store(batch)
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/storage/storage_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package storage
16 |
17 | import (
18 | "time"
19 | )
20 |
21 | const (
22 | MiByte = 1024 * 1024
23 | CoreSecond = 1000 * 1000 * 1000
24 | )
25 |
26 | func newMetricsPoint(st time.Time, ts time.Time, cpu, memory uint64) MetricsPoint {
27 | return MetricsPoint{
28 | StartTime: st,
29 | Timestamp: ts,
30 | CumulativeCpuUsed: cpu,
31 | MemoryUsage: memory,
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/storage/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package storage
16 |
17 | import (
18 | "fmt"
19 | "math"
20 | "time"
21 |
22 | corev1 "k8s.io/api/core/v1"
23 | "k8s.io/apimachinery/pkg/api/resource"
24 | apitypes "k8s.io/apimachinery/pkg/types"
25 | "k8s.io/klog/v2"
26 |
27 | "sigs.k8s.io/metrics-server/pkg/api"
28 | )
29 |
30 | // MetricsBatch is a single batch of pod, container, and node metrics from some source.
31 | type MetricsBatch struct {
32 | Nodes map[string]MetricsPoint
33 | Pods map[apitypes.NamespacedName]PodMetricsPoint
34 | }
35 |
36 | // PodMetricsPoint contains the metrics for some pod's containers.
37 | type PodMetricsPoint struct {
38 | Containers map[string]MetricsPoint
39 | }
40 |
41 | // MetricsPoint represents the a set of specific metrics at some point in time.
42 | type MetricsPoint struct {
43 | // StartTime is the start time of container/node. Cumulative CPU usage at that moment should be equal zero.
44 | StartTime time.Time
45 | // Timestamp is the time when metric point was measured. If CPU and Memory was measured at different time it should equal CPU time to allow accurate CPU calculation.
46 | Timestamp time.Time
47 | // CumulativeCpuUsed is the cumulative cpu used at Timestamp from the StartTime of container/node. Unit: nano core * seconds.
48 | CumulativeCpuUsed uint64
49 | // MemoryUsage is the working set size. Unit: bytes.
50 | MemoryUsage uint64
51 | }
52 |
53 | func resourceUsage(last, prev MetricsPoint) (corev1.ResourceList, api.TimeInfo, error) {
54 | if last.StartTime.Before(prev.StartTime) {
55 | return corev1.ResourceList{}, api.TimeInfo{}, fmt.Errorf("unexpected decrease in startTime of node/container")
56 | }
57 | if last.CumulativeCpuUsed < prev.CumulativeCpuUsed {
58 | return corev1.ResourceList{}, api.TimeInfo{}, fmt.Errorf("unexpected decrease in cumulative CPU usage value")
59 | }
60 | window := last.Timestamp.Sub(prev.Timestamp)
61 | cpuUsage := float64(last.CumulativeCpuUsed-prev.CumulativeCpuUsed) / window.Seconds()
62 | return corev1.ResourceList{
63 | corev1.ResourceCPU: uint64Quantity(uint64(cpuUsage), resource.DecimalSI, -9),
64 | corev1.ResourceMemory: uint64Quantity(last.MemoryUsage, resource.BinarySI, 0),
65 | }, api.TimeInfo{
66 | Timestamp: last.Timestamp,
67 | Window: window,
68 | }, nil
69 | }
70 |
71 | // uint64Quantity converts a uint64 into a Quantity, which only has constructors
72 | // that work with int64 (except for parse, which requires costly round-trips to string).
73 | // We lose precision until we fit in an int64 if greater than the max int64 value.
74 | func uint64Quantity(val uint64, format resource.Format, scale resource.Scale) resource.Quantity {
75 | q := *resource.NewScaledQuantity(int64(val), scale)
76 | if val > math.MaxInt64 {
77 | // lose an decimal order-of-magnitude precision,
78 | // so we can fit into a scaled quantity
79 | klog.V(2).InfoS("Found unexpectedly large resource value, losing precision to fit in scaled resource.Quantity", "value", val)
80 | q = *resource.NewScaledQuantity(int64(val/10), resource.Scale(1)+scale)
81 | }
82 | q.Format = format
83 | return q
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/storage/types_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package storage
16 |
17 | import (
18 | "math"
19 | "reflect"
20 | "testing"
21 | "time"
22 |
23 | "sigs.k8s.io/metrics-server/pkg/api"
24 |
25 | v1 "k8s.io/api/core/v1"
26 | "k8s.io/apimachinery/pkg/api/resource"
27 | )
28 |
29 | func TestUint64Quantity(t *testing.T) {
30 | tcs := []struct {
31 | name string
32 | input uint64
33 | expect resource.Quantity
34 | }{
35 | {"math.MaxInt64 + 10", uint64(math.MaxInt64 + 10), *resource.NewScaledQuantity(int64(math.MaxInt64/10+1), 1)},
36 | {"math.MaxInt64 + 20", uint64(math.MaxInt64 + 20), *resource.NewScaledQuantity(int64(math.MaxInt64/10+2), 1)},
37 | {"math.MaxInt64 - 10", uint64(math.MaxInt64 - 10), *resource.NewScaledQuantity(int64(math.MaxInt64-10), 0)},
38 | {"math.MaxInt64 - 100", uint64(math.MaxInt64 - 100), *resource.NewScaledQuantity(int64(math.MaxInt64-100), 0)},
39 | }
40 | for _, tc := range tcs {
41 | t.Run(tc.name, func(t *testing.T) {
42 | got := uint64Quantity(tc.input, resource.DecimalSI, 0)
43 | if got != tc.expect {
44 | t.Errorf("uint64Quantity(%d, resource.DecimalSI, 0) = %+v, expected: %+v", tc.input, got, tc.expect)
45 | }
46 | })
47 | }
48 | }
49 |
50 | func Test_resourceUsage(t *testing.T) {
51 | start := time.Now()
52 | tcs := []struct {
53 | name string
54 | last MetricsPoint
55 | prev MetricsPoint
56 | wantResourceList v1.ResourceList
57 | wantTimeInfo api.TimeInfo
58 | wantErr bool
59 | }{
60 | {
61 | name: "get resource usage successfully",
62 | last: newMetricsPoint(start, start.Add(20*time.Millisecond), 500, 600),
63 | prev: newMetricsPoint(start, start.Add(10*time.Millisecond), 300, 400),
64 | wantResourceList: v1.ResourceList{v1.ResourceCPU: uint64Quantity(uint64(20000), resource.DecimalSI, -9),
65 | v1.ResourceMemory: uint64Quantity(600, resource.BinarySI, 0)},
66 | wantTimeInfo: api.TimeInfo{Timestamp: start.Add(20 * time.Millisecond), Window: 10 * time.Millisecond},
67 | },
68 | {
69 | name: "get resource usage failed because of unexpected decrease in startTime",
70 | last: newMetricsPoint(start, start.Add(20*time.Millisecond), 500, 600),
71 | prev: newMetricsPoint(start.Add(20*time.Millisecond), start.Add(10*time.Millisecond), 300, 400),
72 | wantResourceList: v1.ResourceList{},
73 | wantTimeInfo: api.TimeInfo{},
74 | wantErr: true,
75 | },
76 | {
77 | name: "get resource usage failed because of unexpected decrease in cumulative CPU usage value",
78 | last: newMetricsPoint(start, start.Add(20*time.Millisecond), 100, 600),
79 | prev: newMetricsPoint(start, start.Add(10*time.Millisecond), 300, 400),
80 | wantResourceList: v1.ResourceList{},
81 | wantTimeInfo: api.TimeInfo{},
82 | wantErr: true,
83 | },
84 | }
85 | for _, tc := range tcs {
86 | t.Run(tc.name, func(t *testing.T) {
87 | resourceList, timeInfo, err := resourceUsage(tc.last, tc.prev)
88 | if (err != nil) != tc.wantErr {
89 | t.Errorf("resourceUsage() error = %v, wantErr %v", err, tc.wantErr)
90 | return
91 | }
92 | if !reflect.DeepEqual(resourceList, tc.wantResourceList) {
93 | t.Errorf("resourceUsage() resourceList = %v, want %v", resourceList, tc.wantResourceList)
94 | }
95 | if !reflect.DeepEqual(timeInfo, tc.wantTimeInfo) {
96 | t.Errorf("resourceUsage() timeInfo = %v, want %v", timeInfo, tc.wantTimeInfo)
97 | }
98 | })
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/pkg/utils/address_resolver.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package utils
16 |
17 | import (
18 | "fmt"
19 |
20 | corev1 "k8s.io/api/core/v1"
21 | )
22 |
23 | var (
24 | // DefaultAddressTypePriority is the default node address type
25 | // priority list, as taken from the Kubernetes API metrics-server options.
26 | // In general, we prefer overrides to others, internal to external,
27 | // and DNS to IPs.
28 | DefaultAddressTypePriority = []corev1.NodeAddressType{
29 | // --override-hostname
30 | corev1.NodeHostName,
31 |
32 | // internal, preferring DNS if reported
33 | corev1.NodeInternalDNS,
34 | corev1.NodeInternalIP,
35 |
36 | // external, preferring DNS if reported
37 | corev1.NodeExternalDNS,
38 | corev1.NodeExternalIP,
39 | }
40 | )
41 |
42 | // NodeAddressResolver knows how to find the preferred connection
43 | // address for a given node.
44 | type NodeAddressResolver interface {
45 | // NodeAddress finds the preferred address to use to connect to
46 | // the given node.
47 | NodeAddress(node *corev1.Node) (address string, err error)
48 | }
49 |
50 | // prioNodeAddrResolver finds node addresses according to a list of
51 | // priorities of types of addresses.
52 | type prioNodeAddrResolver struct {
53 | addrTypePriority []corev1.NodeAddressType
54 | }
55 |
56 | func (r *prioNodeAddrResolver) NodeAddress(node *corev1.Node) (string, error) {
57 | // adapted from k8s.io/kubernetes/pkg/util/node
58 | for _, addrType := range r.addrTypePriority {
59 | for _, addr := range node.Status.Addresses {
60 | if addr.Type == addrType {
61 | return addr.Address, nil
62 | }
63 | }
64 | }
65 |
66 | return "", fmt.Errorf("no address matched types %v", r.addrTypePriority)
67 | }
68 |
69 | // NewPriorityNodeAddressResolver creates a new NodeAddressResolver that resolves
70 | // addresses first based on a list of prioritized address types, then based on
71 | // address order (first to last) within a particular address type.
72 | func NewPriorityNodeAddressResolver(typePriority []corev1.NodeAddressType) NodeAddressResolver {
73 | return &prioNodeAddrResolver{
74 | addrTypePriority: typePriority,
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/utils/monitoring.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package utils
16 |
17 | import (
18 | "time"
19 |
20 | "k8s.io/component-base/metrics"
21 | )
22 |
23 | // BucketsForScrapeDuration calculates a variant of the prometheus default histogram
24 | // buckets that includes relevant buckets around our scrape timeout.
25 | func BucketsForScrapeDuration(scrapeTimeout time.Duration) []float64 {
26 | // set up some buckets that include our scrape timeout,
27 | // so that we can easily pinpoint scrape timeout issues.
28 | // The default buckets provide a sane starting point for
29 | // the smaller numbers.
30 | buckets := append([]float64(nil), metrics.DefBuckets...)
31 | maxBucket := buckets[len(buckets)-1]
32 | timeoutSeconds := float64(scrapeTimeout) / float64(time.Second)
33 | if timeoutSeconds > maxBucket {
34 | // [defaults, (scrapeTimeout + (scrapeTimeout - maxBucket)/ 2), scrapeTimeout, scrapeTimeout*1.5, scrapeTimeout*2]
35 | halfwayToScrapeTimeout := maxBucket + (timeoutSeconds-maxBucket)/2
36 | buckets = append(buckets, halfwayToScrapeTimeout, timeoutSeconds, timeoutSeconds*1.5, timeoutSeconds*2.0)
37 | } else if timeoutSeconds < maxBucket {
38 | var i int
39 | var bucket float64
40 | for i, bucket = range buckets {
41 | if bucket > timeoutSeconds {
42 | break
43 | }
44 | }
45 |
46 | if bucket-timeoutSeconds < buckets[0] || (i > 0 && timeoutSeconds-buckets[i-1] < buckets[0]) {
47 | // if we're sufficiently close to another bucket, just skip this
48 | return buckets
49 | }
50 |
51 | // likely that our scrape timeout is close to another bucket, so don't bother injecting more than just our scrape timeout
52 | oldRest := append([]float64(nil), buckets[i:]...) // make a copy so we don't overwrite it
53 | buckets = append(buckets[:i], timeoutSeconds)
54 | buckets = append(buckets, oldRest...)
55 | }
56 |
57 | return buckets
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/utils/monitoring_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package utils
16 |
17 | import (
18 | "testing"
19 | "time"
20 |
21 | . "github.com/onsi/ginkgo/v2"
22 | . "github.com/onsi/gomega"
23 |
24 | "k8s.io/component-base/metrics"
25 | )
26 |
27 | func TestMetricsUtil(t *testing.T) {
28 | RegisterFailHandler(Fail)
29 | RunSpecs(t, "Prometheus Metrics Util Test")
30 | }
31 |
32 | var _ = Describe("Prometheus Bucket Estimator", func() {
33 | Context("with a scrape timeout longer than the max default bucket", func() {
34 | It("should generate buckets in strictly increasing order", func() {
35 | buckets := BucketsForScrapeDuration(15 * time.Second)
36 | lastBucket := 0.0
37 | for _, bucket := range buckets {
38 | Expect(bucket).To(BeNumerically(">", lastBucket))
39 | lastBucket = bucket
40 | }
41 | })
42 |
43 | It("should include some buckets around the scrape timeout", func() {
44 | Expect(BucketsForScrapeDuration(15 * time.Second)).To(ContainElement(15.0))
45 | Expect(BucketsForScrapeDuration(15 * time.Second)).To(ContainElement(30.0))
46 | })
47 | })
48 | Context("with a scrape timeout shorter than the max default bucket", func() {
49 | It("should generate buckets in strictly increasing order", func() {
50 | buckets := BucketsForScrapeDuration(5 * time.Second)
51 | lastBucket := 0.0
52 | for _, bucket := range buckets {
53 | Expect(bucket).To(BeNumerically(">", lastBucket))
54 | lastBucket = bucket
55 | }
56 | })
57 |
58 | It("should include a bucket for the scrape timeout", func() {
59 | Expect(BucketsForScrapeDuration(5 * time.Second)).To(ContainElement(5.0))
60 | })
61 | })
62 | Context("with a scrape timeout equals to the max default bucket", func() {
63 | maxBucket := metrics.DefBuckets[len(metrics.DefBuckets)-1]
64 | maxBucketDuration := time.Duration(maxBucket) * time.Second
65 |
66 | It("should generate buckets in strictly increasing order", func() {
67 | buckets := BucketsForScrapeDuration(maxBucketDuration)
68 | lastBucket := 0.0
69 | for _, bucket := range buckets {
70 | Expect(bucket).To(BeNumerically(">", lastBucket))
71 | lastBucket = bucket
72 | }
73 | })
74 |
75 | It("should include a bucket for the scrape timeout", func() {
76 | Expect(BucketsForScrapeDuration(maxBucketDuration)).To(ContainElement(maxBucket))
77 | })
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/scripts/boilerplate.go.txt:
--------------------------------------------------------------------------------
1 | // Copyright The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
--------------------------------------------------------------------------------
/scripts/go.mod:
--------------------------------------------------------------------------------
1 | module sigs.k8s.io/metrics-server/scripts
2 |
3 | go 1.24.2
4 |
5 | require (
6 | github.com/google/addlicense v1.1.1
7 | golang.org/x/perf v0.0.0-20230822165715-3c60af34b3f4
8 | k8s.io/kube-openapi v0.0.0-20230816210353-14e408962443
9 | sigs.k8s.io/logtools v0.9.0
10 | sigs.k8s.io/mdtoc v1.4.0
11 | )
12 |
13 | require (
14 | github.com/BurntSushi/toml v0.3.1 // indirect
15 | github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect
16 | github.com/bmatcuk/doublestar/v4 v4.0.2 // indirect
17 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
18 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect
19 | github.com/go-logr/logr v0.2.0 // indirect
20 | github.com/go-openapi/jsonpointer v0.19.6 // indirect
21 | github.com/go-openapi/jsonreference v0.20.1 // indirect
22 | github.com/go-openapi/swag v0.22.3 // indirect
23 | github.com/golang/protobuf v1.5.2 // indirect
24 | github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect
25 | github.com/google/gnostic-models v0.6.8 // indirect
26 | github.com/google/go-cmp v0.6.0 // indirect
27 | github.com/google/gofuzz v1.1.0 // indirect
28 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
29 | github.com/josharian/intern v1.0.0 // indirect
30 | github.com/json-iterator/go v1.1.12 // indirect
31 | github.com/mailru/easyjson v0.7.7 // indirect
32 | github.com/mmarkdown/mmark v2.0.40+incompatible // indirect
33 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
34 | github.com/modern-go/reflect2 v1.0.2 // indirect
35 | github.com/rogpeppe/go-internal v1.12.0 // indirect
36 | github.com/spf13/cobra v1.8.1 // indirect
37 | github.com/spf13/pflag v1.0.5 // indirect
38 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
39 | golang.org/x/mod v0.17.0 // indirect
40 | golang.org/x/sync v0.7.0 // indirect
41 | golang.org/x/tools v0.21.0 // indirect
42 | google.golang.org/protobuf v1.27.1 // indirect
43 | gopkg.in/yaml.v3 v3.0.1 // indirect
44 | k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect
45 | k8s.io/klog/v2 v2.2.0 // indirect
46 | sigs.k8s.io/release-utils v0.8.3 // indirect
47 | )
48 |
--------------------------------------------------------------------------------
/scripts/tools.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Kubernetes Authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // +build tools
16 |
17 | // Package tools tracks dependencies for tools that used in the build process.
18 | // See https://github.com/golang/go/wiki/Modules
19 | package tools
20 |
21 | import (
22 | _ "github.com/google/addlicense"
23 | _ "golang.org/x/perf/cmd/benchstat"
24 | _ "sigs.k8s.io/logtools/logcheck"
25 | _ "k8s.io/kube-openapi/cmd/openapi-gen"
26 | _ "sigs.k8s.io/mdtoc"
27 | )
28 |
--------------------------------------------------------------------------------
/skaffold.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v2beta29
2 | kind: Config
3 | metadata:
4 | name: metrics-server
5 | build:
6 | local:
7 | useDockerCLI: true
8 | useBuildkit: true
9 | artifacts:
10 | - image: gcr.io/k8s-staging-metrics-server/metrics-server
11 | docker:
12 | dockerfile: Dockerfile
13 | buildArgs:
14 | ARCH: "amd64"
15 | GIT_TAG: "devel"
16 |
17 | profiles:
18 | - name: test
19 | deploy:
20 | kustomize:
21 | paths:
22 | - manifests/overlays/test
23 | - name: test-ha
24 | deploy:
25 | kustomize:
26 | paths:
27 | - manifests/overlays/test-ha
28 | - name: helm
29 | deploy:
30 | helm:
31 | releases:
32 | - name: metrics-server
33 | namespace: kube-system
34 | artifactOverrides:
35 | imageKey: gcr.io/k8s-staging-metrics-server/metrics-server
36 | chartPath: charts/metrics-server
37 | setValueTemplates:
38 | image.repository: "{{.IMAGE_REPO}}"
39 | image.tag: "{{.IMAGE_TAG}}"
40 | setValues:
41 | podLabels: { k8s-app: metrics-server }
42 | containerPort: 10250
43 | valuesFiles:
44 | - test/chart-values.yaml
45 |
--------------------------------------------------------------------------------
/test/chart-values.yaml:
--------------------------------------------------------------------------------
1 | args:
2 | - --kubelet-insecure-tls
3 | - --node-selector=metrics-server-skip!=true
4 |
--------------------------------------------------------------------------------
/test/kind-config-with-sidecar-containers.yaml:
--------------------------------------------------------------------------------
1 | kind: Cluster
2 | apiVersion: kind.x-k8s.io/v1alpha4
3 | featureGates:
4 | "SidecarContainers": true
5 | nodes:
6 | - role: control-plane
7 | kubeadmConfigPatches:
8 | - |
9 | kind: ClusterConfiguration
10 | apiServer:
11 | extraArgs:
12 | "enable-aggregator-routing": "true"
13 | - role: worker
14 | - role: worker
15 | labels:
16 | metrics-server-skip: true
17 |
18 |
--------------------------------------------------------------------------------
/test/kind-config.yaml:
--------------------------------------------------------------------------------
1 | kind: Cluster
2 | apiVersion: kind.x-k8s.io/v1alpha4
3 | nodes:
4 | - role: control-plane
5 | kubeadmConfigPatches:
6 | - |
7 | kind: ClusterConfiguration
8 | apiServer:
9 | extraArgs:
10 | "enable-aggregator-routing": "true"
11 | - role: worker
12 | - role: worker
13 | labels:
14 | metrics-server-skip: true
15 |
--------------------------------------------------------------------------------
/test/test-e2e.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | : ${NODE_IMAGE:?Need to set NODE_IMAGE to test}
6 | : ${SKAFFOLD_PROFILE:="test"}
7 | : ${KIND_CONFIG:="$PWD/test/kind-config.yaml"}
8 |
9 |
10 | KIND_VERSION=0.27.0
11 | SKAFFOLD_VERSION=2.10.1
12 | HELM_VERSION=3.14.3
13 | KUBECTL_VERSION=1.32.3
14 | case $(uname -m) in
15 | x86_64)
16 | ARCH="amd64"
17 | ;;
18 | arm64)
19 | ARCH="arm64"
20 | ;;
21 | *)
22 | echo "Unsupported architecture $(uname -m). Only x86_64 and arm64 are supported."
23 | exit 1
24 | ;;
25 | esac
26 | OS=$(uname -s | tr '[:upper:]' '[:lower:]')
27 |
28 | delete_cluster() {
29 | ${KIND} delete cluster --name=e2e &> /dev/null || true
30 | }
31 |
32 | setup_helm() {
33 | HELM=$(command -v helm || true)
34 | if [[ ${HELM} == "" || $(${HELM} |grep Version |awk -F'Version:' '{print $2}' |awk -F',' '{print $1}') != "\"v${HELM_VERSION}\"" ]] ; then
35 | HELM=_output/helm
36 | fi
37 | if ! [[ $(${HELM} version |grep Version |awk -F'Version:' '{print $2}' |awk -F',' '{print $1}') == "\"v${HELM_VERSION}\"" ]] ; then
38 | echo "helm not found or bad version, downloading binary"
39 | mkdir -p _output
40 | curl -sL "https://get.helm.sh/helm-v${HELM_VERSION}-${OS}-${ARCH}.tar.gz" | tar xz -C _output --strip-components 1
41 | chmod +x _output/helm
42 | HELM=_output/helm
43 | fi
44 | }
45 |
46 | setup_kind() {
47 | KIND=$(command -v kind || true)
48 | if [[ ${KIND} == "" || $(${KIND} --version) != "kind version ${KIND_VERSION}" ]] ; then
49 | KIND=_output/kind
50 | fi
51 | if ! [[ $(${KIND} --version) == "kind version ${KIND_VERSION}" ]] ; then
52 | echo "kind not found or bad version, downloading binary"
53 | mkdir -p _output
54 | curl -sLo _output/kind "https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-${OS}-${ARCH}"
55 | chmod +x _output/kind
56 | KIND=_output/kind
57 | fi
58 | }
59 |
60 | setup_skaffold() {
61 | SKAFFOLD=$(command -v skaffold || true)
62 | if [[ ${SKAFFOLD} == "" || $(${SKAFFOLD} version) != "v${SKAFFOLD_VERSION}" ]] ; then
63 | SKAFFOLD=_output/skaffold
64 | fi
65 | if ! [[ $(${SKAFFOLD} version) == "v${SKAFFOLD_VERSION}" ]] ; then
66 | echo "skaffold not found or bad version, downloading binary"
67 | mkdir -p _output
68 | curl -sLo _output/skaffold "https://storage.googleapis.com/skaffold/releases/v${SKAFFOLD_VERSION}/skaffold-${OS}-${ARCH}"
69 | chmod +x _output/skaffold
70 | SKAFFOLD=_output/skaffold
71 | fi
72 | }
73 |
74 | get_kubectl_version() {
75 | ${KUBECTL} version --client --short 2>&1 | grep 'Client Version: ' | sed 's/^.*: //'
76 | }
77 |
78 | setup_kubectl() {
79 | KUBECTL=$(command -v kubectl || true)
80 | if [[ ${KUBECTL} == "" || $(get_kubectl_version) != "v${KUBECTL_VERSION}" ]] ; then
81 | KUBECTL=_output/kubectl
82 | fi
83 | if ! [[ $(get_kubectl_version) == "v${KUBECTL_VERSION}" ]] ; then
84 | echo "kubectl not found or bad version, downloading binary"
85 | mkdir -p _output
86 | curl -sLo _output/kubectl "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/${OS}/${ARCH}/kubectl"
87 | chmod +x _output/kubectl
88 | KUBECTL=_output/kubectl
89 | fi
90 | }
91 |
92 | create_cluster() {
93 | if ! (${KIND} create cluster --name=e2e --image=${NODE_IMAGE} --config=${KIND_CONFIG}) ; then
94 | echo "Could not create KinD cluster"
95 | exit 1
96 | fi
97 | }
98 |
99 | deploy_metrics_server(){
100 | PATH="$PWD/_output:${PATH}" ${SKAFFOLD} run -p "${SKAFFOLD_PROFILE}"
101 | sleep 5
102 | }
103 |
104 | run_tests() {
105 | go test test/e2e_test.go -v -count=1
106 | }
107 |
108 | upload_metrics_server_logs(){
109 | ${KUBECTL} logs -n kube-system -l "k8s-app=metrics-server" > $ARTIFACTS/metrics-server.log
110 | }
111 |
112 | if [ "${SKAFFOLD_PROFILE}" = "helm" ] ; then
113 | setup_helm
114 | fi
115 | setup_kind
116 | setup_skaffold
117 | trap delete_cluster EXIT
118 | delete_cluster
119 | create_cluster
120 | deploy_metrics_server
121 | run_tests
122 |
123 | if [[ ${ARTIFACTS} != "" ]] ; then
124 | setup_kubectl
125 | upload_metrics_server_logs
126 | fi
127 |
--------------------------------------------------------------------------------
/test/test-image.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | GOARCH=$(go env GOARCH)
6 |
7 | : ${CONTAINER_CLI:?Need to provide environment variable CONTAINER_CLI}
8 | : ${IMAGE:?Need to provide environment variable IMAGE}
9 | : ${EXPECTED_ARCH:?Need to provide variable EXPECTED_ARCH}
10 | : ${EXPECTED_VERSION:?Need to provide environment variable EXPECTED_VERSION}
11 |
12 | echo "CONTAINER CLI ${CONTAINER_CLI}"
13 |
14 | IMAGE_ARCH=$(${CONTAINER_CLI} inspect ${IMAGE} | jq -r ".[].Architecture")
15 | echo "Image architecture ${IMAGE_ARCH}"
16 |
17 | if [[ "${IMAGE_ARCH}" != "${EXPECTED_ARCH}" ]] ; then
18 | echo "Unexpected architecture, got ${IMAGE_ARCH}, expected ${EXPECTED_ARCH}"
19 | exit 1
20 | fi
21 |
22 | if [[ "${IMAGE_ARCH}" == "${GOARCH}" ]] ; then
23 | CONTAINER_VERSION=$(${CONTAINER_CLI} run --rm ${IMAGE} --version)
24 | echo "Image version ${CONTAINER_VERSION}"
25 |
26 | if [[ "${CONTAINER_VERSION}" != "${EXPECTED_VERSION}" ]] ; then
27 | echo "Unexpected binary version, got ${CONTAINER_VERSION}, expected ${EXPECTED_VERSION}"
28 | exit 1
29 | fi
30 |
31 | CLI_HELP="$(${CONTAINER_CLI} run --rm ${IMAGE} --help | sed 's/[ \t]*$//')"
32 | EXPECTED_CLI_HELP="$(cat ./docs/command-line-flags.txt)"
33 | echo "Image help ${CLI_HELP}"
34 |
35 | DIFF=$(diff -u <(echo "${EXPECTED_CLI_HELP}") <(echo "${CLI_HELP}") | tail -n +3 || true)
36 | if [ "$DIFF" ]; then
37 | echo "Unexpected cli help, diff:"
38 | echo "$DIFF"
39 | exit 1
40 | fi
41 | fi
42 |
43 |
--------------------------------------------------------------------------------