Skip to content

fix: Keep the leader lease renewing under API load - #301

Merged
ecv merged 1 commit into
mainfrom
fix/leader-lease-own-client
Oct 2, 2026
Merged

ecv merged 1 commit into
mainfrom
fix/leader-lease-own-client

Conversation

@ecv

@ecv ecv commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

A leader replica could lose its lease and restart whenever the API server throttled requests or the connection slowed, because lease renewals queued on the same connection and behind the same rate limit as all other controller traffic. This happened to milo in production on 2 Oct: milo-os/milo#811

Lease renewals now get their own connection and their own small rate limit, so other traffic cannot hold them up.

A stopping leader now gives up its lease, so a rollout or drain hands over straight away instead of waiting for the lease to expire.

Why releasing the lease on shutdown is safe

Releasing the lease on shutdown was already an option here, but it was off by default and in the shipped deployment. It is now on in both.

The manager is the only thing this process runs, and the command returns as soon as the manager stops, so no controller can keep working after the lease is released.

Test plan

  • New unit tests show leader election keeps its own rate limit and connection when the main client is throttled, and fail with the old behaviour
  • Build, vet and unit tests pass locally, and the lint version CI uses reports no issues
  • After rollout, a rolling restart hands the lease to the standby within seconds and the new leader logs no lease renewal failures under load

Related to https://github.com/datum-cloud/infra/issues/6603

Lease renewals shared the manager's API connection and client-side rate
limiter with every other request. When the API server throttled or the
connection slowed, renewals queued behind controller traffic, the renew
deadline passed, and the replica lost its lease and restarted. This was
seen in production on 2 Oct for milo, where lease GET and PUT timed out
awaiting headers during throttling.

Key changes:
- Build a dedicated client config for leader election: a copy of the base
  config with its own dialer, no shared rate limiter, and QPS 5, burst 10
- Default release-on-cancel to true in both the flag and the deployment
  manifest, so a rollout or drain hands over at once instead of waiting
  out the lease. The manager is the only thing the process runs and the
  command returns when it stops, so no controller can act after the
  lease is released
- Add tests showing the base config is untouched, and that leader election
  ignores a saturated main limiter and opens its own connection
@ecv

ecv commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Two limits found in review, neither a blocker.

The change that matters is the dedicated dialer, which gives lease renewal its own connection. Nothing here sets a custom rate limiter on the base config, so dropping the shared limiter and the test for a saturated limiter guard a case this binary does not hit today.

The tests cover the helper, not the wiring: removing the leader election config or release on cancel from the manager options would leave them green. Release on cancel is safe without a shutdown wrapper because controller-runtime stops every leader-elected runnable before it releases the lease, and this binary runs no leader-only work outside the manager.

@ecv
ecv marked this pull request as ready for review October 2, 2026 17:04
@ecv
ecv requested a review from a team as a code owner October 2, 2026 17:04
@ecv
ecv requested review from kevwilliams and scotwells October 2, 2026 17:04

@kevwilliams kevwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sound. Gives leader election its own rest.Config, clearing the shared rate limiter and dialer so lease renewals get their own connection and their own small QPS/burst, independent of main controller traffic. TestLeaderElectionUsesItsOwnConnection confirms a genuinely separate TCP connection, not just distinct struct fields, and TestLeaderElectionIgnoresSaturatedMainLimiter saturates the main client's limiter and confirms lease renewal still succeeds. ReleaseOnCancel is safe here since the process exits immediately after the manager stops, so nothing can keep acting as leader after the lease releases. Matches the cited production incident. CI green.

@ecv
ecv merged commit 659a52f into main Oct 2, 2026
9 checks passed
@ecv
ecv deleted the fix/leader-lease-own-client branch October 2, 2026 23:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants