Skip to content

PMM-15394 Let HAProxy start without waiting for every PMM replica - #937

Merged
theTibi merged 5 commits into
percona:PMM-HA-GAfrom
theTibi:PMM-15394
Sep 3, 2026
Merged

PMM-15394 Let HAProxy start without waiting for every PMM replica#937
theTibi merged 5 commits into
percona:PMM-HA-GAfrom
theTibi:PMM-15394

Conversation

@theTibi

@theTibi theTibi commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

PMM-15394

Removes the HAProxy init container. That is the whole change — init-addr last,libc,none is already in haproxy.cfg today, so nothing is added.

The gate turns a healthy cluster into no ingress

templates/haproxy-init-configmap.yaml required every PMM replica to answer HTTP 200 before HAProxy could start, then exit 1 after 60 attempts at 5s. The pod CrashLoopBackOffs at Init:0/1 and HAProxy never starts.

Every fresh install races this, and the chart guarantees it will. The StatefulSet has no podManagementPolicy, so it defaults to OrderedReady: pmm-ha-1 is created only once pmm-ha-0 is Ready, and pmm-ha-2 only once pmm-ha-1 is. For most of the five-minute budget the later replicas do not exist — no pod, no IP, no DNS record — and curl returns 000. PMM Server is a multi-GB image that also runs database migrations on first boot, so three replicas started one after another routinely exceed it.

PMM-15202 is this failure: all three HAProxy pods at Init:0/1 with ~100 restarts over 11 hours. Closed as Not a Bug after the reporter reinstalled with more resources.

It was also redundant

server-template pmm 1-N monitoring-service.<ns>.svc.cluster.local:8443 \
  check ssl verify none resolvers k8s init-addr last,libc,none

init-addr … none holds a slot with no address instead of aborting startup when the name does not resolve; resolvers k8s picks it up later; http-check expect status 200 against /v1/server/leaderHealthCheck brings it UP only once it is genuinely serving. HAProxy running before any PMM pod exists is the designed behaviour — it returns 503 until a backend is up, which beats not running.

The init container added no capability. It only converted "HAProxy up, no backends yet" into "no HAProxy at all".

Verified on a live cluster

Before — HAProxy was already serving with 7 of 10 slots holding unresolvable names:

pmm1     MAINT (resolution)
pmm3     UP                    ← the Raft leader
pmm5..10 MAINT (resolution)
BACKEND  UP

The gate, run unmodified from the live ConfigMap against two absent replicas:

✅ Instance 0 is ready (1/5)   ✅ Instance 1 (2/5)   ✅ Instance 2 (3/5)
❌ Connection check failed for pmm-ha2-3... (HTTPS status: 000)
❌ Connection check failed for pmm-ha2-4... (HTTPS status: 000)
❌ Timeout: PMM instances did not become ready within 5 minutes  →  exit 1

Three healthy replicas serving, and it still fails.

After — deleted a follower and restarted HAProxy in the same moment:

pmm-ha-haproxy-...-4q9qx   1/1  Running   8s     ← up while a replica was gone
pmm-ha-haproxy-...-rqw85   1/1  Running   10s
pmm-ha2-2                  2/2  Terminating

Rollout complete in ~47s, zero restarts, readyz and leaderHealthCheck 200 throughout. A follower was targeted deliberately so Raft quorum held at 2/3.

Changes

File Change
templates/haproxy-init-configmap.yaml deleted
values.yaml drop haproxy.initContainers and haproxy.extraVolumes — that volume only ever fed the script
Chart.yaml version bump

Verified locally: helm lint clean, rendered manifests drop 38 → 37 (the removed ConfigMap), the HAProxy Deployment renders no initContainers, and both init-addr last,libc,none and the leader health check are still present.

Notes for reviewers

Version. 1.6.01.6.2. .github/ct.yaml sets target-branch: main, so ct lint --check-version-increment=true compares against main, which is already at 1.6.1 — a bump to 1.6.1 here would fail the check, and 1.6.0 would read as a decrement. Worth noting that #935 currently carries no Chart.yaml bump and last passed CI before main moved to 1.6.1; it will likely hit the same check on its next run.

Interaction with #936. That PR (targeting main) fixes a line in the file this one deletes — its HAProxy readiness probe addressed pods by .Release.Name rather than the StatefulSet name. Whichever lands second resolves trivially in favour of the deletion.

Test coverage. The helm unittest harness lives in #936 against main. Once the bases converge I will add a suite asserting HAProxy renders no init container and that init-addr … none plus the leader health check stay in haproxy.cfg, so this cannot silently come back.

Also fixed for free

The init container was alpine:latest running apk add --no-cache curl at every start, so HAProxy could not start without egress to the Alpine package mirrors. Air-gapped and restricted clusters are affected.

Not in scope

podManagementPolicy: Parallel would remove the serialized startup that makes the budget impossible in the first place, and is arguably right for a Raft cluster where pmm-ha-0 currently has to reach Ready with no peers. It is an immutable StatefulSet field, so flipping it breaks helm upgrade on every existing install and needs a documented --cascade=orphan recreate. Worth its own ticket; this change does not depend on it.

🤖 Generated with Claude Code

The HAProxy pod ran an init container that required every PMM replica to answer
HTTP 200 before HAProxy could start, and exited 1 after five minutes - so the pod
CrashLoopBackOffs at Init:0/1 and HAProxy never starts at all.

Every fresh install races this, and the chart guarantees it will. The
StatefulSet uses the default OrderedReady policy, so pmm-ha-1 is created only
after pmm-ha-0 is Ready and pmm-ha-2 only after pmm-ha-1. For most of the
five-minute budget the later replicas do not exist, their names do not resolve,
and curl returns 000. PMM Server is a multi-GB image that also runs database
migrations on first boot, so three replicas started one after another routinely
exceed the budget. PMM-15202 is this failure, closed as Not a Bug.

The gate was also redundant. haproxy.cfg carries "init-addr last,libc,none", so
a backend whose name does not resolve is held with no address rather than
aborting startup, and "http-check expect status 200" against
/v1/server/leaderHealthCheck brings it UP only once it is genuinely serving.
HAProxy running before any PMM pod exists is the designed behaviour: it returns
503 until a backend is up, which beats not running. The init container added no
capability - it only turned "HAProxy up, no backends yet" into "no HAProxy".

Verified on a live 3-replica cluster. Before the change HAProxy was already
serving with seven of ten server-template slots in MAINT (resolution), i.e.
unresolved names. After removing the init container, deleting a follower and
restarting HAProxy in the same moment brought all three pods to 1/1 Running in
under ten seconds with zero restarts, and PMM stayed reachable throughout. The
chart's own init script, run unmodified against two absent replicas, exits 1
while three healthy replicas are serving.

Removing it also drops the alpine:latest image and its "apk add --no-cache curl"
at every HAProxy start, which required egress to the Alpine mirrors.

Signed-off-by: theTibi <tkorocz@gmail.com>
@theTibi
theTibi changed the base branch from main to PMM-HA-GA August 27, 2026 20:14
@theTibi
theTibi marked this pull request as ready for review August 27, 2026 20:15
@theTibi
theTibi requested a review from a team as a code owner August 27, 2026 20:15
@theTibi
theTibi requested review from 4nte and ademidoff and removed request for a team August 27, 2026 20:15
theTibi added a commit to theTibi/percona-helm-charts that referenced this pull request Aug 28, 2026
percona#937 (PMM-15394) removes templates/haproxy-init-configmap.yaml and the
haproxy.initContainers readiness gate. This PR referenced it in three places,
so merging both would have failed the ha-peers suite and left stale docs:

- tests/ha-peers_test.yaml: dropped the template from the suite and the
  data["wait-for-pmm.sh"] assertion.
- values.yaml, README.md: the HAProxy ConfigMap note now describes the one
  ConfigMap left after percona#937 - haproxy.cfg, which carries maxReplicas.

After percona#937 only one value is embedded in an HAProxy ConfigMap, so the guidance
gets simpler rather than weaker.

Signed-off-by: theTibi <tkorocz@gmail.com>
The only conflict was modify/delete on
charts/pmm-ha/templates/haproxy-init-configmap.yaml: PMM-15151 (percona#868) renamed
.Release.Name to pmm.fullname in the wait-for-pmm.sh host loop, in the same file
this branch removes. Resolved by keeping the deletion - the script's only consumer
was the wait-for-pmm-ready init container, which this branch also drops, and
nothing else in the chart references the ConfigMap.

Also removed pmm-ha-haproxy-init-script from the list of fixed-name namespaced
objects in the pmm-ha-dependencies README, since that ConfigMap is no longer
created.

Signed-off-by: theTibi <tkorocz@gmail.com>
PMM-15151 (percona#868) added a lint-test step asserting that the wait-for-pmm.sh host
loop renders pmm_host="<fullname>-$i..." - an assertion on the very file this
branch deletes, so it failed the moment PMM-HA-GA was merged in. Git saw no
conflict because the assertion and the deletion live in different files.

HAProxy no longer builds per-pod names at all: it resolves backends through the
headless service with server-template + init-addr, so there is nothing left to
assert. The PMM_HA_PEERS half of the check, which does still build per-pod names
from pmm.fullname, is kept as-is.

Signed-off-by: theTibi <tkorocz@gmail.com>
@theTibi
theTibi merged commit 924116e into percona:PMM-HA-GA Sep 3, 2026
4 checks passed
theTibi added a commit to theTibi/percona-helm-charts that referenced this pull request Sep 3, 2026
Two conflicts, both from work that landed on PMM-HA-GA after this branch forked:

- charts/pmm-ha/Chart.yaml: percona#937 bumped 1.6.1 -> 1.6.2 and appVersion to "3.9.1"
  while this branch bumped to 1.7.0. Kept 1.7.0 (still ahead of 1.6.2, and the
  render-breaking even-replicas check justifies the minor) with appVersion "3.9.1".

- charts/pmm-ha/templates/haproxy-init-configmap.yaml: percona#937 (PMM-15394) deleted the
  file; this branch had fixed its pmm_host loop to use pmm.fullname. Took the
  deletion - the fix has nothing left to apply to. Nothing references the ConfigMap,
  the init container or wait-for-pmm.sh any more.

Silent, non-conflicting overlaps checked by hand:

- _helpers.tpl pmm.haPeers: percona#868 (PMM-15151) landed the same .Release.Name ->
  pmm.fullname fix, and this branch had already adopted that block verbatim, so the
  merge is a no-op there. The peer-addressing suite stays as a regression guard.
- README.md: dropped the upgrade bullet announcing the PMM_HA_PEERS change. It ships
  in 1.6.2 via percona#868, so attributing it to 1.7.0 is now wrong. The even-replicas
  breaking change is unchanged.
- ci/ci-values.yaml: percona#946 (PMM-15441) rewrote the file and already sets
  clickhouse.keeper.replicasCount, so this branch's fix for the ignored
  clickhouse.keeper.replicas key is subsumed.
- The new examples/ and docs/SIZING.md from percona#935 set no PMM replicas value, so the
  new validators do not reject them.

helm unittest: 3 suites, 27 tests passed. Renders verified against the merged tree -
replicas=4, replicas=12 and clickhouse.keeper.replicasCount=2 all fail with the
intended messages.

Signed-off-by: theTibi <tkorocz@gmail.com>
theTibi added a commit to theTibi/percona-helm-charts that referenced this pull request Sep 3, 2026
Resolve charts/pmm-ha/values.yaml against PMM-15394 (percona#937), which removed the
HAProxy init container while this branch was rewriting the same block.

Keep from PMM-15393: topologySpreadConstraints and the PodDisruptionBudget that
replace the required podAntiAffinity, so replicaCount can exceed the worker-node
count without leaving pods Pending.

Take from PMM-15394: the removal of haproxy.initContainers and the extraVolumes
that mounted pmm-ha-haproxy-init-script. That ConfigMap template is deleted on
PMM-HA-GA, so keeping the init container would mount a ConfigMap that no longer
exists and block HAProxy startup. Its explanatory comment is preserved.

Signed-off-by: theTibi <tkorocz@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants