Unreleased documentation. Choose your installed release in the version menu. Features described here may be absent from that release.
Configuration model¶
The controller is headless: no CLI flags carry domain configuration, no config files are mounted. Operators interact with it through the HAProxyTemplateConfig CRD (primary configuration), the credentials Secret it references (Security — Credentials), and the HTTP surface — /metrics (Monitoring) plus /healthz and /debug/* on a shared listener (Debugging).
Structured logfmt logs on stdout (via slog.NewTextHandler) round out the operational surface — the level is set by LOG_LEVEL at startup, then dynamically overridden at runtime by the CRD's spec.logging.level once the controller's configloader picks it up.
What the CRD covers¶
HAProxyTemplateConfig.spec is the source of truth for controller behaviour. There is one such object; it pulls in HAProxyTemplateLibrary objects through an ordered spec.libraryRefs and the controller merges them, later wins, with the config itself last — so the merged spec is what everything downstream sees. It has four top-level groups:
- Runtime settings —
controller(includingcontroller.configPublishing),dataplane,logging,templatingSettings. - Resource watching —
podSelector,watchedResources,watchedResourcesIgnoreFields. (HTTP fetching is driven by thehttp.Fetch()template function — URLs that appear in templates are auto-registered; there is no top-levelspec.httpResourcesfield, onlyvalidationTests[].httpResources(a sibling offixtures, not nested inside it) for mocking responses during tests.) - Templates —
haproxyConfig,templateSnippets,maps,files,sslCertificates,k8sResources(declarative Kubernetes resources rendered and applied via Server-Side Apply). - Validation —
validationTests, the per-resourceenableValidationWebhookflag, andvalidators(pluggable external validator sidecars).
The full field reference (types, defaults, validation rules) lives in CRD Reference, which also opens with a runnable minimal example; the installation walkthrough is Getting Started. This page shows how the pieces compose.
Configuration layers¶
Users commonly compose configuration from three layers, in order of precedence:
- Template libraries shipped in the Helm chart (base, SSL, ingress, gateway, haproxytech, …). The chart renders each enabled one as its own
HAProxyTemplateLibrary, named<configName>-<library>. A library carries content only —templateSnippets,validationTests,maps,files,sslCertificates,k8sResources,templatingSettings,haproxyConfig— neverpodSelector,watchedResourcesordataplane. controller.configin Helm values — rendered as the singleHAProxyTemplateConfignamed<configName>and merged last, so anything set here wins over every library.- Direct
HAProxyTemplateConfigedits (viakubectl edit htplcfg <configName>) for ad-hoc overrides. That object stays small — about 1% of etcd's per-object limit — because the bulk lives in the libraries. Editing a library in place works too and takes effect immediately;helm upgradeoverwrites it.
Merge order is declared once, in spec.libraryRefs, and nowhere else. Each entry
also names a revision that the referenced object must report: the controller
compares the two strings and never derives either from the content, so a
half-applied set shows up as a mismatch and the controller keeps serving the
last-good configuration rather than rendering a set with a library missing. An
in-place edit leaves the revision untouched, which is why it takes effect
immediately.
The merge uses the same primitive Helm's mustMergeOverwrite does, so the result
is what a chart-side merge would have produced. validationTests from every source are combined — a test name
defined by two sources is an error naming both. See
ADR-0017.
One object per library exists because the single merged object had reached 99.4%
of the ~1.5 MiB limit Kubernetes enforces per object. make cr-size-check gates
each object on every chart-test run.
Because templates are just strings inside a CRD, the chart layers and the user's own values can both contribute snippets and be composed at render time. See Templating Guide for how snippets and extension points interact.
Runtime API resolution¶
The merged config declares which resource versions the templates support. The
controller resolves each ordered apiVersions list against live discovery and
selects the first served candidate. It doesn't substitute the cluster's preferred
version: a version absent from the list may have a shape the templates can't use.
This produces an effective config before template compilation. Unavailable optional
watches and entries whose requires dependencies are unavailable are removed;
missing required resources fail initialization. A discovery error can't establish
that an optional resource is absent. See Watching resources
for the configuration fields and template access rules.
Reloading behaviour¶
Changes to the HAProxyTemplateConfig or its referenced libraries are merged and
validated before replacement. A rejected proposal leaves the current iteration
running. Accepted changes trigger an internal reinitialization; the debounce
combines bursts such as a Helm upgrade. The Secret referenced by
credentialsSecretRef is also watched, so credential rotation requires no pod
restart.
The serving iteration remains active while its successor starts from the accepted config, credentials, and discovery snapshot. The successor waits for its render graph to warm, with a bounded timeout, then takes over leadership on the same replica. Only after successful startup is the predecessor torn down. A failure before leadership handover leaves the predecessor serving; retries fetch live state instead of reusing the consumed snapshot. A reload during unfinished startup cancels that startup and carries the newer snapshot into the next attempt.
Process-owned health and webhook listeners remain available across iterations; admission fails closed whenever no ready validator is installed. HAProxy continues serving its last applied configuration throughout the transition.
Installing, upgrading, or removing a relevant CRD can change API resolution. The controller rechecks discovery and restarts the iteration when the effective config changes. Rebuilding watchers, schemas, templates, and admission together prevents them from using different resource versions. Re-resolution runs all config validators before activation, including when no embedded validation tests exist. The controller doesn't swap individual informers into a running iteration.