Skip to content

Commit 26f4072

Browse files
pacoyangkvinwang
authored andcommitted
fix(dstack-ingress): retry applying a renewed certificate until it sticks
Renewing and applying were treated as one step: if [ "$changed" -eq 1 ]; then collect_evidence || echo "Evidence generation failed" >&2 build_combined_pems || echo "Combined PEM build failed" >&2 haproxy_reload || true fi Every failure there is swallowed, and none of them set `failed`, so the pass still reports success. Twelve hours later the ACME client sees a fresh certificate on disk, reports "nothing to renew", `changed` is 0, and the apply is never attempted again. HAProxy goes on serving the pre-renewal certificate until it expires -- which is soon, because being close to expiry is why it renewed in the first place. The symptom arrives weeks after the cause, as an expired certificate, with nothing in the logs at that moment to connect the two. Separate the two: `APPLY_PENDING` is set when something renewed and only cleared once evidence, PEMs and the reload have all succeeded, so a failed apply is retried on the next pass instead of waiting for the next renewal. Quiet passes stay quiet. With nothing renewed and nothing pending the block does not run at all, so this does not bring back the reload-on- every-check behaviour that made HAProxy replace its worker twice a day. Kept in both mode scripts rather than lifted into a library: the apply steps are mode-specific (certbot and lego lay certificates out differently), so a shared helper would have to call back into functions its callers define, which is harder to follow than the dozen lines it would save.
1 parent ab6d7e1 commit 26f4072

2 files changed

Lines changed: 44 additions & 6 deletions

File tree

custom-domain/dstack-ingress/scripts/dns01.sh

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@ collect_evidence() {
288288
evidence_finalize
289289
}
290290

291+
# Set when certificates have been renewed but not yet applied to haproxy.
292+
# Survives across passes, so a failed apply is retried on the next one.
293+
APPLY_PENDING=false
294+
291295
# One pass over every domain: make the DNS records right, then issue or renew.
292296
# Idempotent, so the first pass and every later one are the same code.
293297
run_pass() {
@@ -306,9 +310,24 @@ run_pass() {
306310
done <<<"$(get-all-domains.sh)"
307311

308312
if [ "$changed" -eq 1 ]; then
309-
collect_evidence || echo "Evidence generation failed" >&2
310-
build_combined_pems || echo "Combined PEM build failed" >&2
311-
haproxy_reload || true
313+
APPLY_PENDING=true
314+
fi
315+
316+
# Applying is separate from renewing, and retried until it sticks. A
317+
# renewal that reaches disk but not haproxy is invisible: the next pass
318+
# sees a fresh certificate, reports "nothing to renew", and never reloads
319+
# again -- so haproxy would serve the pre-renewal certificate until it
320+
# expires, which is soon, because being near expiry is why it renewed.
321+
if [ "$APPLY_PENDING" = true ]; then
322+
local applied=true
323+
collect_evidence || { echo "Evidence generation failed" >&2; applied=false; }
324+
build_combined_pems || { echo "Combined PEM build failed" >&2; applied=false; }
325+
haproxy_reload || applied=false
326+
if [ "$applied" = true ]; then
327+
APPLY_PENDING=false
328+
else
329+
echo "[$(date)] Certificate apply incomplete; retrying on the next pass" >&2
330+
fi
312331
fi
313332
[ "$failed" -eq 0 ]
314333
}

custom-domain/dstack-ingress/scripts/tlsalpn.sh

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ process_domain() {
262262
legoman.py auto --domain "$domain" ${ACME_EMAIL:+--email "$ACME_EMAIL"}
263263
}
264264

265+
# Set when certificates have been renewed but not yet applied to haproxy.
266+
# Survives across passes, so a failed apply is retried on the next one.
267+
APPLY_PENDING=false
268+
265269
# One pass over every domain: make sure the records exist, then issue or renew.
266270
# Idempotent, so the first pass and every later one are the same code. There is
267271
# deliberately only one of these -- an earlier split between a one-shot
@@ -285,9 +289,24 @@ run_pass() {
285289
done <<<"$(get-all-domains.sh)"
286290

287291
if [ "$changed" -eq 1 ]; then
288-
collect_evidence || echo "Evidence generation failed" >&2
289-
build_combined_pems || echo "Combined PEM build failed" >&2
290-
haproxy_reload || true
292+
APPLY_PENDING=true
293+
fi
294+
295+
# Applying is separate from renewing, and retried until it sticks. A
296+
# renewal that reaches disk but not haproxy is invisible: the next pass
297+
# sees a fresh certificate, reports "nothing to renew", and never reloads
298+
# again -- so haproxy would serve the pre-renewal certificate until it
299+
# expires, which is soon, because being near expiry is why it renewed.
300+
if [ "$APPLY_PENDING" = true ]; then
301+
local applied=true
302+
collect_evidence || { echo "Evidence generation failed" >&2; applied=false; }
303+
build_combined_pems || { echo "Combined PEM build failed" >&2; applied=false; }
304+
haproxy_reload || applied=false
305+
if [ "$applied" = true ]; then
306+
APPLY_PENDING=false
307+
else
308+
echo "[$(date)] Certificate apply incomplete; retrying on the next pass" >&2
309+
fi
291310
fi
292311
FIRST_PASS=false
293312
[ "$failed" -eq 0 ]

0 commit comments

Comments
 (0)