mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 20:00:07 +00:00
This fixes a bug that caused log calls involving `klog.Logger` to not be
checked.
As a result we have to fix some code that is now considered faulty:
ERROR: pkg/controller/serviceaccount/tokens_controller.go:382:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (e *TokensController) generateTokenIfNeeded(ctx context.Context, logger klog.Logger, serviceAccount *v1.ServiceAccount, cachedSecret *v1.Secret) ( /* retry */ bool, error) {
ERROR: ^
ERROR: pkg/controller/storageversionmigrator/storageversionmigrator.go:299:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (svmc *SVMController) runMigration(ctx context.Context, logger klog.Logger, gvr schema.GroupVersionResource, resourceMonitor *garbagecollector.Monitor, toBeProcessedSVM *svmv1beta1.StorageVersionMigration, listResourceVersion string) (err error, failed bool) {
ERROR: ^
ERROR: pkg/proxy/node.go:121:3: logging function "Error" should not use format specifier "%q" (logcheck)
ERROR: klog.FromContext(ctx).Error(nil, "Timed out waiting for node %q to exist", nodeName)
ERROR: ^
ERROR: pkg/proxy/node.go:123:3: logging function "Error" should not use format specifier "%q" (logcheck)
ERROR: klog.FromContext(ctx).Error(nil, "Timed out waiting for node %q to be assigned IPs", nodeName)
ERROR: ^
ERROR: pkg/scheduler/backend/queue/scheduling_queue.go:610:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (p *PriorityQueue) runPreEnqueuePlugin(ctx context.Context, logger klog.Logger, pl fwk.PreEnqueuePlugin, pInfo *framework.QueuedPodInfo, shouldRecordMetric bool) *fwk.Status {
ERROR: ^
ERROR: pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go:286:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (pl *DynamicResources) deleteClaim(ctx context.Context, claim *resourceapi.ResourceClaim, logger klog.Logger) error {
ERROR: ^
ERROR: pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go:499:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (pl *DynamicResources) waitForExtendedClaimInAssumeCache(
ERROR: ^
ERROR: pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go:528:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (pl *DynamicResources) createExtendedResourceClaimInAPI(
ERROR: ^
ERROR: pkg/scheduler/framework/plugins/dynamicresources/extendeddynamicresources.go:592:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (pl *DynamicResources) unreserveExtendedResourceClaim(ctx context.Context, logger klog.Logger, pod *v1.Pod, state *stateData) {
ERROR: ^
ERROR: pkg/scheduler/framework/runtime/batch.go:171:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (b *OpportunisticBatch) batchStateCompatible(ctx context.Context, logger klog.Logger, pod *v1.Pod, signature fwk.PodSignature, cycleCount int64, state fwk.CycleState, nodeInfos fwk.NodeInfoLister) bool {
ERROR: ^
ERROR: staging/src/k8s.io/component-base/featuregate/feature_gate.go:890:4: Additional arguments to Info should always be Key Value pairs. Please check if there is any key or value missing. (logcheck)
ERROR: logger.Info("Warning: SetEmulationVersionAndMinCompatibilityVersion will change already queried feature", "featureGate", feature, "oldValue", oldVal, newVal)
ERROR: ^
ERROR: test/images/sample-device-plugin/sampledeviceplugin.go:108:2: logging function "Info" should not use format specifier "%s" (logcheck)
ERROR: logger.Info("pluginSocksDir: %s", pluginSocksDir)
ERROR: ^
ERROR: test/images/sample-device-plugin/sampledeviceplugin.go:123:2: logging function "Info" should not use format specifier "%s" (logcheck)
ERROR: logger.Info("CDI_ENABLED: %s", cdiEnabled)
ERROR: ^
While waiting for this to merge, another call was added which also doesn't
follow conventions:
ERROR: pkg/kubelet/kubelet.go:2454:1: A function should accept either a context or a logger, but not both. Having both makes calling the function harder because it must be defined whether the context must contain the logger and callers have to follow that. (logcheck)
ERROR: func (kl *Kubelet) deletePod(ctx context.Context, logger klog.Logger, pod *v1.Pod) error {
ERROR: ^
Contextual logging has been beta and enabled by default for several releases
now. It's mostly just a matter of wrapping up and declaring it GA. Therefore
the calls which directly call WithName or WithValues (always have an effect)
are left as-is instead of converting them to use the klog wrappers (support
disabling the effect). To allow that, the linter gets reconfigured to not
complain about this anymore, anywhere.
The calls which would have to be fixed otherwise are:
ERROR: pkg/kubelet/cm/dra/claiminfo.go:170:11: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger = logger.WithName("dra-claiminfo")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/healthinfo.go:45:11: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger = logger.WithName("dra-healthinfo")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/healthinfo.go:89:11: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger = logger.WithName("dra-healthinfo")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/healthinfo.go:157:11: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger = logger.WithName("dra-healthinfo")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/manager.go:175:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(ctx).WithName("dra-manager")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/manager.go:239:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(ctx).WithName("dra-manager")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/manager.go:593:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(ctx).WithName("dra-manager")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/manager.go:781:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(context.Background()).WithName("dra-manager")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/manager.go:898:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(ctx).WithName("dra-manager")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/manager_test.go:1638:15: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(streamCtx).WithName(st.Name())
ERROR: ^
ERROR: pkg/kubelet/cm/dra/plugin/dra_plugin.go:77:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(ctx).WithName("dra-plugin")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/plugin/dra_plugin.go:108:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(ctx).WithName("dra-plugin")
ERROR: ^
ERROR: pkg/kubelet/cm/dra/plugin/dra_plugin.go:161:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger := klog.FromContext(ctx).WithName("dra-plugin")
ERROR: ^
ERROR: staging/src/k8s.io/dynamic-resource-allocation/resourceslice/tracker/tracker.go:695:14: function "WithValues" should be called through klogr.LoggerWithValues (logcheck)
ERROR: logger := logger.WithValues("device", deviceID)
ERROR: ^
ERROR: test/integration/apiserver/watchcache_test.go:42:54: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: etcd0URL, stopEtcd0, err := framework.RunCustomEtcd(klog.FromContext(ctx).WithName("etcd0"), "etcd_watchcache0", etcdArgs)
ERROR: ^
ERROR: test/integration/apiserver/watchcache_test.go:47:54: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: etcd1URL, stopEtcd1, err := framework.RunCustomEtcd(klog.FromContext(ctx).WithName("etcd1"), "etcd_watchcache1", etcdArgs)
ERROR: ^
ERROR: test/integration/scheduler_perf/scheduler_perf.go:1149:12: function "WithName" should be called through klogr.LoggerWithName (logcheck)
ERROR: logger = logger.WithName(tCtx.Name())
ERROR: ^
240 lines
9.1 KiB
Go
240 lines
9.1 KiB
Go
/*
|
|
Copyright 2025 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"time"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/klog/v2"
|
|
fwk "k8s.io/kube-scheduler/framework"
|
|
"k8s.io/kubernetes/pkg/scheduler/framework"
|
|
"k8s.io/kubernetes/pkg/scheduler/metrics"
|
|
)
|
|
|
|
// OpportunisticBatching caches results from filtering and scoring when possible to optimize
|
|
// scheduling of common pods.
|
|
type OpportunisticBatch struct {
|
|
state *batchState
|
|
lastCycle schedulingCycle
|
|
signatureFunc SignatureFunc
|
|
handle fwk.Handle
|
|
|
|
// Used primarily for tests, count the total pods we
|
|
// have successfully batched.
|
|
batchedPods int64
|
|
}
|
|
|
|
type batchState struct {
|
|
signature fwk.PodSignature
|
|
sortedNodes framework.SortedScoredNodes
|
|
creationTime time.Time
|
|
}
|
|
|
|
type schedulingCycle struct {
|
|
cycleCount int64
|
|
chosenNode string
|
|
succeeded bool
|
|
}
|
|
|
|
const (
|
|
maxBatchAge = 500 * time.Millisecond
|
|
)
|
|
|
|
type SignatureFunc func(h fwk.Handle, ctx context.Context, p *v1.Pod, recordPluginStats bool) fwk.PodSignature
|
|
|
|
func signUsingFramework(h fwk.Handle, ctx context.Context, p *v1.Pod, recordPluginStats bool) fwk.PodSignature {
|
|
return h.SignPod(ctx, p, recordPluginStats)
|
|
}
|
|
|
|
// Provide a hint for the pod based on filtering an scoring results of previous cycles. Caching works only for consecutive pods
|
|
// with the same signature that are scheduled in 1-pod-per-node manner (otherwise previous scores could not be reused).
|
|
// It's assured by checking the top rated node is no longer feasible (meaning the previous pod was successfully scheduled and the
|
|
// current one does not fit).
|
|
func (b *OpportunisticBatch) GetNodeHint(ctx context.Context, pod *v1.Pod, state fwk.CycleState, cycleCount int64) (string, fwk.PodSignature) {
|
|
logger := klog.FromContext(ctx)
|
|
var hint string
|
|
|
|
startTime := time.Now()
|
|
defer func() {
|
|
hinted := "hint"
|
|
if hint == "" {
|
|
hinted = "no_hint"
|
|
}
|
|
metrics.GetNodeHintDuration.WithLabelValues(hinted, b.handle.ProfileName()).Observe(metrics.SinceInSeconds(startTime))
|
|
}()
|
|
|
|
signature := b.signatureFunc(b.handle, ctx, pod, state.ShouldRecordPluginMetrics())
|
|
|
|
nodeInfos := b.handle.SnapshotSharedLister().NodeInfos()
|
|
|
|
// If we don't have state that we can use, then return an empty hint.
|
|
if !b.batchStateCompatible(ctx, pod, signature, cycleCount, state, nodeInfos) {
|
|
metrics.BatchAttemptStats.WithLabelValues(b.handle.ProfileName(), metrics.BatchAttemptNoHint).Inc()
|
|
logger.V(4).Info("OpportunisticBatch no node hint available for pod",
|
|
"profile", b.handle.ProfileName(), "pod", klog.KObj(pod), "cycleCount", cycleCount)
|
|
return "", signature
|
|
}
|
|
|
|
// Otherwise, pop the head of the list in our state and return it as
|
|
// a hint. Also record it in our data to compare on storage.
|
|
hint = b.state.sortedNodes.Pop()
|
|
logger.V(3).Info("OpportunisticBatch provided node hint",
|
|
"profile", b.handle.ProfileName(), "pod", klog.KObj(pod), "cycleCount", cycleCount, "hint", hint,
|
|
"remainingNodes", b.state.sortedNodes.Len())
|
|
|
|
return hint, signature
|
|
}
|
|
|
|
// Store results from scheduling for use as a batch later.
|
|
func (b *OpportunisticBatch) StoreScheduleResults(ctx context.Context, signature fwk.PodSignature, hintedNode, chosenNode string, otherNodes framework.SortedScoredNodes, cycleCount int64) {
|
|
logger := klog.FromContext(ctx)
|
|
|
|
defer metrics.StoreScheduleResultsDuration.ObserveSince(time.Now(), b.handle.ProfileName())()
|
|
// Set our cycle information for next time.
|
|
b.lastCycle = schedulingCycle{
|
|
cycleCount: cycleCount,
|
|
chosenNode: chosenNode,
|
|
succeeded: true,
|
|
}
|
|
logger.V(4).Info("OpportunisticBatch set cycle state",
|
|
"profile", b.handle.ProfileName(), "cycleCount", cycleCount, "hintedNode", hintedNode, "chosenNode", chosenNode)
|
|
|
|
if hintedNode == chosenNode {
|
|
logger.V(4).Info("OpportunisticBatch skipping set state because hint was provided",
|
|
"profile", b.handle.ProfileName(), "cycleCount", cycleCount, "chosenNode", chosenNode)
|
|
metrics.BatchAttemptStats.WithLabelValues(b.handle.ProfileName(), metrics.BatchAttemptHintUsed).Inc()
|
|
b.batchedPods++
|
|
return
|
|
}
|
|
|
|
// Only store new results if we didn't give a hint or it wasn't used.
|
|
// Track the somewhat unusual case where we gave a hint and it wasn't used.
|
|
if hintedNode != "" {
|
|
metrics.BatchAttemptStats.WithLabelValues(b.handle.ProfileName(), metrics.BatchAttemptHintNotUsed).Inc()
|
|
logger.V(4).Info("OpportunisticBatch hint not used",
|
|
"profile", b.handle.ProfileName(), "cycleCount", b.lastCycle.cycleCount, "hint", hintedNode, "chosen", chosenNode)
|
|
}
|
|
|
|
// If this pod is batchable, set our results as state.
|
|
// We will check this against the next pod when we give the
|
|
// next hint.
|
|
if signature != nil && otherNodes != nil && otherNodes.Len() > 0 {
|
|
b.state = &batchState{
|
|
signature: signature,
|
|
sortedNodes: otherNodes,
|
|
creationTime: time.Now(),
|
|
}
|
|
if loggerV := logger.V(6); loggerV.Enabled() {
|
|
loggerV.Info("OpportunisticBatch set batch information",
|
|
"profile", b.handle.ProfileName(), "signature", b.state.signature, "nodes", otherNodes.Len(), "cycleCount", cycleCount)
|
|
} else {
|
|
logger.V(4).Info("OpportunisticBatch set batch information",
|
|
"profile", b.handle.ProfileName(), "nodes", otherNodes.Len(), "cycleCount", cycleCount)
|
|
}
|
|
} else {
|
|
reason := metrics.BatchFlushPodNotBatchable
|
|
if otherNodes == nil || otherNodes.Len() == 0 {
|
|
reason = metrics.BatchFlushEmptyList
|
|
}
|
|
|
|
b.logUnusableState(logger, cycleCount, reason)
|
|
b.state = nil
|
|
}
|
|
}
|
|
|
|
// logUnusableState our batch state because we can't keep it up to date.
|
|
// Record the reason for our invalidation in the stats.
|
|
func (b *OpportunisticBatch) logUnusableState(logger klog.Logger, cycleCount int64, reason string) {
|
|
metrics.BatchCacheFlushed.WithLabelValues(b.handle.ProfileName(), reason).Inc()
|
|
logger.V(4).Info("OpportunisticBatch found unusable state",
|
|
"profile", b.handle.ProfileName(), "cycleCount", cycleCount, "reason", reason)
|
|
}
|
|
|
|
// Update the batch state based on a the arrival of a new pod and the chosen node from the last pod.
|
|
func (b *OpportunisticBatch) batchStateCompatible(ctx context.Context, pod *v1.Pod, signature fwk.PodSignature, cycleCount int64, state fwk.CycleState, nodeInfos fwk.NodeInfoLister) bool {
|
|
// Just return if we don't have any state to use.
|
|
if b.stateEmpty() {
|
|
return false
|
|
}
|
|
logger := klog.FromContext(ctx)
|
|
|
|
// In this case, a previous pod was scheduled by another profile, meaning we can't use the state anymore.
|
|
if cycleCount != b.lastCycle.cycleCount+1 {
|
|
b.logUnusableState(logger, cycleCount, metrics.BatchFlushPodSkipped)
|
|
return false
|
|
}
|
|
|
|
// If our last pod failed we can't use the state.
|
|
if !b.lastCycle.succeeded {
|
|
b.logUnusableState(logger, cycleCount, metrics.BatchFlushPodFailed)
|
|
return false
|
|
}
|
|
|
|
// If the new pod is incompatible with the current state, throw the state away.
|
|
if signature == nil || !bytes.Equal(signature, b.state.signature) {
|
|
b.logUnusableState(logger, cycleCount, metrics.BatchFlushPodIncompatible)
|
|
return false
|
|
}
|
|
|
|
// If the state is too old, throw the state away. This is to avoid
|
|
// cases where we either have huge numbers of compatible pods in a
|
|
// row or we have a long wait between pods.
|
|
if time.Now().After((b.state.creationTime.Add(maxBatchAge))) {
|
|
b.logUnusableState(logger, cycleCount, metrics.BatchFlushExpired)
|
|
return false
|
|
}
|
|
|
|
// We can only reuse the previous state if the new pod will not
|
|
// fit on the node we used for the last pod. If the node we
|
|
// chose last time can't host this pod, then we know
|
|
// that the next best should be the next node in the list.
|
|
// If we *could* put this pod on the node we just used, then we can't
|
|
// use our cache because we don't know how to rescore the used node; it
|
|
// could be the best, or it could be somewhere else.
|
|
// See git.k8s.io/enhancements/keps/sig-scheduling/5598-opportunistic-batching
|
|
lastChosenNode, err := nodeInfos.Get(b.lastCycle.chosenNode)
|
|
if lastChosenNode == nil || err != nil {
|
|
b.logUnusableState(logger, cycleCount, metrics.BatchFlushNodeMissing)
|
|
return false
|
|
}
|
|
|
|
status := b.handle.RunFilterPlugins(ctx, state, pod, lastChosenNode)
|
|
if !status.IsRejected() {
|
|
b.logUnusableState(logger, cycleCount, metrics.BatchFlushNodeNotFull)
|
|
return false
|
|
}
|
|
|
|
// Our state matches with our new pod and is useable
|
|
return true
|
|
}
|
|
|
|
// Irritatingly we can end up with a variety of different configurations that are all "empty".
|
|
// Rather than trying to normalize all cases when they happen, we just check them all.
|
|
func (b *OpportunisticBatch) stateEmpty() bool {
|
|
return b.state == nil || b.state.sortedNodes == nil || b.state.sortedNodes.Len() == 0
|
|
}
|
|
|
|
func newOpportunisticBatch(h fwk.Handle, signatureFunc SignatureFunc) *OpportunisticBatch {
|
|
return &OpportunisticBatch{
|
|
signatureFunc: signatureFunc,
|
|
handle: h,
|
|
}
|
|
}
|