Fix certmanager provisioning retry and metrics
Address several provisioning defects that either stalled the retry budget or crashed the process: - Classify CAA resolver/transport failures apart from a real CAA policy denial. Both shared the "caa records" wording, so a transient resolver error was persisted as customer misconfiguration and retried forever without consuming the retry budget. A new ErrCAANotPermitted sentinel now marks the genuine misconfiguration; other CAA errors are treated as ordinary transient failures. - Honor an explicit Retry-After: 0 (or a past date) as permission for an immediate retry instead of promoting it to the one-hour default cooldown. acme.RateLimit collapses zero, invalid, and absent headers to a zero duration, so the header is now parsed directly to tell an explicit zero apart from a missing one. - Reuse already-registered Prometheus collectors when a second ACMEService shares a registerer. The fixed-name collectors were MustRegistered, so a duplicate registration panicked the process. - Persist provisioning failures on a context detached from the process tick deadline. A timed-out attempt reached persistFailure with an expired context, so the write-back failed and the retry budget never advanced, leaving the certificate indefinitely retriable. - Use pgx.StrictNamedArgs in the certificate FOR UPDATE loaders to match the coredata SQL contract. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -43,6 +43,10 @@ const (
|
||||
maxProvisioningRetries = 3
|
||||
dnsExchangeTimeout = 10 * time.Second
|
||||
processTickTimeout = 90 * time.Second
|
||||
// persistFailureTimeout bounds the retry-outcome write-back. It runs on a
|
||||
// context detached from the process tick deadline so a timed-out attempt can
|
||||
// still record its failure.
|
||||
persistFailureTimeout = 15 * time.Second
|
||||
|
||||
tracerName = "go.probo.inc/probo/pkg/certmanager"
|
||||
)
|
||||
@@ -404,6 +408,15 @@ func (h *provisionHandler) persistFailure(
|
||||
) error {
|
||||
errorCode := classifyProvisioningError(provisionErr)
|
||||
|
||||
// Process runs each tick under processTickTimeout. When that deadline fires
|
||||
// mid-attempt, the same expired context reaches here, and the write-back
|
||||
// silently fails — so the retry budget never advances and the certificate
|
||||
// stays retriable forever. Detach from the tick deadline (and cancellation)
|
||||
// and bound the write with its own timeout so repeated timeouts still make
|
||||
// progress toward FAILED.
|
||||
ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), persistFailureTimeout)
|
||||
defer cancel()
|
||||
|
||||
return h.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, tx pg.Tx) error {
|
||||
@@ -731,7 +744,8 @@ func (h *provisionHandler) checkCAARecords(ctx context.Context, hostname string)
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"caa records for domain %q do not permit issuance by %q",
|
||||
"%w: domain %q by %q",
|
||||
ErrCAANotPermitted,
|
||||
hostname,
|
||||
h.caaIssuerDomain,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user