Skip to content

Commit 68382a7

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 68382a7

2 files changed

Lines changed: 48 additions & 6 deletions

File tree

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

Lines changed: 24 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() {
@@ -305,10 +309,27 @@ run_pass() {
305309
esac
306310
done <<<"$(get-all-domains.sh)"
307311

312+
# `if`, not `[ ... ] && ...`: the AND-list returns non-zero when nothing
313+
# changed, and the startup pass runs bare under `set -e`.
308314
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
315+
APPLY_PENDING=true
316+
fi
317+
318+
# Applying is separate from renewing, and retried until it sticks. A
319+
# renewal that reaches disk but not haproxy is invisible: the next pass
320+
# sees a fresh certificate, reports "nothing to renew", and never reloads
321+
# again -- so haproxy would serve the pre-renewal certificate until it
322+
# expires, which is soon, because being near expiry is why it renewed.
323+
if [ "$APPLY_PENDING" = true ]; then
324+
local applied=true
325+
collect_evidence || { echo "Evidence generation failed" >&2; applied=false; }
326+
build_combined_pems || { echo "Combined PEM build failed" >&2; applied=false; }
327+
haproxy_reload || applied=false
328+
if [ "$applied" = true ]; then
329+
APPLY_PENDING=false
330+
else
331+
echo "[$(date)] Certificate apply incomplete; retrying on the next pass" >&2
332+
fi
312333
fi
313334
[ "$failed" -eq 0 ]
314335
}

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

Lines changed: 24 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
@@ -284,10 +288,27 @@ run_pass() {
284288
esac
285289
done <<<"$(get-all-domains.sh)"
286290

291+
# `if`, not `[ ... ] && ...`: the AND-list returns non-zero when nothing
292+
# changed, and the startup pass runs bare under `set -e`.
287293
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
294+
APPLY_PENDING=true
295+
fi
296+
297+
# Applying is separate from renewing, and retried until it sticks. A
298+
# renewal that reaches disk but not haproxy is invisible: the next pass
299+
# sees a fresh certificate, reports "nothing to renew", and never reloads
300+
# again -- so haproxy would serve the pre-renewal certificate until it
301+
# expires, which is soon, because being near expiry is why it renewed.
302+
if [ "$APPLY_PENDING" = true ]; then
303+
local applied=true
304+
collect_evidence || { echo "Evidence generation failed" >&2; applied=false; }
305+
build_combined_pems || { echo "Combined PEM build failed" >&2; applied=false; }
306+
haproxy_reload || applied=false
307+
if [ "$applied" = true ]; then
308+
APPLY_PENDING=false
309+
else
310+
echo "[$(date)] Certificate apply incomplete; retrying on the next pass" >&2
311+
fi
291312
fi
292313
FIRST_PASS=false
293314
[ "$failed" -eq 0 ]

0 commit comments

Comments
 (0)