Skip to content

Commit d680e8c

Browse files
authored
Merge pull request #2705 from rashmichandrashekar/rashmi/ksm-config-override
fix: Config file overrides apply to some fields but not other
2 parents 9add5df + 9211476 commit d680e8c

File tree

4 files changed

+152
-2
lines changed

4 files changed

+152
-2
lines changed

docs/developer/cli-arguments.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Flags:
4444
--auth-filter If true, requires authentication and authorization through Kubernetes API to access metrics endpoints
4545
--auto-gomemlimit Automatically set GOMEMLIMIT to match container or system memory limit. (experimental)
4646
--auto-gomemlimit-ratio float The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory. (experimental) (default 0.9)
47-
--config string Path to the kube-state-metrics options config file
47+
--config string Path to the kube-state-metrics options config YAML file. If this flag is set, the flags defined in the file override the command line flags.
4848
--custom-resource-state-config string Inline Custom Resource State Metrics config YAML (experimental)
4949
--custom-resource-state-config-file string Path to a Custom Resource State Metrics config file (experimental)
5050
--custom-resource-state-only Only provide Custom Resource State metrics (experimental)

pkg/app/server.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options) error {
149149
hash := md5HashAsMetricValue(configFile)
150150
configHash.WithLabelValues("config", filepath.Clean(got)).Set(hash)
151151
}
152+
opts = configureResourcesAndMetrics(opts, configFile)
152153
}
153154

154155
if opts.AutoGoMemlimit {
@@ -264,9 +265,12 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options) error {
264265
if err := storeBuilder.WithAllowAnnotations(opts.AnnotationsAllowList); err != nil {
265266
return fmt.Errorf("failed to set up annotations allowlist: %v", err)
266267
}
268+
klog.InfoS("Using annotations allowlist", "annotationsAllowList", opts.AnnotationsAllowList)
269+
267270
if err := storeBuilder.WithAllowLabels(opts.LabelsAllowList); err != nil {
268271
return fmt.Errorf("failed to set up labels allowlist: %v", err)
269272
}
273+
klog.InfoS("Using labels allowlist", "labelsAllowList", opts.LabelsAllowList)
270274

271275
ksmMetricsRegistry.MustRegister(
272276
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
@@ -378,6 +382,59 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options) error {
378382
return nil
379383
}
380384

385+
func configureResourcesAndMetrics(opts *options.Options, configFile []byte) *options.Options {
386+
// If the config file is set, we will overwrite the opts with the config file.
387+
// This is only needed for maps because the default behaviour of yaml.Unmarshal is to append keys (and overwrite any conflicting ones).
388+
config := options.NewOptions()
389+
err := yaml.Unmarshal(configFile, &config)
390+
if err == nil {
391+
if len(config.Resources) > 0 {
392+
opts.Resources = options.ResourceSet{}
393+
for resource := range config.Resources {
394+
opts.Resources[resource] = struct{}{}
395+
}
396+
}
397+
398+
if len(config.MetricAllowlist) > 0 {
399+
opts.MetricAllowlist = options.MetricSet{}
400+
for metric := range config.MetricAllowlist {
401+
opts.MetricAllowlist[metric] = struct{}{}
402+
}
403+
}
404+
405+
if len(config.MetricDenylist) > 0 {
406+
opts.MetricDenylist = options.MetricSet{}
407+
for metric := range config.MetricDenylist {
408+
opts.MetricDenylist[metric] = struct{}{}
409+
}
410+
}
411+
412+
if len(config.MetricOptInList) > 0 {
413+
opts.MetricOptInList = options.MetricSet{}
414+
for metric := range config.MetricOptInList {
415+
opts.MetricOptInList[metric] = struct{}{}
416+
}
417+
}
418+
419+
if len(config.LabelsAllowList) > 0 {
420+
opts.LabelsAllowList = options.LabelsAllowList{}
421+
for label, value := range config.LabelsAllowList {
422+
opts.LabelsAllowList[label] = value
423+
}
424+
}
425+
426+
if len(config.AnnotationsAllowList) > 0 {
427+
opts.AnnotationsAllowList = options.LabelsAllowList{}
428+
for annotation, value := range config.AnnotationsAllowList {
429+
opts.AnnotationsAllowList[annotation] = value
430+
}
431+
}
432+
} else {
433+
klog.ErrorS(err, "failed to unmarshal configFile")
434+
}
435+
return opts
436+
}
437+
381438
func buildTelemetryServer(registry prometheus.Gatherer, authFilter bool, kubeConfig *rest.Config) *http.ServeMux {
382439
mux := http.NewServeMux()
383440

pkg/app/server_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,3 +981,96 @@ func (f *fooFactory) ListWatch(customResourceClient interface{}, ns string, fiel
981981
},
982982
}
983983
}
984+
func TestConfigureResourcesAndMetrics(t *testing.T) {
985+
// Prepare a config file in YAML format
986+
configYAML := `
987+
"resources":
988+
"pod": {}
989+
"service": {}
990+
"metric_allowlist":
991+
"kube_pod_info": {}
992+
"metric_denylist":
993+
"kube_pod_labels": {}
994+
"metric_opt_in_list":
995+
"kube_pod_status_phase": {}
996+
"labels_allow_list":
997+
"labelX":
998+
- foo
999+
- bar
1000+
"annotations_allow_list":
1001+
"annotationY":
1002+
- baz
1003+
`
1004+
opts := options.NewOptions()
1005+
// Set some initial values to be overwritten
1006+
opts.Resources = options.ResourceSet{"oldresource": {}}
1007+
opts.MetricAllowlist = options.MetricSet{"oldallow": {}}
1008+
opts.MetricDenylist = options.MetricSet{"olddeny": {}}
1009+
opts.MetricOptInList = options.MetricSet{"oldoptin": {}}
1010+
opts.LabelsAllowList = options.LabelsAllowList{"oldlabel": {"oldvalue"}}
1011+
opts.AnnotationsAllowList = options.LabelsAllowList{"oldannotation": {"oldvalue"}}
1012+
1013+
newOpts := configureResourcesAndMetrics(opts, []byte(configYAML))
1014+
1015+
// Check resources
1016+
expectedResources := []string{"pod", "service"}
1017+
for _, r := range expectedResources {
1018+
if _, ok := newOpts.Resources[r]; !ok {
1019+
t.Errorf("expected resource %q in opts.Resources", r)
1020+
}
1021+
}
1022+
if _, ok := newOpts.Resources["oldresource"]; ok {
1023+
t.Errorf("expected oldresource to be overwritten")
1024+
}
1025+
1026+
// Check metric allowlist
1027+
if _, ok := newOpts.MetricAllowlist["kube_pod_info"]; !ok {
1028+
t.Errorf("expected kube_pod_info in MetricAllowlist")
1029+
}
1030+
if _, ok := newOpts.MetricAllowlist["oldallow"]; ok {
1031+
t.Errorf("expected oldallow to be overwritten")
1032+
}
1033+
1034+
// Check metric denylist
1035+
if _, ok := newOpts.MetricDenylist["kube_pod_labels"]; !ok {
1036+
t.Errorf("expected kube_pod_labels in MetricDenylist")
1037+
}
1038+
if _, ok := newOpts.MetricDenylist["olddeny"]; ok {
1039+
t.Errorf("expected olddeny to be overwritten")
1040+
}
1041+
1042+
// Check metric opt-in list
1043+
if _, ok := newOpts.MetricOptInList["kube_pod_status_phase"]; !ok {
1044+
t.Errorf("expected kube_pod_status_phase in MetricOptInList")
1045+
}
1046+
if _, ok := newOpts.MetricOptInList["oldoptin"]; ok {
1047+
t.Errorf("expected oldoptin to be overwritten")
1048+
}
1049+
1050+
// Check labels allow list
1051+
if vals, ok := newOpts.LabelsAllowList["labelX"]; !ok || len(vals) != 2 || vals[0] != "foo" || vals[1] != "bar" {
1052+
t.Errorf("expected labelX with values [foo bar], got %v", vals)
1053+
}
1054+
if vals, ok := newOpts.LabelsAllowList["oldlabel"]; ok {
1055+
t.Errorf("expected oldlabel to be overwritten, got %v", vals)
1056+
}
1057+
1058+
// Check annotations allow list
1059+
if vals, ok := newOpts.AnnotationsAllowList["annotationY"]; !ok || len(vals) != 1 || vals[0] != "baz" {
1060+
t.Errorf("expected annotationY with value [baz], got %v", vals)
1061+
}
1062+
if vals, ok := newOpts.AnnotationsAllowList["oldannotation"]; ok {
1063+
t.Errorf("expected oldannotation to be overwritten, got %v", vals)
1064+
}
1065+
1066+
}
1067+
1068+
func TestConfigureResourcesAndMetrics_InvalidYAML(t *testing.T) {
1069+
opts := options.NewOptions()
1070+
invalidYAML := []byte("invalid: [unclosed")
1071+
// Should not panic or overwrite opts
1072+
result := configureResourcesAndMetrics(opts, invalidYAML)
1073+
if result != opts {
1074+
t.Errorf("expected opts to be returned unchanged on invalid YAML")
1075+
}
1076+
}

pkg/options/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
162162
o.cmd.Flags().StringVar(&o.Pod, "pod", "", "Name of the pod that contains the kube-state-metrics container. "+autoshardingNotice)
163163
o.cmd.Flags().StringVar(&o.TLSConfig, "tls-config", "", "Path to the TLS configuration file")
164164
o.cmd.Flags().StringVar(&o.TelemetryHost, "telemetry-host", "::", `Host to expose kube-state-metrics self metrics on.`)
165-
o.cmd.Flags().StringVar(&o.Config, "config", "", "Path to the kube-state-metrics options config file")
165+
o.cmd.Flags().StringVar(&o.Config, "config", "", "Path to the kube-state-metrics options config YAML file. If this flag is set, the flags defined in the file override the command line flags.")
166166
o.cmd.Flags().StringVar((*string)(&o.Node), "node", "", "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.")
167167
o.cmd.Flags().Var(&o.AnnotationsAllowList, "metric-annotations-allowlist", "Comma-separated list of Kubernetes annotations keys that will be used in the resource' labels metric. By default the annotations metrics are not exposed. To include them, provide a list of resource names in their plural form and Kubernetes annotation keys you would like to allow for them (Example: '=namespaces=[kubernetes.io/team,...],pods=[kubernetes.io/team],...)'. A single '*' can be provided per resource instead to allow any annotations, but that has severe performance implications (Example: '=pods=[*]').")
168168
o.cmd.Flags().Var(&o.LabelsAllowList, "metric-labels-allowlist", "Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the labels metrics are not exposed. To include them, provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]'). Additionally, an asterisk (*) can be provided as a key, which will resolve to all resources, i.e., assuming '--resources=deployments,pods', '=*=[*]' will resolve to '=deployments=[*],pods=[*]'.")

0 commit comments

Comments
 (0)