cilium/cilium · error
cannot read file '%s': %w
Error message
cannot read file '%s': %w
What it means
The Hubble metric ConfigMap watcher's readConfig could not read the YAML config file at c.configFilePath (os.ReadFile failed) and wraps the OS error. This means the path is missing, unreadable, or is a directory — Hubble then keeps the previous metric configuration. The wrapped error (e.g., 'no such file or directory', 'permission denied') tells the exact cause.
Source
Thrown at pkg/hubble/metrics/metric_config_watcher.go:110
c.configFilePath = path
}
// Stop stops watcher.
func (c *metricConfigWatcher) Stop() {
if c.ticker != nil {
c.ticker.Stop()
}
close(c.stop)
}
func (c *metricConfigWatcher) readConfig() (*api.Config, bool, uint64, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
config := &api.Config{Metrics: []*api.MetricConfig{}}
yamlFile, err := os.ReadFile(c.configFilePath)
if err != nil {
return nil, false, 0, fmt.Errorf("cannot read file '%s': %w", c.configFilePath, err)
}
if err := yaml.Unmarshal(yamlFile, config); err != nil {
return nil, false, 0, fmt.Errorf("cannot parse yaml: %w", err)
}
if err := c.validateMetricConfig(config); err != nil {
return nil, false, 0, fmt.Errorf("invalid yaml config file: %w", err)
}
newHash := calculateMetricHash(yamlFile)
isSameHash := newHash == c.currentCfgHash
c.currentCfgHash = newHash
return config, isSameHash, newHash, nil
}
func calculateMetricHash(file []byte) uint64 {
sum := md5.Sum(file)View on GitHub (pinned to ac7b90affa)
Solutions
- Verify the --hubble-metrics-config-file path exists and is readable by the hubble process
- Check the ConfigMap volume is mounted in the pod (kubectl describe pod)
- If transient during ConfigMap update, rely on the watcher retaining the last good config and retrying on the next fs event
- Fix file permissions/ownership on the mounted path
Example fix
// before hubble: metrics-config-file: /etc/hubble/config.yaml # file not mounted // after # mount the ConfigMap and point the flag at it: # --hubble-metrics-config-file=/etc/hubble-metrics/config.yaml volumeMounts: - name: hubble-metrics mountPath: /etc/hubble-metrics
Defensive patterns
Strategy: validation
Validate before calling
// before relying on the watcher, ensure the file exists and is readable:
import "os"
func configReadable(path string) error {
fi, err := os.Stat(path)
if err != nil { return err }
if fi.IsDir() { return fmt.Errorf("%s is a directory", path) }
f, err := os.Open(path)
if err != nil { return err }
return f.Close()
} Try / catch
// watcher already keeps last-good config; on reload error:
cfg, changed, hash, err := c.readConfig()
if err != nil {
log.WithError(err).Warn("keeping previous hubble metric config")
return
} Prevention
- Verify the --hubble-metrics-config-file path matches the volumeMount
- Ensure the ConfigMap is mounted before hubble starts (initContainer wait or retry)
- Watch for kubelet atomic-mount windows that briefly remove the file
- Check file permissions for the hubble user
When it happens
Trigger: ConfigMap volume not yet mounted (empty/cleaned mount during kubelet refresh); wrong --hubble-metrics-config-file path; file deleted between mounts; permission changes on the mounted file.
Common situations: Kubernetes ConfigMap updates with atomic mounts causing transient ENOENT; typo'd CLI flag; running hubble with a mounted ConfigMap removed from the pod spec.
Related errors
- cannot parse yaml: %w
- too many slashes in pattern
- cannot configure both static and dynamic Hubble metrics
- %s is not set in ConfigMap %q
- unable to parse %s: %w
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/dde298f6bb83e565.
Report an issue: GitHub.