Skip to content

fix(external-dns): add startupProbe to survive slow first start under load - #3461

Draft
IvanHunters wants to merge 3 commits into
mainfrom
fix/external-dns-startup-probe
Draft

IvanHunters wants to merge 3 commits into
mainfrom
fix/external-dns-startup-probe

Conversation

@IvanHunters

@IvanHunters IvanHunters commented Jul 27, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Fixes the external-dns/external-dns-inmemory Chainsaw E2E flake (#3459).

The external-dns container binds its /healthz server on :7979 only after the informer source-cache sync completes. The upstream chart templates no startupProbe, so a slow first start has no grace period distinct from the tight livenessProbe (initialDelaySeconds 10 + periodSeconds 10 × failureThreshold 2 ≈ 30s). Under CI-cluster load the sync outlasts that budget, kubelet SIGKILLs a healthy-but-slow container (exitCode 137), and the restart loop cascades: Deployment never Ready → HR cozy-external-dns InstallFailed → HR external-dns InstallFailed → Chainsaw case FAIL.

This is not a regression of any feature PR: in the same run kubernetes-previous and kubernetes-latest pass; the external-dns chart is orthogonal.

Fix: render a values-driven startupProbe on /healthz:7979 so livenessProbe only begins evaluating after the slow-start window. Budget = periodSeconds 5 × failureThreshold 45 = 225s, comfortably clearing the single-digit-second healthy bind time while staying well inside the operator's default 10m HelmRelease install timeout. Same class of fix as #3204 (snapshot-controller), applied to a different component.

The template stanza is rendered with {{- with .Values.startupProbe }}, so an unset value is a no-op (upstream-compatible). Because the chart is vendored via helm pull, the template change is carried as patches/deployment-startup-probe.diff and re-applied by make update; the --version 1.20.0 pin guards the patch against an upstream layout shift. A helm-unittest locks the patch in and pins the budget as a regression sentinel.

Downstream repositories

Walked the trigger map file-by-file against the diff. This is internal probe tuning on a vendored system chart: no API/CRD change, no user-facing values schema, no docs surface.

  • No downstream repository is affected by this change

Release note

fix(external-dns): add a startupProbe so external-dns tolerates a slow first start (informer cache sync) under cluster load instead of being killed by the liveness probe into a restart loop

Summary by CodeRabbit

  • New Features

    • Added an optional startup health check for ExternalDNS to support slower initial startup.
    • Configured the check to monitor the existing health endpoint before liveness checks begin.
  • Bug Fixes

    • Prevented ExternalDNS from being restarted prematurely while it is still becoming ready.
  • Tests

    • Added Helm tests covering startup, liveness, and readiness probe configuration.

Closes #3459

… load

The external-dns container binds its /healthz server on :7979 only after the
informer source-cache sync completes. The upstream chart templates no
startupProbe, so a slow first start has no grace period distinct from the tight
livenessProbe (initialDelaySeconds 10 + periodSeconds 10 * failureThreshold 2 =
~30s). Under CI-cluster load the sync outlasts that budget, kubelet SIGKILLs a
healthy-but-slow container, and the restart loop cascades into the wrapping
HelmRelease timing out and the external-dns-inmemory E2E case failing.

Render a values-driven startupProbe on /healthz:7979 (budget 5s * 45 = 225s,
well inside the operator's 10m HelmRelease install timeout) so liveness only
begins evaluating after the slow-start window. Carried as a patch on the
vendored chart and re-applied on 'make update'; the version pin guards the patch
against an upstream layout shift.

Signed-off-by: IvanHunters <[email protected]>
Guards the vendored external-dns startupProbe patch against silent loss: a
'make update' bump that re-pulls upstream and drops the local diff, or an
upstream refactor that renames the Deployment or probe endpoint, fails these
asserts before the E2E flake can return. Also pins the 5s * 45 budget as a
regression sentinel and checks liveness/readiness survive the patch unchanged.

Signed-off-by: IvanHunters <[email protected]>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

ExternalDNS now defines an HTTP startup probe, conditionally renders it in the deployment, tests its configuration, and refreshes the vendored chart using a pinned upstream version plus a local patch.

Changes

ExternalDNS startup probe

Layer / File(s) Summary
Pinned chart refresh and patching
packages/system/external-dns/Makefile, packages/system/external-dns/patches/*
The update target pulls external-dns chart version 1.20.0 and applies the local startup-probe patch.
Startup probe configuration and rendering
packages/system/external-dns/values.yaml, packages/system/external-dns/charts/..., packages/system/external-dns/patches/*
Values configure an HTTP /healthz startup probe on port http; the deployment template renders it conditionally while retaining liveness and readiness probes.
Probe rendering tests
packages/system/external-dns/tests/*, packages/system/external-dns/Makefile
Helm tests verify startup-probe settings and existing probe endpoints, with a new test target to run them.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Suggested labels: size/M, area/testing

Suggested reviewers: kvaps

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a startupProbe to external-dns to prevent failures during slow initial startup.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/external-dns-startup-probe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The explanatory comment sat as literal YAML '#' lines inside the {{- with }}
block, so it rendered into 'helm template' output and onto the live Deployment
object. Move it into a Helm {{/* */}} template comment: the rationale stays in
the chart source for maintainers but no longer leaks into rendered manifests.
Patch regenerated against pristine upstream 1.20.0; forward-apply, reverse-apply
byte-consistency, and helm-unittest all re-verified.

Signed-off-by: IvanHunters <[email protected]>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@dosubot dosubot Bot added area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/bug Categorizes issue or PR as related to a bug labels Jul 27, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/system/external-dns/charts/external-dns/templates/deployment.yaml`:
- Around line 160-176: Move the startupProbe change out of the vendored
deployment template and into the repository-owned external-dns chart patch,
restoring the generated template to upstream form. Ensure the referenced patch
exists and run the project’s make update workflow to regenerate the chart; if
checked-in generated changes are intentional, update the project convention
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2450a5c2-6045-43ab-9e1f-aae30556a87d

📥 Commits

Reviewing files that changed from the base of the PR and between 66c96b4 and 82420dd.

📒 Files selected for processing (5)
  • packages/system/external-dns/Makefile
  • packages/system/external-dns/charts/external-dns/templates/deployment.yaml
  • packages/system/external-dns/patches/deployment-startup-probe.diff
  • packages/system/external-dns/tests/deployment_startup_probe_test.yaml
  • packages/system/external-dns/values.yaml

Comment on lines +160 to +176
{{- /*
cozystack (patches/deployment-startup-probe.diff): the upstream
external-dns chart templates only liveness/readiness and renders no
startupProbe, so a slow first start has no grace period distinct
from liveness. Under E2E-sandbox load the informer source-cache sync
outlasts the liveness budget (initialDelaySeconds 10 + periodSeconds
10 * failureThreshold 2 = ~30s), kubelet SIGKILLs a healthy-but-slow
container before it binds /healthz on :7979, and the restart loop
cascades into the wrapping HelmRelease timing out. A values-driven
startupProbe gates liveness on the slow-start window instead. Rendered
with `with` so an unset value is a no-op (upstream-compatible).
See cozystack/cozystack#3459.
*/}}
{{- with .Values.startupProbe }}
startupProbe:
{{- toYaml . | nindent 12 }}
{{- end }}

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files around target:"
git ls-files packages/system/external-dns/charts/external-dns/templates/deployment.yaml patches/deployment-startup-probe.diff 2>/dev/null || true

echo
echo "Git status/stat summary:"
git diff --stat -- packages/system/external-dns/charts/external-dns/templates/deployment.yaml patches/deployment-startup-probe.diff 2>/dev/null || true

echo
echo "Target diff context:"
git diff -- packages/system/external-dns/charts/external-dns/templates/deployment.yaml patches/deployment-startup-probe.diff 2>/dev/null | sed -n '1,220p'

echo
echo "README/docs references to chart vendoring/update/patches:"
rg -n --hidden --glob '!*.png' --glob '!*.jpg' --glob '!*.jpeg' \
  'packages/[^/]*/charts|vendored|make update|update|deployment-startup-probe.diff|patches/' \
  README.md docs Makefile .github 2>/dev/null || true

Repository: cozystack/cozystack

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Patch file existence/content:"
if [ -f patches/deployment-startup-probe.diff ]; then
  wc -l patches/deployment-startup-probe.diff
  sed -n '1,120p' patches/deployment-startup-probe.diff
else
  echo "missing patches/deployment-startup-probe.diff"
fi

echo
echo "Target template startupProbe context:"
sed -n '130,190p' packages/system/external-dns/charts/external-dns/templates/deployment.yaml

Repository: cozystack/cozystack

Length of output: 2954


Do not hand-edit the vendored chart template.

packages/system/external-dns/charts/external-dns/templates/deployment.yaml is in the protected packages/**/charts/** tree, and the referenced repo patch file is missing. Keep changes in the repository-owned patch and regenerate the chart template through make update, or update the project convention if checked-in generated diffs are intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/system/external-dns/charts/external-dns/templates/deployment.yaml`
around lines 160 - 176, Move the startupProbe change out of the vendored
deployment template and into the repository-owned external-dns chart patch,
restoring the generated template to upstream form. Ensure the referenced patch
exists and run the project’s make update workflow to regenerate the chart; if
checked-in generated changes are intentional, update the project convention
accordingly.

Source: Coding guidelines

@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review size/L This PR changes 100-499 lines, ignoring generated files labels Jul 27, 2026
@IvanHunters
IvanHunters marked this pull request as draft July 27, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(e2e): external-dns liveness kill-loop flakes external-dns-inmemory (missing startupProbe)

1 participant