Skip to content

feat(replication): self-heal replicas stuck on repl auth error 1045 (opt-in) - #4

Merged
xcompass merged 7 commits into
mainfrom
feat/replica-reconnect-on-auth-error
Jul 19, 2026
Merged

feat(replication): self-heal replicas stuck on repl auth error 1045 (opt-in)#4
xcompass merged 7 commits into
mainfrom
feat/replica-reconnect-on-auth-error

Conversation

@xcompass

Copy link
Copy Markdown
Member

Summary

Adds an opt-in operator self-heal for replication replicas stuck on repl auth error 1045 (Access denied for user 'repl').

When spec.replication.replica.recovery.reconnectOnAuthError: true and a replica's IO thread reports a sustained Last_IO_Errno == 1045 (persisted past recovery.errorDurationThreshold, default 5m), the operator re-issues CHANGE MASTER — via ConfigureReplica(..., WithResetMaster(false)) — to re-sync the repl password from the secret. Once the replica reconnects (Last_IO_Errno → 0), it stops firing.

Why

Replica configuration is role-gated, not health-gated: in ReconcileReplicationInPod, a pod already in the Replica role early-returns without re-issuing CHANGE MASTER. So if the repl password diverges between the master's grant and the replica's master.info (e.g. a secret edit / manual ALTER USER / partial reconcile), the replica is stuck on 1045 forever — the operator never re-applies its credentials. This closes that gap.

Field incident: 7 hotcrp-* DBs on our prod cluster went StatefulSetNotReady on 1045 and had to be recovered by hand (STOP SLAVE; CHANGE MASTER TO MASTER_PASSWORD=…; START SLAVE).

Design notes

  • Opt-in, default off — zero behavior change when unset (predicate short-circuits on the flag).
  • Independent of recovery.enabled — no bootstrapFrom, no backup rebuild. (Note: recovery.enabled is a required CRD field, so the enabling manifest must set enabled: false alongside the flag.)
  • 1045 only — deliberately NOT added to recoverableIOErrorCodes (that backup-rebuild path is for data-divergence errors like 1236).
  • WithResetMaster(false) — a minimal, non-destructive re-CHANGE-MASTER (no RESET MASTER, GTID position preserved), mirroring the existing switchover reconnect path.

Changes

  • api/v1alpha1/mariadb_replication_types.goReconnectOnAuthError field on ReplicaRecovery + IsReplicaReconnectOnAuthErrorEnabled() helper.
  • pkg/controller/replication/auth_resync.go (new) — replicaAuthResyncQualifies predicate + podNeedsAuthResync wrapper + auth_resync_test.go (table-driven unit tests).
  • pkg/controller/replication/controller.go — the reconcile-path hook in the role-gate.
  • Regenerated CRDs / api_reference.md.
  • docs/superpowers/{specs,plans}/… — design spec + implementation plan.

Test plan

  • go build ./..., go vet ./... clean
  • go test ./pkg/controller/replication/ — predicate/wrapper unit tests + existing suite pass
  • Generators idempotent (make manifests code helm-crds docs-api manifests-crds → no further diff)
  • CI: unit + integration (envtest+Docker) suite green
  • CI: Artifacts (full make gen "Check diff") green
  • CI: helm lint green

Rollout (after merge)

  1. CI publishes ghcr.io/ubc/mariadb-operator:<sha> + fork charts.
  2. helm upgrade the appcloud operator (expect the fleet-wide agent roll).
  3. Set recovery: { enabled: false, reconnectOnAuthError: true } on the 7 hotcrp DBs in k8s-config.

https://claude.ai/code/session_01BCHp8v87idhtUwTJ5XJ5qu

xcompass added 7 commits July 18, 2026 13:00
Opt-in operator self-heal for replicas stuck on repl error 1045: when
spec.replication.replica.recovery.reconnectOnAuthError is set and a replica
reports a sustained 1045 IO error, re-issue CHANGE MASTER with the current
secret. Closes the role-gate gap in reconcileReplicationInPod that leaves
credential desyncs stuck indefinitely. Independent of the backup-rebuild
recovery path.

Claude-Session: https://claude.ai/code/session_01BCHp8v87idhtUwTJ5XJ5qu
Task-by-task TDD plan: API field + helper + predicate (unit-tested),
reconcile-path hook, CRD/deepcopy/docs regeneration, plus post-merge rollout.

Claude-Session: https://claude.ai/code/session_01BCHp8v87idhtUwTJ5XJ5qu
Regenerates config/crd/bases, the mariadb-operator-crds Helm chart CRDs,
deploy/crds/crds.yaml, and docs/api_reference.md to include the
spec.replication.replica.recovery.reconnectOnAuthError field added in
prior commits. zz_generated.deepcopy.go is unchanged: ReplicaRecovery's
DeepCopyInto already copies the whole struct by value (*out = *in), so a
plain bool field needs no new deepcopy logic.
… note

Whole-branch review found the example YAML would be rejected at apply time:
ReplicaRecovery.Enabled is a required CRD field, so enabling auth-resync needs
`recovery: { enabled: false, reconnectOnAuthError: true }`. Also corrected the
churn risk note (fires per-reconcile, not per-threshold, when the secret is wrong).

Claude-Session: https://claude.ai/code/session_01BCHp8v87idhtUwTJ5XJ5qu
The operator chart's default image tag is the chart appVersion (26.6.0+ubc.1),
an invalid OCI reference (Docker tags can't contain '+') that is also not the
image built/loaded in CI. `ct install` therefore rendered the operator,
webhook, and cert-controller pods with an unparseable image -> InvalidImageName
-> cert-controller never provisioned the webhook cert -> webhook FailedMount ->
install timed out. Override image/webhook/certController repo+tag with the
locally-built IMG_NAME:VERSION (derived from the Makefile) so the install test
exercises the just-built image. Pre-existing since the 26.6.0+ubc.1 bump;
surfaced now because this is the first chart change to run ct install.

Claude-Session: https://claude.ai/code/session_01BCHp8v87idhtUwTJ5XJ5qu
@xcompass
xcompass merged commit 3cdc6f7 into main Jul 19, 2026
11 checks passed
@xcompass
xcompass deleted the feat/replica-reconnect-on-auth-error branch July 19, 2026 00:00
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.

1 participant