containerd/containerd · error

cni network conf monitor error: %w

Error message

cni network conf monitor error: %w

What it means

criService.Run starts a cniNetConfSyncer loop per CNI network config monitor (flannel and default networks); if any syncLoop exits with an error, Run tears down the entire CRI service and returns this wrapped error, last in priority after event monitor and stream server errors. It means the CNI network configuration watching/synchronization loop failed, so pod networking config can no longer be kept in sync.

Source

Thrown at internal/cri/server/service.go:396

	}
	// If the error is set above, err from channel must be nil here, because
	// the channel is supposed to be closed. Or else, we wait and set it.
	if err := <-eventMonitorErrCh; err != nil {
		eventMonitorErr = err
	}
	log.L.Info("Event monitor stopped")
	if err := <-streamServerErrCh; err != nil {
		streamServerErr = err
	}
	log.L.Info("Stream server stopped")
	if eventMonitorErr != nil {
		return fmt.Errorf("event monitor error: %w", eventMonitorErr)
	}
	if streamServerErr != nil {
		return fmt.Errorf("stream server error: %w", streamServerErr)
	}
	if cniNetConfMonitorErr != nil {
		return fmt.Errorf("cni network conf monitor error: %w", cniNetConfMonitorErr)
	}
	return nil
}

// Close stops the CRI service.
// TODO(random-liu): Make close synchronous.
func (c *criService) Close() error {
	log.L.Info("Stop CRI service")
	for name, h := range c.cniNetConfMonitor {
		if err := h.stop(); err != nil {
			log.L.WithError(err).Errorf("failed to stop cni network conf monitor for %s", name)
		}
	}
	c.eventMonitor.Stop()
	if c.statsCollector != nil {
		c.statsCollector.Stop()
	}
	if err := c.streamServer.Stop(); err != nil {

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Check the wrapped inner error and which syncer name was logged at startup ('Start cni network conf syncer for %s') to identify the failing network.
  2. Verify the CNI config directory (/etc/cni/net.d) exists, is readable by containerd, and contains valid .conflist/.conf JSON (run jq against each file).
  3. Reinstall or repair the CNI network plugin (e.g. flannel/calico manifests) so its config files and subnet env are regenerated.
  4. Check inotify limits (fs.inotify.max_user_watches) if the inner error indicates watch creation failure.
  5. Restart containerd after fixing the CNI config so the syncers restart cleanly.

Example fix

// before: malformed CNI config
/etc/cni/net.d/10-flannel.conflist  (truncated JSON)

// after: restore a valid conflist or remove the broken file, then
sudo systemctl restart containerd
Defensive patterns

Strategy: validation

Validate before calling

// before starting containerd, sanity-check CNI config dir
import (
    "encoding/json"
    "os"
    "path/filepath"
)

func validateCNIConfDir(dir string) error {
    if _, err := os.Stat(dir); err != nil {
        return fmt.Errorf("CNI conf dir %s missing/unreadable: %w", dir, err)
    }
    files, _ := filepath.Glob(filepath.Join(dir, "*.conf*"))
    for _, f := range files {
        b, err := os.ReadFile(f)
        if err != nil {
            return err
        }
        if !json.Valid(b) {
            return fmt.Errorf("malformed CNI config: %s", f)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: h.syncLoop() in the cniNetConfSyncer returns non-nil: watching the CNI conf directory (e.g. /etc/cni/net.d) fails (inotify errors, directory missing), reading/parsing a CNI network config file fails, or the syncer's context is cancelled with an error.

Common situations: /etc/cni/net.d removed or unreadable on the node (CNI plugin files deleted by a broken uninstall); malformed CNI .conflist written by a network plugin installer; permission problems after host hardening; flannel syncer failing on nodes where the flannel subnet env file is missing.

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/76cc05720034fa3d. Report an issue: GitHub.