Skip to content

Commit 915ef4e

Browse files
committed
feat: Introduce object limits
This change allows user-controlled limits on how many objects KSM will list from the API. This is helpful to prevent resource exhaustion on KSM, in case the API creates too many resources. The object limit it set globally and applied per resource watched.
1 parent 3d73ddb commit 915ef4e

File tree

7 files changed

+76
-47
lines changed

7 files changed

+76
-47
lines changed

docs/developer/cli-arguments.md

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Flags:
6464
--namespaces string Comma-separated list of namespaces to be enabled. Defaults to ""
6565
--namespaces-denylist string Comma-separated list of namespaces not to be enabled. If namespaces and namespaces-denylist are both set, only namespaces that are excluded in namespaces-denylist will be used.
6666
--node string Name of the node that contains the kube-state-metrics pod. Most likely it should be passed via the downward API. This is used for daemonset sharding. Only available for resources (pod metrics) that support spec.nodeName fieldSelector. This is experimental.
67+
--object-limit int The total number of objects to list from the API Server.
6768
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)
6869
--pod string Name of the pod that contains the kube-state-metrics container. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice.
6970
--pod-namespace string Name of the namespace of the pod specified by --pod. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice.

internal/store/builder.go

+51-42
Large diffs are not rendered by default.

pkg/app/server.go

+1
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options) error {
247247
))
248248

249249
storeBuilder.WithUsingAPIServerCache(opts.UseAPIServerCache)
250+
storeBuilder.WithObjectLimit(opts.ObjectLimit)
250251
storeBuilder.WithGenerateStoresFunc(storeBuilder.DefaultGenerateStoresFunc())
251252
proc.StartReaper()
252253

pkg/builder/builder_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func customStore(_ []generator.FamilyGenerator,
6767
_ interface{},
6868
_ func(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher,
6969
_ bool,
70+
_ int64,
7071
) []cache.Store {
7172
stores := make([]cache.Store, 0, 2)
7273
stores = append(stores, newFakeStore(fakeMetricLists[0]))

pkg/builder/types/interfaces.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ type BuilderInterface interface {
5757
type BuildStoresFunc func(metricFamilies []generator.FamilyGenerator,
5858
expectedType interface{},
5959
listWatchFunc func(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher,
60-
useAPIServerCache bool,
60+
useAPIServerCache bool, limit int64,
6161
) []cache.Store
6262

6363
// BuildCustomResourceStoresFunc function signature that is used to return a list of custom resource cache.Store
6464
type BuildCustomResourceStoresFunc func(resourceName string,
6565
metricFamilies []generator.FamilyGenerator,
6666
expectedType interface{},
6767
listWatchFunc func(customResourceClient interface{}, ns string, fieldSelector string) cache.ListerWatcher,
68-
useAPIServerCache bool,
68+
useAPIServerCache bool, limit int64,
6969
) []cache.Store
7070

7171
// AllowDenyLister interface for AllowDeny lister that can allow or exclude metrics by there names

pkg/options/options.go

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ type Options struct {
7979
Help bool `yaml:"help"`
8080
TrackUnscheduledPods bool `yaml:"track_unscheduled_pods"`
8181
UseAPIServerCache bool `yaml:"use_api_server_cache"`
82+
ObjectLimit int64 `yaml:"object_limit"`
8283
}
8384

8485
// GetConfigFile is the getter for --config value.
@@ -143,6 +144,7 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
143144
o.cmd.Flags().BoolVar(&o.TrackUnscheduledPods, "track-unscheduled-pods", false, "This configuration is used in conjunction with node configuration. When this configuration is true, node configuration is empty and the metric of unscheduled pods is fetched from the Kubernetes API Server. This is experimental.")
144145
o.cmd.Flags().BoolVarP(&o.Help, "help", "h", false, "Print Help text")
145146
o.cmd.Flags().BoolVarP(&o.UseAPIServerCache, "use-apiserver-cache", "", false, "Sets resourceVersion=0 for ListWatch requests, using cached resources from the apiserver instead of an etcd quorum read.")
147+
o.cmd.Flags().Int64Var(&o.ObjectLimit, "object-limit", 0, "The total number of objects to list from the API Server.")
146148
o.cmd.Flags().Int32Var(&o.Shard, "shard", int32(0), "The instances shard nominal (zero indexed) within the total number of shards. (default 0)")
147149
o.cmd.Flags().IntVar(&o.Port, "port", 8080, `Port to expose metrics on.`)
148150
o.cmd.Flags().IntVar(&o.TelemetryPort, "telemetry-port", 8081, `Port to expose kube-state-metrics self metrics on.`)

pkg/watch/watch.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ import (
2727

2828
// ListWatchMetrics stores the pointers of kube_state_metrics_[list|watch]_total metrics.
2929
type ListWatchMetrics struct {
30-
WatchTotal *prometheus.CounterVec
31-
ListTotal *prometheus.CounterVec
30+
WatchTotal *prometheus.CounterVec
31+
ListTotal *prometheus.CounterVec
32+
ListLimitTotal *prometheus.GaugeVec
3233
}
3334

3435
// NewListWatchMetrics takes in a prometheus registry and initializes
@@ -50,6 +51,13 @@ func NewListWatchMetrics(r prometheus.Registerer) *ListWatchMetrics {
5051
},
5152
[]string{"result", "resource"},
5253
),
54+
ListLimitTotal: promauto.With(r).NewGaugeVec(
55+
prometheus.GaugeOpts{
56+
Name: "kube_state_metrics_list_limit",
57+
Help: "Number of resource list limit in kube-state-metrics",
58+
},
59+
[]string{"resource"},
60+
),
5361
}
5462
}
5563

@@ -60,15 +68,17 @@ type InstrumentedListerWatcher struct {
6068
metrics *ListWatchMetrics
6169
resource string
6270
useAPIServerCache bool
71+
limit int64
6372
}
6473

6574
// NewInstrumentedListerWatcher returns a new InstrumentedListerWatcher.
66-
func NewInstrumentedListerWatcher(lw cache.ListerWatcher, metrics *ListWatchMetrics, resource string, useAPIServerCache bool) cache.ListerWatcher {
75+
func NewInstrumentedListerWatcher(lw cache.ListerWatcher, metrics *ListWatchMetrics, resource string, useAPIServerCache bool, limit int64) cache.ListerWatcher {
6776
return &InstrumentedListerWatcher{
6877
lw: lw,
6978
metrics: metrics,
7079
resource: resource,
7180
useAPIServerCache: useAPIServerCache,
81+
limit: limit,
7282
}
7383
}
7484

@@ -80,6 +90,11 @@ func (i *InstrumentedListerWatcher) List(options metav1.ListOptions) (runtime.Ob
8090
options.ResourceVersion = "0"
8191
}
8292

93+
if i.limit != 0 {
94+
options.Limit = i.limit
95+
i.metrics.ListLimitTotal.WithLabelValues(i.resource).Set(float64(i.limit))
96+
}
97+
8398
res, err := i.lw.List(options)
8499
if err != nil {
85100
i.metrics.ListTotal.WithLabelValues("error", i.resource).Inc()

0 commit comments

Comments
 (0)