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 ofView on GitHub (pinned to 8dc789c5cf)
Solutions
- Create the lock first and handle its error: lock, err := k8sresourcelock.NewFromKubeconfig(...); if err != nil { return err }
- Assign Lock: lec.Lock = lock before NewLeaderElector
- Never ignore errors from lock constructors - they return nil on failure
- 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
- Never discard errors from lock constructors; they return nil on failure
- Create the lock immediately before the elector in the same function
- Verify RBAC for the lock resource (leases/endpoints/configmaps) in the namespace
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
- invalid lock-type %s
- leaseDuration must be greater than renewDeadline
- renewDeadline must be greater than retryPeriod*JitterFactor
- leaseDuration must be greater than zero
- renewDeadline must be greater than zero
AI-assisted analysis of istio/istio@8dc789c5cf (2026-08-15).
Data as JSON: /api/errors/6bd90c459ac5894a.
Report an issue: GitHub.