Repository navigation
fix(config): make the client-go rate limit configurable (kubevirt.qps, kubevirt.burst) - #30
itayherzberg wants to merge 2 commits into
Conversation
|
Hi @itayherzberg. Thanks for your PR. PRs from untrusted users cannot be marked as trusted with I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
…ory, write tracker dropped Picks up bd12d08, the review revision of kubevirt#32. Conflicts were the adjacent hunks the rebase note in SCALEFIX-CHANGES.md describes: kept kubevirt#30/kubevirt#31's qps/burst/workers lines and their shared test helpers, dropped cache_enabled and the write tracker. # Conflicts: # config/config.yaml # helm/templates/secret.yaml # helm/values-example.yaml # helm/values.yaml # pkg/config/config.go # pkg/config/config_test.go # pkg/kubevirt/client.go
|
@MarSik Gentle ping on this when you have a chance! Let me know if you need any additional context |
|
/cc @0xFelix |
|
/lgtm |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: MarSik The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
@itayherzberg: The following tests failed, say
DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
…s, kubevirt.burst) The Kubernetes client used client-go's default limiter of 5 requests/s, burst 10, a default sized for kubectl. Every Redfish request fans out into several API calls (GET System: 6, boot-device PATCH: 12), so with many BareMetalHosts polling at once requests queue for tens of seconds and run past the OpenShift route's 30s timeout. Add kubevirt.qps / kubevirt.burst (and the matching KUBEVIRT_REDFISH_KUBEVIRT_QPS / _BURST environment variables, through the viper mapping every other key already has). Unset or zero keeps client-go's defaults, so nothing changes unless an operator raises them; negative values are rejected. The value in use is logged at startup, which required creating the client after the logger is initialized in cmd/main.go. Signed-off-by: itayherzberg <itayherzberg@gmail.com>
cdf6a51 to
1d7b14f
Compare
|
New changes are detected. LGTM label has been removed. |
|
@itayherzberg: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
| // Zero means "not set" (client-go's own convention): apply the defaults. | ||
| if kv.QPS == 0 { | ||
| kv.QPS = DefaultKubeClientQPS | ||
| } | ||
| if kv.Burst == 0 { | ||
| kv.Burst = DefaultKubeClientBurst | ||
| } |
There was a problem hiding this comment.
Drop this and only reject negatives here? kubeClientRateLimits already falls back to the defaults for <= 0, and SetDefault covers the unset case, so the validator wouldn't need to mutate the config.
There was a problem hiding this comment.
Done in d2951f1: validateKubeVirtConfig only rejects negative values now and no longer mutates the config. The zero fallback lives in kubeClientRateLimits alone. With that the two DefaultKubeClient* constants had no purpose left, so I removed them and SetDefault uses rest.DefaultQPS / rest.DefaultBurst directly.
| QPS float32 `mapstructure:"qps"` | ||
| Burst int `mapstructure:"burst"` |
There was a problem hiding this comment.
Mention that changing these needs a restart? The config is hot-reloaded via watchConfigFile, but the client is only built once at startup. Same for the comment in helm/values.yaml.
There was a problem hiding this comment.
Good catch, the client is created once in main and a config reload does not touch it. Added a note that a change needs a restart, both to the KubeVirtConfig comment and to helm/values.yaml (d2951f1).
| # defaults saturate at ~15 BMHs. Watch the pod log for "Waited for ... due | ||
| # to client-side throttling"; if it appears under load, raise these |
There was a problem hiding this comment.
With client-go v0.34 the log line is "Waited before sending request" ... reason="client-side throttling..." (as in the PR description). Maybe just say to grep for client-side throttling?
There was a problem hiding this comment.
Right, the line I quoted is the wording of older client-go versions. The comment now just says to grep the pod log for client-side throttling (d2951f1).
| // kubeClientRateLimits returns the client-go QPS and burst for the Kubernetes | ||
| // API client. They come from kubevirt.qps / kubevirt.burst in the app config | ||
| // (or KUBEVIRT_REDFISH_KUBEVIRT_QPS / _BURST). Without a config, or for a | ||
| // value left at zero, client-go's own defaults apply. Large deployments need | ||
| // higher values: every Redfish request fans out to several API calls, and with | ||
| // many BMHs polling at once the default limiter queues requests long enough | ||
| // for the OpenShift route to time out. |
There was a problem hiding this comment.
nit: The fan-out / many BMHs explanation is repeated in 4 places (here, KubeVirtConfig, the defaults const block and helm/values.yaml). Keep it in one spot?
There was a problem hiding this comment.
Agreed. The explanation now lives only in helm/values.yaml, where an operator sizing the values will read it. The comments on kubeClientRateLimits and KubeVirtConfig are trimmed to what the code does (the struct comment points to the values file), and the constants block is gone (d2951f1).
…restart Review follow-up: - validateKubeVirtConfig only rejects negative kubevirt.qps / burst and no longer rewrites zero to the defaults; kubeClientRateLimits already falls back to client-go's defaults for zero, and SetDefault covers unset keys. - Note that the client is built once at startup, so a change to these values needs a restart even though the config file is hot-reloaded. - Point at the "client-side throttling" log text instead of quoting a log line whose wording differs between client-go versions. - Keep the fan-out / sizing explanation in helm/values.yaml only. Signed-off-by: itayherzberg <itayherzberg@gmail.com>
Problem
With about 15 BareMetalHosts provisioning at the same time through one kubevirt-redfish instance, Redfish requests took 10–35 seconds. Ironic's boot-device
PATCHran past the OpenShift route's 30-second timeout, got a 504, and inspection looped forever.Cause
The Kubernetes client is created with client-go's default rate limiter: 5 requests/second, burst 10. That default is sized for
kubectl, not for a server. Every Redfish request fans out into several Kubernetes API calls (GET /Systems/{id}is 6: one to resolve the system name, then the VM four times and the VMI once; a boot-devicePATCHis 12), and Ironic polls every host it manages. With N hosts in flight the slowest request waits roughly12 × N / 5seconds: at 15 hosts, ~36 s. client-go reports the waits asclient-side throttling.Reproduced on OpenShift 4.22 / OpenShift Virtualization 4.22.12 with 100 ×
GET /redfish/v1/Systems/{id}, 20 in parallel, on this branch with the limiter left at its default (identical to upstream behaviour):(10 such lines in the pod log during the run)
Fix
Make the limiter configurable. The default is not changed.
kubevirt.qps/kubevirt.burstin the config file. They default to client-go's ownrest.DefaultQPS/rest.DefaultBurst(5 / 10); unset or0keeps the default, negative values fail validation.KUBEVIRT_REDFISH_KUBEVIRT_QPS/KUBEVIRT_REDFISH_KUBEVIRT_BURSToverride the file. No extra code: this is the viper environment mapping every other key already has.pkg/kubevirt/client.go:NewClientsetsrest.Config.QPS/Burstfrom the pure helperkubeClientRateLimits, which reads the values through a small interface (GetKubeClientRateLimits()), the same way the client already readsGetDataVolumeConfig(). The value in use is logged at startup:Kubernetes client rate limits: qps=… burst=….cmd/main.go: the client is created after the logger is initialized. Before, it was created first, so anything the client logged at startup was lost.kubevirt.qps/kubevirt.burstvalues (default0, which leaves the keys out of the renderedconfig.yaml);values-example.yamlshows 100 / 200.Sizing for many concurrent hosts: with
qps: 100, burst: 200the worst request is ~12 × N / 100s. 100 requests/second at ~20 ms each is about two concurrent requests against an API server that allows thousands in flight. Ifclient-side throttlingstill appears in the log under load, the limit is too low.Verification on the cluster
Same load, same cluster, with
kubevirt.qps: 100,kubevirt.burst: 200in the chart values:Environment wins over the file: file at 100 / 200 plus
KUBEVIRT_REDFISH_KUBEVIRT_QPS=50→Kubernetes client rate limits: qps=50 burst=200.Invalid value is rejected at startup:
KUBEVIRT_REDFISH_KUBEVIRT_BURST=-5→Failed to load configuration: … Invalid KubeVirt burst (kubevirt.burst must be greater than 0, got -5).Tests
pkg/config/config_test.go: defaults are 5 / 10; values from the file and from the environment; negative values rejected; zero means default.pkg/kubevirt/ratelimit_test.go:kubeClientRateLimitswith no config, a config without the method, configured values, and per-field zero fallback.go build ./...,go vet ./...,go test ./...,gofmt,go mod tidy(no change) andhelm lintpass;helm templaterendersqps/burstonly when set.Related
One of four independent PRs from running many concurrent BMHs against kubevirt-redfish; each stands on its own and they can merge in any order: #31 (task worker pool), #32 (VM/VMI informer cache), #29 (shutdown hang). The three config PRs add the same two test helpers (
minimalConfig,writeConfig) topkg/config/config_test.go; whichever lands later drops its copy on rebase.