diff --git a/pkg/controller/replicaset/metrics/metrics.go b/pkg/controller/replicaset/metrics/metrics.go new file mode 100644 index 00000000000..73278b05130 --- /dev/null +++ b/pkg/controller/replicaset/metrics/metrics.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package metrics + +import ( + "k8s.io/component-base/metrics" +) + +const ReplicaSetControllerSubsystem = "replicaset_controller" + +var SortingDeletionAgeRatio = metrics.NewHistogram( + &metrics.HistogramOpts{ + Subsystem: ReplicaSetControllerSubsystem, + Name: "sorting_deletion_age_ratio", + Help: "The ratio of chosen deleted pod's ages to the current youngest pod's age (at the time). Should be <2." + + "The intent of this metric is to measure the rough efficacy of the LogarithmicScaleDown feature gate's effect on" + + "the sorting (and deletion) of pods when a replicaset scales down. This only considers Ready pods when calculating and reporting.", + Buckets: metrics.ExponentialBuckets(0.25, 2, 6), + StabilityLevel: metrics.ALPHA, + }, +) + +// Register registers ReplicaSet controller metrics. +func Register(registrationFunc func(metrics.Registerable) error) error { + err := registrationFunc(SortingDeletionAgeRatio) + if err != nil { + return err + } + return nil +} diff --git a/pkg/controller/replicaset/replica_set.go b/pkg/controller/replicaset/replica_set.go index cef41bf9c90..c040ee2aa26 100644 --- a/pkg/controller/replicaset/replica_set.go +++ b/pkg/controller/replicaset/replica_set.go @@ -55,10 +55,12 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + "k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/prometheus/ratelimiter" "k8s.io/klog/v2" podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/replicaset/metrics" "k8s.io/utils/integer" ) @@ -112,6 +114,9 @@ func NewReplicaSetController(rsInformer appsinformers.ReplicaSetInformer, podInf eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartStructuredLogging(0) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) + if err := metrics.Register(legacyregistry.Register); err != nil { + klog.ErrorS(err, "unable to register metrics") + } return NewBaseController(rsInformer, podInformer, kubeClient, burstReplicas, apps.SchemeGroupVersion.WithKind("ReplicaSet"), "replicaset_controller", @@ -802,10 +807,31 @@ func getPodsToDelete(filteredPods, relatedPods []*v1.Pod, diff int) []*v1.Pod { if diff < len(filteredPods) { podsWithRanks := getPodsRankedByRelatedPodsOnSameNode(filteredPods, relatedPods) sort.Sort(podsWithRanks) + reportSortingDeletionAgeRatioMetric(filteredPods, diff) } return filteredPods[:diff] } +func reportSortingDeletionAgeRatioMetric(filteredPods []*v1.Pod, diff int) { + now := time.Now() + youngestTime := time.Time{} + // first we need to check all of the ready pods to get the youngest, as they may not necessarily be sorted by timestamp alone + for _, pod := range filteredPods { + if pod.CreationTimestamp.Time.After(youngestTime) && podutil.IsPodReady(pod) { + youngestTime = pod.CreationTimestamp.Time + } + } + + // for each pod chosen for deletion, report the ratio of its age to the youngest pod's age + for _, pod := range filteredPods[:diff] { + if !podutil.IsPodReady(pod) { + continue + } + ratio := float64(now.Sub(pod.CreationTimestamp.Time).Milliseconds() / now.Sub(youngestTime).Milliseconds()) + metrics.SortingDeletionAgeRatio.Observe(ratio) + } +} + // getPodsRankedByRelatedPodsOnSameNode returns an ActivePodsWithRanks value // that wraps podsToRank and assigns each pod a rank equal to the number of // active pods in relatedPods that are colocated on the same node with the pod. diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index ef683cda7ec..70c93a80e14 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -658,7 +658,8 @@ const ( ServiceLoadBalancerClass featuregate.Feature = "ServiceLoadBalancerClass" // owner: @damemi - // aplpha: v1.21 + // alpha: v1.21 + // beta: v1.22 // // Enables scaling down replicas via logarithmic comparison of creation/ready timestamps LogarithmicScaleDown featuregate.Feature = "LogarithmicScaleDown" @@ -844,9 +845,9 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS TopologyAwareHints: {Default: false, PreRelease: featuregate.Alpha}, PodAffinityNamespaceSelector: {Default: true, PreRelease: featuregate.Beta}, ServiceLoadBalancerClass: {Default: true, PreRelease: featuregate.Beta}, - LogarithmicScaleDown: {Default: false, PreRelease: featuregate.Alpha}, IngressClassNamespacedParams: {Default: true, PreRelease: featuregate.Beta}, ServiceInternalTrafficPolicy: {Default: true, PreRelease: featuregate.Beta}, + LogarithmicScaleDown: {Default: true, PreRelease: featuregate.Beta}, SuspendJob: {Default: true, PreRelease: featuregate.Beta}, KubeletPodResourcesGetAllocatable: {Default: false, PreRelease: featuregate.Alpha}, NamespaceDefaultLabelName: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24