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

  1. Verify the --hubble-metrics-config-file path exists and is readable by the hubble process
  2. Check the ConfigMap volume is mounted in the pod (kubectl describe pod)
  3. If transient during ConfigMap update, rely on the watcher retaining the last good config and retrying on the next fs event
  4. 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

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


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/dde298f6bb83e565. Report an issue: GitHub.