Skip to content

Commit 295d70a

Browse files
changes for continuing to run when file doesnt exist
1 parent 9add5df commit 295d70a

File tree

4 files changed

+94
-59
lines changed

4 files changed

+94
-59
lines changed

docs/developer/cli-arguments.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ kube-state-metrics can be configured through command line arguments.
44

55
Those arguments can be passed during startup when running locally:
66

7-
`kube-state-metrics --telemetry-port=8081 --kubeconfig=<KUBE-CONFIG> --apiserver=<APISERVER> ...`
7+
`kube-state-metrics --telemetry-port=8081 --kubeconfig=<KUBE-CONFIG> --apiserver=<APISERVER> ...`
88

99
Or configured in the `args` section of your deployment configuration in a Kubernetes / Openshift context:
1010

@@ -45,6 +45,8 @@ Flags:
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)
4747
--config string Path to the kube-state-metrics options config file
48+
--continue-without-config If true, kube-state-metrics continues to run even if the config file specified by --config is not present. This is useful for scenarios where config file is not provided at startup but is provided later, for e.g., via configmap. Kube-state-metrics will not exit with an error if the config file is not found, instead watches and reloads when it is created.
49+
--continue-without-custom-resource-state-config-file If true, Kube-state-metrics continues to run even if the config file specified by --custom-resource-state-config-file is not present. This is useful for scenarios where config file is not provided at startup but is provided later, for e.g., via configmap. Kube-state-metrics will not exit with an error if the custom-resource-state-config file is not found, instead watches and reloads when it is created.
4850
--custom-resource-state-config string Inline Custom Resource State Metrics config YAML (experimental)
4951
--custom-resource-state-config-file string Path to a Custom Resource State Metrics config file (experimental)
5052
--custom-resource-state-only Only provide Custom Resource State metrics (experimental)

internal/wrapper.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,25 @@ func RunKubeStateMetricsWrapper(opts *options.Options) {
4343
}
4444

4545
ctx, cancel := context.WithCancel(context.Background())
46+
4647
if file := options.GetConfigFile(*opts); file != "" {
4748
cfgViper := viper.New()
4849
cfgViper.SetConfigType("yaml")
4950
cfgViper.SetConfigFile(file)
50-
if err := cfgViper.ReadInConfig(); err != nil {
51-
if errors.Is(err, viper.ConfigFileNotFoundError{}) {
52-
klog.ErrorS(err, "Options configuration file not found", "file", file)
51+
var cfgViperReadInConfigErr error
52+
if cfgViperReadInConfigErr = cfgViper.ReadInConfig(); cfgViperReadInConfigErr != nil {
53+
if errors.Is(cfgViperReadInConfigErr, viper.ConfigFileNotFoundError{}) {
54+
klog.InfoS("Options configuration file not found at startup", "file", file)
55+
} else if _, isNotExisterr := os.Stat(filepath.Clean(file)); os.IsNotExist(isNotExisterr) {
56+
// TODO: Remove this check once viper.ConfigFileNotFoundError is working as expected, see this issue -
57+
// https://github.com/spf13/viper/issues/1783
58+
klog.InfoS("Options configuration file not found at startup", "file", file)
5359
} else {
54-
klog.ErrorS(err, "Error reading options configuration file", "file", file)
60+
klog.ErrorS(cfgViperReadInConfigErr, "Error reading options configuration file", "file", file)
61+
}
62+
if !opts.ContinueWithoutConfig {
63+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
5564
}
56-
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
5765
}
5866
cfgViper.OnConfigChange(func(e fsnotify.Event) {
5967
klog.InfoS("Changes detected", "name", e.Name)
@@ -65,25 +73,33 @@ func RunKubeStateMetricsWrapper(opts *options.Options) {
6573
})
6674
cfgViper.WatchConfig()
6775

68-
// Merge configFile values with opts so we get the CustomResourceConfigFile from config as well
69-
configFile, err := os.ReadFile(filepath.Clean(file))
70-
if err != nil {
71-
klog.ErrorS(err, "failed to read options configuration file", "file", file)
72-
}
76+
if cfgViperReadInConfigErr == nil {
77+
// Merge configFile values with opts so we get the CustomResourceConfigFile from config as well
78+
configFile, err := os.ReadFile(filepath.Clean(file))
79+
if err != nil {
80+
klog.ErrorS(err, "failed to read options configuration file", "file", file)
81+
}
7382

74-
yaml.Unmarshal(configFile, opts)
83+
yaml.Unmarshal(configFile, opts)
84+
}
7585
}
7686
if opts.CustomResourceConfigFile != "" {
7787
crcViper := viper.New()
7888
crcViper.SetConfigType("yaml")
7989
crcViper.SetConfigFile(opts.CustomResourceConfigFile)
8090
if err := crcViper.ReadInConfig(); err != nil {
8191
if errors.Is(err, viper.ConfigFileNotFoundError{}) {
82-
klog.ErrorS(err, "Custom resource configuration file not found", "file", opts.CustomResourceConfigFile)
92+
klog.InfoS("Custom resource configuration file not found at startup", "file", opts.CustomResourceConfigFile)
93+
} else if _, err = os.Stat(filepath.Clean(opts.CustomResourceConfigFile)); os.IsNotExist(err) {
94+
// Adding this check in addition to the above since viper.ConfigFileNotFoundError is not working as expected due to this issue -
95+
// https://github.com/spf13/viper/issues/1783
96+
klog.InfoS("Custom resource configuration file not found at startup", "file", opts.CustomResourceConfigFile)
8397
} else {
8498
klog.ErrorS(err, "Error reading Custom resource configuration file", "file", opts.CustomResourceConfigFile)
8599
}
86-
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
100+
if !opts.ContinueWithoutCustomResourceConfigFile {
101+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
102+
}
87103
}
88104
crcViper.OnConfigChange(func(e fsnotify.Event) {
89105
klog.InfoS("Changes detected", "name", e.Name)

pkg/app/server.go

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -125,29 +125,33 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options) error {
125125
})
126126
storeBuilder := store.NewBuilder()
127127
storeBuilder.WithMetrics(ksmMetricsRegistry)
128-
129128
got := options.GetConfigFile(*opts)
130129
if got != "" {
131-
configFile, err := os.ReadFile(filepath.Clean(got))
132-
if err != nil {
133-
return fmt.Errorf("failed to read opts config file: %v", err)
134-
}
135-
// NOTE: Config value will override default values of intersecting options.
136-
err = yaml.Unmarshal(configFile, opts)
137-
if err != nil {
138-
// DO NOT end the process.
139-
// We want to allow the user to still be able to fix the misconfigured config (redeploy or edit the configmaps) and reload KSM automatically once that's done.
140-
klog.ErrorS(err, "failed to unmarshal opts config file")
141-
// Wait for the next reload.
142-
klog.InfoS("misconfigured config detected, KSM will automatically reload on next write to the config")
143-
klog.InfoS("waiting for config to be fixed")
144-
configSuccess.WithLabelValues("config", filepath.Clean(got)).Set(0)
145-
<-ctx.Done()
130+
if _, err := os.Stat(filepath.Clean(got)); os.IsNotExist(err) {
131+
klog.InfoS("config file does not exist, ignoring", "file", got)
146132
} else {
147-
configSuccess.WithLabelValues("config", filepath.Clean(got)).Set(1)
148-
configSuccessTime.WithLabelValues("config", filepath.Clean(got)).SetToCurrentTime()
149-
hash := md5HashAsMetricValue(configFile)
150-
configHash.WithLabelValues("config", filepath.Clean(got)).Set(hash)
133+
configFile, err := os.ReadFile(filepath.Clean(got))
134+
if err != nil {
135+
return fmt.Errorf("failed to read opts config file: %v", err)
136+
}
137+
// NOTE: Config value will override default values of intersecting options.
138+
err = yaml.Unmarshal(configFile, opts)
139+
140+
if err != nil {
141+
// DO NOT end the process.
142+
// We want to allow the user to still be able to fix the misconfigured config (redeploy or edit the configmaps) and reload KSM automatically once that's done.
143+
klog.ErrorS(err, "failed to unmarshal opts config file")
144+
// Wait for the next reload.
145+
klog.InfoS("misconfigured config detected, KSM will automatically reload on next write to the config")
146+
klog.InfoS("waiting for config to be fixed")
147+
configSuccess.WithLabelValues("config", filepath.Clean(got)).Set(0)
148+
<-ctx.Done()
149+
} else {
150+
configSuccess.WithLabelValues("config", filepath.Clean(got)).Set(1)
151+
configSuccessTime.WithLabelValues("config", filepath.Clean(got)).SetToCurrentTime()
152+
hash := md5HashAsMetricValue(configFile)
153+
configHash.WithLabelValues("config", filepath.Clean(got)).Set(hash)
154+
}
151155
}
152156
}
153157

@@ -177,15 +181,18 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options) error {
177181
}
178182

179183
if opts.CustomResourceConfigFile != "" {
180-
crcFile, err := os.ReadFile(filepath.Clean(opts.CustomResourceConfigFile))
181-
if err != nil {
182-
return fmt.Errorf("failed to read custom resource config file: %v", err)
184+
if _, err := os.Stat(filepath.Clean(opts.CustomResourceConfigFile)); os.IsNotExist(err) {
185+
klog.InfoS("config file does not exist,ignoring", "file", opts.CustomResourceConfigFile)
186+
} else {
187+
crcFile, err := os.ReadFile(filepath.Clean(opts.CustomResourceConfigFile))
188+
if err != nil {
189+
return fmt.Errorf("failed to read custom resource config file: %v", err)
190+
}
191+
configSuccess.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).Set(1)
192+
configSuccessTime.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).SetToCurrentTime()
193+
hash := md5HashAsMetricValue(crcFile)
194+
configHash.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).Set(hash)
183195
}
184-
configSuccess.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).Set(1)
185-
configSuccessTime.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).SetToCurrentTime()
186-
hash := md5HashAsMetricValue(crcFile)
187-
configHash.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).Set(hash)
188-
189196
}
190197

191198
resources := []string{}
@@ -539,11 +546,17 @@ func resolveCustomResourceConfig(opts *options.Options) (customresourcestate.Con
539546
return yaml.NewDecoder(strings.NewReader(s)), nil
540547
}
541548
if file := opts.CustomResourceConfigFile; file != "" {
542-
f, err := os.Open(filepath.Clean(file))
543-
if err != nil {
544-
return nil, fmt.Errorf("unable to open Custom Resource State Metrics file: %v", err)
549+
if opts.ContinueWithoutCustomResourceConfigFile {
550+
if _, err := os.Stat(filepath.Clean(file)); os.IsNotExist(err) {
551+
klog.ErrorS(err, "failed to open Custom Resource State Metrics file, ignoring", "file", file)
552+
}
553+
} else {
554+
f, err := os.Open(filepath.Clean(file))
555+
if err != nil {
556+
return nil, fmt.Errorf("unable to open Custom Resource State Metrics file: %v", err)
557+
}
558+
return yaml.NewDecoder(f), nil
545559
}
546-
return yaml.NewDecoder(f), nil
547560
}
548561
return nil, nil
549562
}

pkg/options/options.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,21 @@ type Options struct {
4747
MetricOptInList MetricSet `yaml:"metric_opt_in_list"`
4848
Resources ResourceSet `yaml:"resources"`
4949

50-
cmd *cobra.Command
51-
Apiserver string `yaml:"apiserver"`
52-
CustomResourceConfig string `yaml:"custom_resource_config"`
53-
CustomResourceConfigFile string `yaml:"custom_resource_config_file"`
54-
Host string `yaml:"host"`
55-
Kubeconfig string `yaml:"kubeconfig"`
56-
Namespace string `yaml:"namespace"`
57-
Node NodeType `yaml:"node"`
58-
Pod string `yaml:"pod"`
59-
TLSConfig string `yaml:"tls_config"`
60-
TelemetryHost string `yaml:"telemetry_host"`
61-
62-
Config string
50+
cmd *cobra.Command
51+
Apiserver string `yaml:"apiserver"`
52+
CustomResourceConfig string `yaml:"custom_resource_config"`
53+
CustomResourceConfigFile string `yaml:"custom_resource_state_config_file"`
54+
ContinueWithoutCustomResourceConfigFile bool `yaml:"continue_without_custom_resource_state_config_file"`
55+
Host string `yaml:"host"`
56+
Kubeconfig string `yaml:"kubeconfig"`
57+
Namespace string `yaml:"namespace"`
58+
Node NodeType `yaml:"node"`
59+
Pod string `yaml:"pod"`
60+
TLSConfig string `yaml:"tls_config"`
61+
TelemetryHost string `yaml:"telemetry_host"`
62+
63+
Config string
64+
ContinueWithoutConfig bool `yaml:"continue_without_config"`
6365

6466
Namespaces NamespaceList `yaml:"namespaces"`
6567
NamespacesDenylist NamespaceList `yaml:"namespaces_denylist"`
@@ -156,13 +158,15 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
156158
o.cmd.Flags().Float64Var(&o.AutoGoMemlimitRatio, "auto-gomemlimit-ratio", float64(0.9), "The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory. (experimental)")
157159
o.cmd.Flags().StringVar(&o.CustomResourceConfig, "custom-resource-state-config", "", "Inline Custom Resource State Metrics config YAML (experimental)")
158160
o.cmd.Flags().StringVar(&o.CustomResourceConfigFile, "custom-resource-state-config-file", "", "Path to a Custom Resource State Metrics config file (experimental)")
161+
o.cmd.Flags().BoolVar(&o.ContinueWithoutCustomResourceConfigFile, "continue-without-custom-resource-state-config-file", false, "If true, Kube-state-metrics continues to run even if the config file specified by --custom-resource-state-config-file is not present. This is useful for scenarios where config file is not provided at startup but is provided later, for e.g., via configmap. Kube-state-metrics will not exit with an error if the custom-resource-state-config file is not found, instead watches and reloads when it is created.")
159162
o.cmd.Flags().StringVar(&o.Host, "host", "::", `Host to expose metrics on.`)
160163
o.cmd.Flags().StringVar(&o.Kubeconfig, "kubeconfig", "", "Absolute path to the kubeconfig file")
161164
o.cmd.Flags().StringVar(&o.Namespace, "pod-namespace", "", "Name of the namespace of the pod specified by --pod. "+autoshardingNotice)
162165
o.cmd.Flags().StringVar(&o.Pod, "pod", "", "Name of the pod that contains the kube-state-metrics container. "+autoshardingNotice)
163166
o.cmd.Flags().StringVar(&o.TLSConfig, "tls-config", "", "Path to the TLS configuration file")
164167
o.cmd.Flags().StringVar(&o.TelemetryHost, "telemetry-host", "::", `Host to expose kube-state-metrics self metrics on.`)
165168
o.cmd.Flags().StringVar(&o.Config, "config", "", "Path to the kube-state-metrics options config file")
169+
o.cmd.Flags().BoolVar(&o.ContinueWithoutConfig, "continue-without-config", false, "If true, kube-state-metrics continues to run even if the config file specified by --config is not present. This is useful for scenarios where config file is not provided at startup but is provided later, for e.g., via configmap. Kube-state-metrics will not exit with an error if the config file is not found, instead watches and reloads when it is created.")
166170
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.")
167171
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=[*]').")
168172
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)