kube/certs: use Let's Encrypt's recommended retry schedule (#20292)

Replace the doubling backoff (1m, 2m, 4m, ...) with LE's recommended
1m, 10m, 100m, daily. The old schedule burned retry attempts inside
the rate-limit window without speeding recovery.

Updates #20288
Updates #19895

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
Tom Meadows
2026-07-01 11:50:54 +01:00
committed by GitHub
parent 85d8644215
commit 64422f274d
+15 -14
View File
@@ -94,6 +94,16 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig)
return nil return nil
} }
// retrySchedule is the wait between successive failed issuance attempts,
// following LE's recommended schedule.
// https://letsencrypt.org/docs/integration-guide/#retrying-failures
var retrySchedule = []time.Duration{
1 * time.Minute,
10 * time.Minute,
100 * time.Minute,
24 * time.Hour,
}
// runCertLoop: // runCertLoop:
// - calls localAPI certificate endpoint to ensure that certs are issued for the // - calls localAPI certificate endpoint to ensure that certs are issued for the
// given domain name // given domain name
@@ -103,13 +113,8 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig)
// Note that renewal check also happens when the node receives an HTTPS request and it is possible that certs get // Note that renewal check also happens when the node receives an HTTPS request and it is possible that certs get
// renewed at that point. Renewal here is needed to prevent the shared certs from expiry in edge cases where the 'write' // renewed at that point. Renewal here is needed to prevent the shared certs from expiry in edge cases where the 'write'
// replica does not get any HTTPS requests. // replica does not get any HTTPS requests.
// https://letsencrypt.org/docs/integration-guide/#retrying-failures
func (cm *CertManager) runCertLoop(ctx context.Context, domain string) { func (cm *CertManager) runCertLoop(ctx context.Context, domain string) {
const ( const normalInterval = 24 * time.Hour // regular renewal check
normalInterval = 24 * time.Hour // regular renewal check
initialRetry = 1 * time.Minute // initial backoff after a failure
maxRetryInterval = 24 * time.Hour // max backoff period
)
if err := cm.waitForCertDomain(ctx, domain); err != nil { if err := cm.waitForCertDomain(ctx, domain); err != nil {
// Best-effort, log and continue with the issuing loop. // Best-effort, log and continue with the issuing loop.
@@ -153,15 +158,11 @@ func (cm *CertManager) runCertLoop(ctx context.Context, domain string) {
nextInterval = normalInterval nextInterval = normalInterval
} else { } else {
retryCount++ retryCount++
// Calculate backoff: initialRetry * 2^(retryCount-1) idx := retryCount - 1
// For retryCount=1: 1min * 2^0 = 1min if idx >= len(retrySchedule) {
// For retryCount=2: 1min * 2^1 = 2min idx = len(retrySchedule) - 1
// For retryCount=3: 1min * 2^2 = 4min
backoff := initialRetry * time.Duration(1<<(retryCount-1))
if backoff > maxRetryInterval {
backoff = maxRetryInterval
} }
nextInterval = backoff nextInterval = retrySchedule[idx]
cm.logf("Error refreshing certificate for %s (retry %d): %v. Will retry in %v\n", cm.logf("Error refreshing certificate for %s (retry %d): %v. Will retry in %v\n",
domain, retryCount, err, nextInterval) domain, retryCount, err, nextInterval)
} }