istio/istio · error

lock must not be nil

Error message

lock must not be nil

What it means

Validation error from NewLeaderElector in pilot/pkg/leaderelection/k8sleaderelection. The Lock field (a k8sresourcelock.Interface) is the resource the elector contends on (Lease, Endpoint, ConfigMap, or a MultiLock); a nil Lock means there is nothing to elect on, so the constructor rejects it. Locks are normally created via k8sresourcelock.New or NewFromKubeconfig.

Source

Thrown at pilot/pkg/leaderelection/k8sleaderelection/leaderelection.go:102

	}
	if lec.LeaseDuration < 1 {
		return nil, fmt.Errorf("leaseDuration must be greater than zero")
	}
	if lec.RenewDeadline < 1 {
		return nil, fmt.Errorf("renewDeadline must be greater than zero")
	}
	if lec.RetryPeriod < 1 {
		return nil, fmt.Errorf("retryPeriod must be greater than zero")
	}
	if lec.Callbacks.OnStartedLeading == nil {
		return nil, fmt.Errorf("callback OnStartedLeading must not be nil")
	}
	if lec.Callbacks.OnStoppedLeading == nil {
		return nil, fmt.Errorf("callback OnStoppedLeading  must not be nil")
	}

	if lec.Lock == nil {
		return nil, fmt.Errorf("lock must not be nil")
	}
	le := LeaderElector{
		config:  lec,
		clock:   clock.RealClock{},
		metrics: globalMetricsFactory.newLeaderMetrics(),
	}
	le.metrics.leaderOff(le.config.Name)
	return &le, nil
}

type KeyComparisonFunc func(existingKey string) bool

type LeaderElectionConfig struct {
	// Lock is the resource that will be used for locking
	Lock k8sresourcelock.Interface

	// LeaseDuration is the duration that non-leader candidates will
	// wait to force acquire leadership. This is measured against time of

View on GitHub (pinned to 8dc789c5cf)

Solutions

  1. Create the lock first and handle its error: lock, err := k8sresourcelock.NewFromKubeconfig(...); if err != nil { return err }
  2. Assign Lock: lec.Lock = lock before NewLeaderElector
  3. Never ignore errors from lock constructors - they return nil on failure
  4. Verify the kubeconfig/client used can access the target namespace and resource type

Example fix

// before
lock, _ := k8sresourcelock.NewFromKubeconfig(t, ns, name, rlc, restCfg, renew) // error ignored; lock==nil
cfg.Lock = lock // -> lock must not be nil

// after
lock, err := k8sresourcelock.NewFromKubeconfig(k8sresourcelock.LeasesResourceLock, ns, name, rlc, restCfg, renew)
if err != nil {
    return fmt.Errorf("creating leader election lock: %w", err)
}
cfg.Lock = lock
Defensive patterns

Strategy: validation

Validate before calling

if lec.Lock == nil {
    return errors.New("leader election lock not constructed")
}
// and check the constructor's error before assignment:
lock, err := k8sresourcelock.NewFromKubeconfig(k8sresourcelock.LeasesResourceLock, ns, name, rlc, restCfg, renew)
if err != nil {
    return fmt.Errorf("lock construction: %w", err)
}

Try / catch

lock, err := k8sresourcelock.NewFromKubeconfig(...)
if err != nil {
    return err
}
cfg.Lock = lock // only assign on success

Prevention

When it happens

Trigger: Omitting the Lock field when building LeaderElectionConfig, or a lock-construction call whose error was ignored so Lock stayed nil (Go returns a nil Interface on error). NewLeaderElector then fails this final check.

Common situations: Calling k8sresourcelock.NewFromKubeconfig and not handling its error (e.g. invalid lock-type) before assigning; wiring code that builds the lock conditionally and skips the assignment on some path; refactor that moved lock creation after elector creation.

Related errors


AI-assisted analysis of istio/istio@8dc789c5cf (2026-08-15). Data as JSON: /api/errors/6bd90c459ac5894a. Report an issue: GitHub.