Monitoring

Bloodraven exposes Prometheus metrics, a REST status API, and a WebSocket endpoint for real-time status streaming. For the structured-log field set and the stable msg vocabulary that downstream log pipelines key off of, see the Log schema contract.
Minimum alert set
| Signal | Why it matters | Runbook |
|---|---|---|
| Operator down | Reconciliation and DNS updates stop | Operator unavailable |
| No writable site | Application writes are unavailable | Emergency manual promotion |
| Split-brain detected | More than one site may accept writes | Split-brain recovery |
| Replication lag high | RPO risk is increasing | Replication lag high |
| Divergent transactions | A site holds transactions the primary lacks: an old primary cannot safely rejoin, or a replica (often replicating normally) is excluded from ordered updates and planned failovers | Divergent recovery, Divergent replicas |
| Backup stale | Recovery point is aging | Failed backup |
| Verification stale | Backups are not proven restorable | Backup Verification |
| PITR archive lagging | PITR RPO target may be missed | Backup And Restore |
| Dragonfly degraded | Cache/session continuity may be unavailable during planned failover | Playground Dragonfly co-management |
Operator health
Check these before debugging individual failover groups:
kubectl rollout status deployment/bloodraven -n bloodraven
kubectl logs -n bloodraven deploy/bloodraven
kubectl port-forward -n bloodraven deploy/bloodraven 8080:8080
curl http://localhost:8080/metrics | grep '^bloodraven_'
Prometheus metrics
Metrics are served on :8080/metrics in standard Prometheus exposition format.
Available metrics
Deployment lease metrics are listed in the Deployment API metric reference.
Active lease counts are dimensionless; age is seconds; grants, expirations,
revocations, and deferrals are event counts. The bounded labels are kind
(migration, failover-hold), grant result (four values), and revocation
reason (TopologyChanged, OperatorRevoked). group scales with managed
group names; operationId, instance, and token material are never metric
labels. Per group, active/age/expiry families have at most two kind series,
grants have at most eight kind/result series, and revocations have at most
four kind/reason series. The deferral counter uses a bounded controller
reason such as DeploymentHold. These families have no namespace label;
same-named groups in different namespaces cannot be distinguished by them.
| Metric | Type | Labels | Description |
|---|---|---|---|
bloodraven_poll_latency_seconds | Histogram | site | Duration of each MySQL probe. A site in probe backoff records nothing on the polls that skip it. |
bloodraven_state_transitions_total | Counter | site, from, to | Count of state transitions per site. from and to are kebab-case site states, as on bloodraven_site_state. |
bloodraven_taint_operations_total | Counter | site, action | Count of node taint/untaint operations |
bloodraven_dns_flips_total | Counter | site | Count of DNSEndpoint updates (DNS record flips) |
bloodraven_failovers_total | Counter | target_site | Total number of failovers executed. Incremented after successful MySQL promotion. |
bloodraven_keyring_promotions_blocked_total | Counter | namespace, group, site, outcome | Promotion decisions that skipped or refused a site because it is mid-keyring-rotation. outcome is skipped or refused. Incremented once per decision, not per Event reminder. |
bloodraven_primary_reassert_total | Counter | site | Times the operator restored writability on the promoted primary after finding it fenced with no writable site remaining (typically the target's own sidecar re-fenced it with a stale lease right after a promotion, or the primary's MySQL restarted and came back with the read_only=ON every mysqld boots with). A steadily increasing counter means something keeps fencing the primary โ check the sidecars' connectivity to the operator's auxiliary Service. |
bloodraven_websocket_connected_clients | Gauge | -- | Number of currently connected WebSocket clients |
bloodraven_replication_lag_seconds | Gauge | namespace, group, site, role | Replication lag in seconds on a follower site. -1 if lag is NULL (not replicating). Only present for replica sites. role is the kebab-case of spec.sites[].role: primary-candidate, disaster-recovery, or read-only. |
bloodraven_replication_running | Gauge | namespace, group, site, role, thread | Whether a replication thread is running (1=yes, 0=no). Thread is io or sql. Only present for replica sites. |
bloodraven_restore_reclone_pending | Gauge | namespace, group, site, phase | 1 for each site a full-instance in-place restore has not recloned yet (status.restoreReclones.pending); phase is the kebab-case of the queue phase (armed, draining, held). Such a site's replication receiver is stopped by design until its reclone, so exclude it from replication alerts (see BloodravenReplicationDown). The series is removed when the site leaves the queue. |
bloodraven_replication_source_state | Gauge | namespace, group, site, state | Direct-source convergence state set for each follower, scoped by failover group. state is one of converged, pending, or blocked; one series is 1 and the others are 0. Cleared for the active primary. |
bloodraven_site_state | Gauge | namespace, group, site, role, state | Current site state as a state-set: 1 for the current state, 0 for others. state is the kebab-case of status.sites[].state: writable, read-only, unreachable, or unknown. role is the kebab-case of spec.sites[].role. |
bloodraven_divergent_transactions | Gauge | site | Number of transactions on a site that the primary lacks: a returning old primary after emergency failover, or a replica found by the replica GTID audit (which runs whatever the replication threads report). 0 when healthy. Non-zero means the site is RecoveryBlocked: it is not promoted by an ordered update or planned failover, and Degraded is True with reason DivergentTransactions. |
bloodraven_archiver_upload_failures | Gauge | namespace, group, site | Cumulative PITR archiver upload failures reported by the per-site sidecar, including one per failed archive store construction attempt. Monotonic except across sidecar restarts โ use increase() / rate() in dashboards. |
bloodraven_archiver_last_upload_timestamp_seconds | Gauge | namespace, group, site | Unix timestamp of the last successful PITR binlog archive per site. 0 if nothing archived yet. |
bloodraven_archiver_backlog_files | Gauge | namespace, group, site | Sealed binlogs present in the MySQL index but missing from the archiver manifest at the end of the last scan. >0 means archival is falling behind. |
bloodraven_backup_verified_timestamp_seconds | Gauge | group, profile | Unix timestamp of the last Succeeded MysqlBackupVerification per profile. Anchor staleness alerts on this gauge โ a fresh bloodraven_backup_last_success_timestamp_seconds without a fresh verification means nobody has proven the backup can be restored. |
bloodraven_backup_verification_last_attempt_timestamp_seconds | Gauge | group, profile | Unix timestamp of the last terminal verification attempt, regardless of result. Distinguishes "verification never ran" from "verification ran but failed". |
bloodraven_backup_verification_runs_total | Counter | group, profile, result | Terminal verification attempts labelled success or failure. |
bloodraven_backup_verification_duration_seconds | Histogram | group, profile | Wall-clock duration of a verification run. |
bloodraven_backup_verification_replay_lag_seconds | Gauge | group, profile | For successful mode: Latest PITR verifications only: completionTime โ pointInTime.newestReplayedCommitTime. Timestamp drills never set it, because their newest replayed event is a historical target. A rising value means archived binlogs trail the live primary โ alert below your RPO target. |
bloodraven_restore_duration_seconds | Histogram | namespace, group, restore_kind, target_site | Data-plane duration of successful restore Jobs. restore_kind is init_from_backup or in_place; duration starts at Job status.startTime when available and ends when the operator observes terminal success. |
bloodraven_restore_last_success_timestamp_seconds | Gauge | namespace, group, restore_kind, target_site | Unix timestamp of the last successful restore Job observation. |
bloodraven_restore_last_source_size_bytes | Gauge | namespace, group, restore_kind, target_site | Source backup artifact size in bytes for the most recent successful restore when known from MysqlBackup.status.sizeBytes. Direct S3/PVC restores and unknown sizes clear/omit this series so an older known size is not reported as current. |
bloodraven_dragonfly_site_up | Gauge | group, site | Dragonfly site reachability from the operator's latest INFO replication poll (1 reachable, 0 unreachable). |
bloodraven_dragonfly_promotions_total | Counter | group, target_site, result | Dragonfly promotion attempts labelled success, failed, skipped, or sessions_lost. success is a REPLTAKEOVER that preserved continuity. sessions_lost is a successful REPLICAOF NO ONE fallback โ the target is writable but session continuity is not guaranteed. failed means the target was not promoted. |
bloodraven_dragonfly_manager_panics_total | Counter | namespace, name | Panics recovered inside the Dragonfly manager polling loop. Any increase should be investigated. |
bloodraven_license_info | Gauge | namespace, group, organization, edition, valid | Constant-1 info series for the license observed on a failover group. edition is community, production, or organization. valid is the string true or false. No license is edition="community", valid="true". An invalid token is edition="community", valid="false". An ended update period stays valid="true". One series per group. |
bloodraven_license_updates_expiry_timestamp_seconds | Gauge | namespace, group, organization, edition | Unix timestamp when the paid update period ends. Absent for community and invalid licenses. A past value is the supported perpetual state. Alert with (bloodraven_license_updates_expiry_timestamp_seconds - time()) / 86400 < 30. |
Dragonfly replica full-resync state is exposed on the CR as
status.dragonfly.sites[].syncInProgress, with companion fields
linkStatus, lastIOSecondsAgo, and ready. syncInProgress and
lastIOSecondsAgo are absent when Dragonfly's INFO replication did not
report them. If you export custom-resource
status with kube-state-metrics or another CRD status adapter, alert when
syncInProgress remains true for longer than the expected warm-up window or
when it toggles repeatedly outside planned failovers and Dragonfly image
rollouts. Frequent full resyncs can spike master latency and reduce planned
failover session-preservation confidence.
Scrape configuration
Add a scrape job for the operator:
# prometheus.yml
scrape_configs:
- job_name: bloodraven
kubernetes_sd_configs:
- role: pod
namespaces:
names: [bloodraven]
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
regex: bloodraven
action: keep
- source_labels: [__meta_kubernetes_pod_container_port_number]
regex: "8080"
action: keep
Or if using the Prometheus Operator, create a PodMonitor:
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: bloodraven
namespace: bloodraven
spec:
selector:
matchLabels:
app.kubernetes.io/name: bloodraven
podMetricsEndpoints:
- port: metrics
interval: 15s
Alerting rules
Recommended alerts:
groups:
- name: bloodraven
rules:
# Site has no healthy primary
- alert: BloodravenNoPrimary
expr: |
count by (site) (
bloodraven_state_transitions_total{to="writable"}
) == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Site {{ $labels.site }} has no writable primary"
# High poll latency may indicate network issues
- alert: BloodravenHighPollLatency
expr: |
histogram_quantile(0.99, rate(bloodraven_poll_latency_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "Poll latency to {{ $labels.site }} exceeds 1 second (p99)"
# Failover occurred
- alert: BloodravenFailoverOccurred
expr: increase(bloodraven_failovers_total[5m]) > 0
labels:
severity: warning
annotations:
summary: "Failover occurred: {{ $labels.target_site }} promoted as new primary"
# Replication lag exceeds threshold on a data-bearing replica.
# role=~"primary-candidate|disaster-recovery" pages RPO risk and stays
# silent on role="read-only" readers, whose lag is designed (they are shed
# from mysql-<group>-replicas). For promotable replicas only, use
# role="primary-candidate".
- alert: BloodravenReplicationLagging
expr: bloodraven_replication_lag_seconds{role=~"primary-candidate|disaster-recovery"} > 300
for: 2m
labels:
severity: warning
annotations:
summary: "Replication lag on {{ $labels.group }}/{{ $labels.site }} ({{ $labels.role }}) is {{ $value }}s"
# A site holds transactions the primary lacks (old primary after an
# emergency failover, or a replica found by the GTID audit)
- alert: BloodravenDivergentTransactions
expr: bloodraven_divergent_transactions > 0
labels:
severity: critical
annotations:
summary: "{{ $labels.site }} has {{ $value }} transactions the primary lacks โ reclone it or make the primary contain them; ordered updates and planned failovers will not promote it"
# Replication thread down on a data-bearing replica. Sites awaiting
# their post-restore reclone have their receiver stopped by design.
- alert: BloodravenReplicationDown
expr: |
bloodraven_replication_running{role=~"primary-candidate|disaster-recovery"} == 0
unless on(namespace, group, site) bloodraven_restore_reclone_pending == 1
for: 1m
labels:
severity: critical
annotations:
summary: "Replication {{ $labels.thread }} thread stopped on {{ $labels.group }}/{{ $labels.site }} ({{ $labels.role }})"
# A failed full-instance restore left peers held with their
# replication stopped; an administrator must decide what to keep.
- alert: BloodravenRestoreReclonesHeld
expr: bloodraven_restore_reclone_pending{phase="held"} == 1
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.group }}/{{ $labels.site }} is held after a failed in-place restore: no replication, not promotable"
# No writable site in a failover group
- alert: BloodravenNoWritableSite
expr: |
max by (namespace, group) (bloodraven_site_state{state="writable"}) == 0
for: 30s
labels:
severity: critical
annotations:
summary: "No site is currently writable in {{ $labels.namespace }}/{{ $labels.group }}"
# Dragonfly site unavailable; MySQL remains authoritative, but planned
# session/cache continuity is at risk.
- alert: BloodravenDragonflySiteDown
expr: bloodraven_dragonfly_site_up == 0
for: 2m
labels:
severity: warning
annotations:
summary: "Dragonfly site {{ $labels.site }} is unreachable for {{ $labels.group }}"
# Failed Dragonfly promotion means the MySQL failover may have succeeded
# with cache unavailable.
- alert: BloodravenDragonflyPromotionFailed
expr: increase(bloodraven_dragonfly_promotions_total{result="failed"}[5m]) > 0
labels:
severity: warning
annotations:
summary: "Dragonfly promotion failed for {{ $labels.group }} target {{ $labels.target_site }}"
# Empty-master fallback: target is writable but REPLTAKEOVER did not run.
- alert: BloodravenDragonflySessionsLost
expr: increase(bloodraven_dragonfly_promotions_total{result="sessions_lost"}[5m]) > 0
labels:
severity: warning
annotations:
summary: "Dragonfly promoted {{ $labels.target_site }} via REPLICAOF NO ONE for {{ $labels.group }}; session continuity is not guaranteed"
# A recovered panic keeps the manager alive, but it is still a bug signal.
- alert: BloodravenDragonflyManagerPanic
expr: increase(bloodraven_dragonfly_manager_panics_total[5m]) > 0
labels:
severity: critical
annotations:
summary: "Dragonfly manager recovered a panic for {{ $labels.namespace }}/{{ $labels.name }}"
Grafana dashboards
Bloodraven ships five ready-to-use Grafana dashboards covering every metric the operator publishes. They live in the chart at charts/bloodraven/dashboards/ and are also installable as ConfigMaps via the Helm chart.
| Dashboard | UID | What it's for |
|---|---|---|
| Overview | bloodraven-overview | Health at a glance: writable sites, state timeline, failover activity, replication lag, backup/archiver freshness. Start here. |
| Failover & Topology | bloodraven-failover | Auto and planned failovers, durations, lag-wait histograms, state transitions, DNS flips, node taints, split-brain resolves. |
| Replication & Recovery | bloodraven-replication | Replication lag per site, IO/SQL thread up/down, divergent transactions, reclone ops, poll latency. |
| Backups & Verification | bloodraven-backups | Backup run/failure counts, duration, size, last-success age, verification runs, PITR replay lag. |
| PITR Archiver | bloodraven-archiver | Per-site archiver upload age, backlog files, upload failures. |
All five dashboards share a datasource variable and cross-link in the top-left corner, so you can roam between them without losing the time range.
Setup โ three paths
Pick whichever matches your Grafana install. All three use the same JSON files.
1. Helm chart + kube-prometheus-stack (zero-config)
If your Grafana is deployed by kube-prometheus-stack or the upstream grafana/grafana chart, its dashboard-sidecar watches for ConfigMaps labelled grafana_dashboard: "1" by default. Just enable the flag:
helm upgrade --install bloodraven bloodraven/bloodraven \
--namespace bloodraven --create-namespace \
--set grafanaDashboards.enabled=true
The chart renders one ConfigMap per dashboard, the sidecar picks them up within ~30s, and the dashboards appear in a "Bloodraven" folder in Grafana. No restart, no re-login.
Common tweaks in values.yaml:
grafanaDashboards:
enabled: true
# Sidecar is often scoped to the monitoring namespace
namespace: monitoring
# Change folder name
folder: MySQL / Bloodraven
# If your sidecar watches a non-default label
label: grafana_dashboard
labelValue: "1"
2. Grafana file-based provisioning
If you provision Grafana from disk, copy the JSON files into your provisioning directory and point a provider at them:
# One-time copy (re-run on upgrade to get dashboard updates)
kubectl -n monitoring cp -c grafana \
bloodraven/charts/bloodraven/dashboards \
grafana-pod:/var/lib/grafana/dashboards/bloodraven
# /etc/grafana/provisioning/dashboards/bloodraven.yaml
apiVersion: 1
providers:
- name: bloodraven
folder: Bloodraven
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards/bloodraven
3. Manual UI import
For one-off installs:
- In Grafana, click Dashboards โ New โ Import.
- Paste the contents of any file in
charts/bloodraven/dashboards/. - Pick your Prometheus datasource when prompted.
- Repeat for each dashboard you want.
Cross-dashboard links use the dashboard UID (bloodraven-overview, etc.) so they keep working as long as you don't edit the UID on import.
Prerequisites
- Your Prometheus must be scraping the operator โ either the ServiceMonitor the chart ships (
--set metrics.serviceMonitor.enabled=true) or an equivalent scrape job. - The dashboards use standard Prometheus histograms/gauges, so no recording rules are required. Alerts in the next section can sit alongside them.
Kubernetes Events
Bloodraven records standard Kubernetes Events on all five of its kinds: MysqlFailoverGroup, MysqlBackup, MysqlBackupVerification, MysqlDatabase and MysqlStandbyCluster. These events can be forwarded to Slack, PagerDuty, or any webhook endpoint using tools like Kubewatch, Argo Events, or Event Router.
The tables below list every Event reason the operator records, with the one type (Normal or Warning) it is always recorded with. They are generated from the reason registry in api/v1alpha1/events.go. Reasons are named <Subject><PastTenseVerb>, optionally followed by a qualifier that names the cause: Succeeded and Failed for an operation, Rejected for a request that is not admitted, Refused for an automatic action blocked for safety. An Event that accompanies a status condition and repeats that condition's reason (a condition mirror) keeps the condition's reason instead.
Topology and failover
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
FailoverExecuted | Normal | A failover completed and a new primary was promoted. |
DataLossDetected | Warning | Divergent transactions found on old primary after emergency failover. |
DivergentReplicaDetected | Warning | The replica GTID audit found a replica holding transactions the primary lacks; emitted on first detection and when the count changes. |
DivergenceResolved | Normal | A replica's divergence report was cleared by a fresh containment proof (after a reclone, or after the primary was made to contain the set). |
DivergentSitePromoted | Warning | A site carrying a divergence report became the primary (an emergency failover with no clean candidate); its extra transactions are now authoritative. Also recorded as the DivergentPromotion condition. Repeats the condition reason. |
OldPrimaryRecovered | Normal | The old primary recovered after a failover and is now replicating from the new primary. |
SplitBrainDetected | Warning | Both sites are writable (split brain). Also emitted when a second site answers writable before the debounce confirms it; the message names the unconfirmed sites, and fencing waits for the confirmation. |
NoPrimaryDetected | Warning | Both sites are read-only (no primary). Not emitted when the cause is a mid-rotation or pending-reclone promotion refusal (that is KeyringPromotionRefused or PromotionRefusedReclonePending), or while the group is Initializing after an operator start. |
TotalLossDetected | Warning | Both sites are unreachable. |
SiteRecovered | Normal | Degraded condition cleared, topology is healthy. Not emitted when the group leaves Initializing, which is not a degraded state. |
PromotionRefusedReclonePending | Warning | Automatic promotion refused: the remaining candidates have not been recloned since a full-instance in-place restore and still hold pre-restore data (status.restoreReclones.pending). The group stays without a writable primary until the old primary returns, or until status.restoreReclones is removed to accept the stale copy (see Peer reclones). |
TopologyManagerStartFailed | Warning | The group has no running topology manager (no polling, status updates or failover) because starting one keeps failing, usually over an operator or TLS Secret that is missing, an operator Secret without a username or password key, or TLS material that will not load. The message carries the error; the runner retries every 30s. Emitted on the first failure after a running manager was torn down, otherwise once the failure outlives one retry; then at most once per 5 minutes (a changed error) or every 10 minutes (unchanged). The failed to start topology manager log line still fires on every attempt. |
TopologyManagerStarted | Normal | A topology manager started after a TopologyManagerStartFailed Warning; the message gives the number of failed attempts. The operator keeps this history in memory only: after an operator restart a recovery is not announced, so neither Event proves the current state โ the failed to start topology manager log line, repeated every 30s while the group has no manager, does. |
OrderedUpdateStalled | Warning | An ordered update stopped on a site whose pod is not Ready: one it just rolled, or one already on the new pod template. Nothing else is rolled or handed off until that pod has been Ready continuously for 30s. The message (also the Updating condition's) names the site, pod and cause. Emitted once per stall, not on every retry; a crash loop reads the same (crash looping, last exit code โฆ) in every phase of the loop. See Ordered updates. Repeats the condition reason. |
RecloneRequested | Normal | A valid bloodraven.shipstream.io/reclone-site annotation was accepted (an administrator's, or the in-place restore's reclone queue); CLONE INSTANCE will start on the next poll, or once a planned failover, ordered update, promotion or in-place restore holding the sites has finished. |
RecloneRejected | Warning | The reclone annotation failed the safety interlock, or named a site whose post-restore reclone queue is Armed or Held. The interlock refuses a value that does not parse as <site>:confirm=<group>:uid=<group UID>[:gtidPrefix=<prefix>] (no site, an unknown key, a bare site name), a confirm= or uid= that does not name this group, an unknown site, and a divergent-GTID prefix that is missing, too short or mismatched, or given for a site with no recorded divergence. The message names the full value to set. The annotation is cleared so the administrator can retry. |
BootstrapRefusedRestoreHold | Warning | A clone into a site was refused because an in-place restore holds it (its reclone queue is Armed or Held, or the topology is frozen). The site keeps its data and its stopped replication. |
SidecarVersionSkew | Warning | The group's sidecar image (a spec.sidecar.image pin, or the operator's default BLOODRAVEN_SIDECAR_IMAGE when it pins none) is tagged with a different release than the running operator. The two share a rendering contract and are supported only at matching releases; on a group with spec.tls, an older sidecar cannot reach MySQL at all. Advisory only: the operator never blocks on it, and it is not emitted for digest-pinned or untagged images. |
MysqlConfIgnored | Warning | Entries of the group or site spec.mysqlConf were left out of the rendered my.cnf: a key outside [A-Za-z0-9_.-]+ or a value with a line break, or a key that would override an operator-owned setting, including through a loose-, skip-, disable-, enable- or maximum- spelling. The message names the site and the keys; remove them from spec.mysqlConf. Admission refuses the malformed keys and values, so only an object stored before that rule reaches the first case. Also emitted, without a site, when spec.backup.pitr.maxBinlogSize is not a byte count with an optional k, m or g suffix (likewise refused at admission): my.cnf gets the 100M default instead. |
Refused workloads and metadata
Recorded on MysqlFailoverGroup and MysqlBackupVerification.
The operator refuses to act on something until a human fixes it, and does not retry in a loop. Each Event repeats the condition reason it sets and is recorded once per finding.
| Reason | Type | Description |
|---|---|---|
WorkloadSelectorMismatch | Warning | A site's Deployment or Dragonfly StatefulSet has a selector other than the one the operator renders (for example an object left from an older release with other label keys). The selector is immutable, so the operator does not update the object and reports Degraded=True/WorkloadSelectorMismatch; the message names the object. Delete it so the operator re-creates it (a MySQL site keeps its PVC). Repeats the condition reason. |
MetadataVersionUnsupported | Warning | An internal annotation payload carries a schemaVersion this operator does not know (a newer operator wrote it, or it was edited). It proves nothing: on a MysqlFailoverGroup (the planned-failover terminal marker, a deployment lease record) it is reported as Degraded=True/MetadataVersionUnsupported and the payload is refused, except that a terminal marker naming a run with outcome ManualRecoveryRequired still holds automatic failover. An unreadable lease record refuses lease requests (the deployment API answers 423 unstable with this reason), the admission of planned operations, and the reopening of admission after a topology change, until the Lease is deleted or an operator that reads it runs; on a MysqlBackupVerification (the verification Job's source record) the run fails with Verified=False/MetadataVersionUnsupported. Repeats the condition reason. |
Planned failover
Recorded on MysqlFailoverGroup.
See Planned failover for the phases, outcomes and the automation hold.
| Reason | Type | Description |
|---|---|---|
PlannedFailoverStarted | Normal | A planned-failover request was accepted, or a deferred one resumed; the message gives the source, the target, maxLagWait and drainTimeout. The run enters Admitting. |
PlannedFailoverSkipped | Normal | The planned-failover target is already the active primary, so nothing is done and the request is removed. A deferred run that resolves this way then ends NotStarted with AlreadyActive, which also emits PlannedFailoverRejected. |
PlannedFailoverRejected | Warning | A planned-failover request was refused and the annotation removed: its value is invalid, the run ended NotStarted (the message gives the reason, for example TargetUnhealthy, CooldownActive or AdmissionTimeout), another run is still running, an automation hold stands, or an earlier run's fenced source has not healed. Nothing was fenced; fix the cause and re-annotate. See Planned failover outcomes. |
PlannedFailoverDeferred | Normal | Planned switchover waits for cooldown or a deployment hold. Deferred/DeploymentHold status names the blocking operation and instance and gives an expiry-based retry time. No action is required while an expected deployment is progressing; investigate an unexpectedly long hold. |
PlannedFailoverAdmissionDelayed | Normal | A planned failover is waiting to be admitted; the message gives the admission.waitReason (for example PeerUnreadable, TargetNotReady, UpdateInFlight or AuthorityUnsettled) and its detail. Emitted when the wait reason or its detail changes. Nothing is fenced while it waits, and the run ends NotStarted with AdmissionTimeout if it is not admitted within 2 minutes. |
PlannedFailoverClaimGranted | Normal | The planned failover was granted the claim; the message names the run, source and target. The topology manager defers emergency failover, ordered updates and other topology changes while the run holds it, and fencing the source starts next. |
PlannedFailoverDrainStarted | Normal | The source is fenced (super_read_only=ON) and the planned failover is closing application connections on it, for at most drainTimeout. Writes are paused from here until the target is promoted or the run rolls back. |
PlannedFailoverTargetCaughtUp | Normal | The target applied every transaction in the fenced set; the message gives how long that took. The run promotes the target next, after syncing Dragonfly when it is enabled. |
PlannedFailoverDNSUpdateFailed | Warning | A planned failover promoted its target but the DNS update failed; the message carries the error. The topology manager retries the update, and the run records the DNSUpdateFailed warning on its result. Check the DNS provider if clients still resolve the old primary. |
PlannedFailoverRollbackStarted | Warning | A planned failover failed before anything was written to its target and entered RollingBack; the message gives the reason (for example LagTimeout, FenceFailed, SourceLost, DivergentTarget, DragonflySyncTimeout or DragonflyPromotionFailed). The operator unfences the source unless another site is writable; the run then ends Failed, normally RolledBack or SourceLeftFenced, with PlannedFailoverFailed. |
PlannedFailoverSucceeded | Normal | The planned failover completed: the target is the primary and status.lastFailover records it. The message lists any warnings the run recorded, such as DNSUpdateFailed, or none. |
PlannedFailoverFailed | Warning | A planned failover ended Failed with outcome RolledBack, SourceLeftFenced or PromotedUnconfirmed; the message gives the outcome, reason and status message. NotStarted runs emit PlannedFailoverRejected instead. See Planned failover outcomes. |
PlannedFailoverPromotionConfirmationFailed | Warning | super_read_only=OFF may have reached the target but the promotion could not be confirmed. Check which site is writable. |
PlannedFailoverStalledManagerAbsent | Warning | A planned failover that holds its claim has had no topology manager for 30 seconds; it ends after 5 minutes. |
PlannedFailoverStatusStalled | Warning | A planned failover's status writes failed for 2 minutes and the run ended with the planned-failover-terminal annotation, or the operator cannot write the group at all. |
PlannedFailoverLegacyRunRefused | Warning | A planned-failover block written by an older operator was refused after an upgrade. A SourceLeftFenced refusal (the old run was draining or waiting for lag) or a PromotedUnconfirmed refusal (it was promoting or resuming, so the target may be writable) needs a human. |
PlannedFailoverManualRecoveryRequired | Warning | A planned failover may have written to its target and cannot explain the result. Automation is held (AutomaticFailoverHeld=True) until acknowledged. Page on it; see the hold runbook. Repeats the condition reason. |
PlannedFailoverHoldPersisted | Warning | Repeated every 10 minutes while an automation hold stands. |
PlannedFailoverHoldAcknowledged | Normal | The hold was acknowledged and cleared; automation resumes. Repeats the condition reason. |
PlannedFailoverHoldAcknowledgmentIgnored | Warning | The acknowledge-planned-failover-hold annotation resumed nothing: it names a run other than the held one, no automation hold is standing for the run it names, or the planned-failover claim is held for another run. When no hold stands the annotation is removed anyway. |
Deployment coordination
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
DeploymentLeaseRevoked | Warning | The group's deployment authority was revoked by a topology change (TopologyChanged) or an explicit operator request (OperatorRevoked). Stop the affected deployment clients; uncertain post-DDL state needs manual recovery. See deployment coordination. |
Dragonfly
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
DragonflyPromotionStarted | Normal | The operator is promoting a Dragonfly replica: in a planned failover's PromotingDragonfly phase, because the MySQL active site differs from the Dragonfly master, or because the Dragonfly master is unreachable and exactly one healthy replica can take over. |
DragonflyPromotionSucceeded | Normal | A Dragonfly target was promoted with REPLTAKEOVER and the active Service now points at it. A promotion that fell back to REPLICAOF NO ONE emits DragonflySessionsLost instead. |
DragonflyPromotionFailed | Warning | A Dragonfly target was not promoted: it was unreachable, REPLTAKEOVER failed or its reply was lost, it already reported master before the takeover, the Dragonfly master was neither the run's source nor its target, or PromotingDragonfly passed its deadline. The source's traffic label is restored; a planned failover then continues best-effort or rolls back, as onSyncTimeout says. Also emitted when the old master could not be demoted after a promotion: it is kept out of the active Service and needs manual intervention. |
DragonflyPromotionDeferred | Warning | A planned failover reached PromotingDragonfly while another Dragonfly promotion, usually an emergency one, was still running. MySQL writes stay paused on the fenced source until it finishes, at most about 40 seconds. Emitted once per wait; status.plannedFailover.dragonfly.message says the same. |
DragonflySyncTimedOut | Warning | A planned failover's target Dragonfly replica did not catch up within spec.dragonfly.plannedFailover.maxSyncWait. With onSyncTimeout: Proceed (the default) the takeover goes ahead and status.plannedFailover.dragonfly.sessions records Lost; with Fail the run rolls back before MySQL is promoted. |
DragonflySessionsLost | Warning | An emergency Dragonfly promotion succeeded without REPLTAKEOVER: it fell back to REPLICAOF NO ONE after the takeover failed or was skipped because the old master was unreachable, or its reply was lost and the target was then observed as master. The target is writable, but cached data and sessions may be lost. |
DragonflyStaleMasterDetected | Warning | A Dragonfly site other than the active one reports the master role. The operator reattaches it as a replica only if it never accepted writes (connected_slaves=0 and master_repl_offset=0); otherwise it stays out of the active Service until you decide whether its data can be discarded. Repeated at most every 5 minutes per site. |
DragonflyOldSiteReconfigured | Normal | A stale Dragonfly master on a non-active site that provably never accepted writes (connected_slaves=0 and master_repl_offset=0) was reattached as a replica of the active master. |
DragonflyReplTakeoverSupported | Normal | Dragonfly advertises REPLTAKEOVER again after a DragonflyReplTakeoverUnsupported Warning, typically after an image change. |
DragonflyReplTakeoverUnsupported | Warning | A Dragonfly site does not advertise REPLTAKEOVER. Emergency promotions fall back to REPLICAOF NO ONE, which does not preserve session continuity. Emitted when the capability probe changes to unsupported; use a Dragonfly image that supports the command. |
DragonflyUpgradeStarted | Normal | A snapshot-restore upgrade run started: spec.dragonfly.upgradeStrategy is SnapshotRestore and a site's live StatefulSet runs an image other than spec.dragonfly.image. status.dragonfly.upgrade records it; the message gives the source and target images. Dragonfly promotions and replication changes pause until the run ends. |
DragonflyUpgradeSnapshotStarted | Normal | The active Dragonfly site was removed from the active Service and the operator is saving a snapshot. Cache and session traffic is unavailable until the upgrade completes or fails. |
DragonflyUpgradeSnapshotCompleted | Normal | SAVE completed on the active Dragonfly master into spec.dragonfly.snapshot.dir. The active pod is restarted on the target image next and restores from the snapshot. |
DragonflyUpgradeSucceeded | Normal | Every Dragonfly site runs the target image and the active site serves traffic again with its replicas attached. |
DragonflyUpgradeFailed | Warning | A Dragonfly snapshot-restore upgrade failed, once per run; the message gives the reason. On DragonflySnapshotDialFailed, DragonflySnapshotSaveFailed, UnknownPhase, WorkloadSelectorMismatch, ActiveWorkloadDeleted, SpecChanged, DragonflyDisabled or TimedOut the operator restores the active site's traffic label, except that an active the run restarted and that is still loading its snapshot keeps it off until it answers as loaded (the message says so); on ActiveWorkloadDeleted it re-creates the active site's StatefulSet. On ActiveSiteChanged the upgrade was abandoned because MySQL failed over or a promotion moved the Dragonfly master. SpecChanged: spec.dragonfly changed during the run, and the operator decides again for the new spec. TimedOut: the run had not finished an hour after it started. The run is not restarted while spec.dragonfly is unchanged: change it to retry. When every site still runs one image, setting spec.dragonfly.image back to it and then forward again also retries; when the sites run different images, that starts a run back to the old image instead. |
DragonflyUpgradeDeferred | Normal | A snapshot-restore upgrade run is needed but waits: status.activeSite is empty, a site's StatefulSet is refused for its selector (WorkloadSelectorMismatch), a planned failover run or automation hold owns the sites, the Dragonfly master is not on the MySQL active site, or the sites do not run the other spec.dragonfly settings (snapshot flags, credentials, service account) yet. Emitted when the cause changes; the run is retried every 10 seconds and starts once the cause clears. No site's image moves meanwhile; the render rolls out the other settings, keeping each site's image. |
Encryption at rest
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
EncryptionAdoptionRefused | Warning | spec.encryptionAtRest.enabled was turned on for a group that is already serving, with spec.encryptionAtRest.existingData: Refuse (the default). Existing tablespaces would stay plaintext, so EncryptionAtRestReady is False with AdoptionRefused. enabled cannot be turned back off. Restore a backup into a new encrypted group and move the applications to it, or set existingData: LeavePlaintext to accept partial coverage. |
KeyringPhaseChanged | Normal | A site's keyring moved to another phase (Pending, Unsealed, Escrowed, Sealed or Failed); the message gives the old and new phase and the site's status message. A move to Failed comes with its own Warning, such as KeyringEscrowTimedOut or KeyringEscrowMissing. |
KeyringUnsealed | Normal | A site was unsealed for a CLONE INSTANCE: its pod rolls onto a writable keyring so the clone can rewrap the donor's tablespace keys. The site is escrowed and sealed again after the clone. |
KeyringEscrowTimedOut | Warning | A site's keyring was not escrowed within keyring.escrowTimeout (default 10m) of being unsealed, and the site goes Failed. The site stays unsealed and the operator keeps retrying; the message carries the last reason. See Keyring not sealed. |
KeyringEscrowMissing | Warning | A sealed site's escrow Secret, its only copy of its keys, no longer exists, so the site cannot restart. The site goes Failed. Do not restart or delete the pod: recreate the Secret from it following Keyring escrow lost. |
KeyringEscrowCorrupt | Warning | A sealed site's escrow Secret, its only copy of its keys, no longer matches the recorded digest. The site goes Failed. Do not restart the pod; follow Keyring escrow lost. |
KeyringDigestMismatch | Warning | A site's pod runs a keyring that does not match its escrow Secret, either when it was about to be declared Sealed or later, when the pod may be projecting a superseded version. The site goes Failed; tablespaces rewrapped under a newer master key will not decrypt. Check which escrow version the site's Deployment projects. |
KeyringNotReadOnly | Warning | MySQL reports a writable keyring component on a site the operator considers Sealed, so the site is not protected as reported. The site goes Failed; check the site's keyring rendering. |
KeyringRecovered | Normal | A Failed site returned to Sealed: its escrow Secret is present and matches the recorded digest, and MySQL confirms it runs that keyring. |
KeyringRotationRejected | Warning | A rotate-keyring request was refused. For the active primary the annotation is removed: rotate the replicas, run a planned failover, then rotate the former primary. Any other refusal (an ordered update, planned failover or promotion in flight, an unsettled topology, no running topology manager) keeps the annotation and retries every 10 seconds, with one Event per reason. |
KeyringPromotionSkipped | Warning | Emergency failover skipped a site whose UnsealReason is Rotation and promoted another candidate. |
KeyringPromotionRefused | Warning | Emergency failover refused: every remaining candidate is mid-keyring-rotation. The group stays without a writable primary until the site is Sealed. |
Restore
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
RestoreStarted | Normal | Restore Job created. |
RestoreSucceeded | Normal | Restore completed (with or without PITR). Not emitted for a partial outcome. |
RestoreFailed | Warning | Restore Job failed. Emitted once per Job; PITR failures carry a stable reason in status.restore.reason. For initFromBackup the message also states the recovery: the group stays out of service (no DNS record, no Service endpoints) and nothing is cloned. If the load had started, empty the target: delete and recreate the group, or delete the group's MySQL pods and PVCs together, wait until the operator has re-created every claim Bound and every pod Running, then delete the restore Job. Ready=False with reason InitFromBackupFailed says the same. |
RestoreTargetUnavailable | Warning | The active site an initFromBackup restore would load into is unreachable or in an unknown state; the restore waits in Pending. |
RestoreBuildFailed | Warning | Failed to build restore Job spec. PITR render refusals (PITRArchiveDestinationShared when pointInTime.archiveProfileName resolves to the group's own active PITR destination, PITRTargetInvalid for an unparsable or fractional target, a missing or unknown archiveProfileName, or an excludeGtids entry without a server UUID or longer than 64 KiB canonical, PITRBackupIdentityUnknown for a backup with no recorded source identity (no stamp) or a direct S3/PVC source at the root of its store, PITRBackupFiltered for a filtered load) also set status.restore.reason. PITRBackupAfterTarget is not raised here: the restore Job checks the target against the dump itself. A cause inside spec.initFromBackup, which is fixed at creation, names the recreate remedy (delete the group and create it again with the corrected block); a restoreInPlace cause names the edit. |
Point-in-time replay results
Recorded on MysqlFailoverGroup and MysqlBackupVerification.
| Reason | Type | Description |
|---|---|---|
PITRReplayTruncated | Warning | allowPartial replayed to the archive head because nothing is archived at or after the requested target (pointInTime.outcome: Partial: status.restore.provenance.pointInTime on a group, status.pointInTime on a verification). Names the requested target and the newest replayed commit time. |
PITRDivergentHistoryExcluded | Warning | The replay excluded N archived transactions committed before the target that are outside the target history (pointInTime.excludedDivergentTransactionCount). |
PITROnHistoryTransactionsExcluded | Warning | An excluded transaction on the target history precedes a replayed one: "excluded <set> precedes replayed transactions on the target history; their GTIDs were skipped but later row images may carry their effects; the result is not certified for them". |
In-place restore
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
RestoreInPlaceStarted | Normal | A new in-place restore was accepted and entered Preflight; the message gives the scope and confirm. |
RestoreInPlaceJobCreated | Normal | The in-place restore Job was created against the active site; the run is Restoring. |
RestoreInPlaceLoaded | Normal | The restore Job succeeded and the load is complete; the operator lifts the fence (Resuming) and then marks the run Succeeded. |
RestoreInPlaceSucceeded | Normal | The in-place restore finished Succeeded, and status.restoreInPlace.confirmTokenUsed records the confirm it ran with. After a full-instance restore the peers are recloned from the restored primary next. |
RestoreInPlaceFailed | Warning | The run's restore Job failed; the run ends Failed with the Job's failure in the message. The confirm the Job ran with is used (confirmTokenUsed), so bump confirm to retry. The fence and topology freeze are released; for the peers of a full-instance restore see RestoreReclonesHeld or RestoreReclonesDiscarded. |
RestoreInPlaceRejected | Warning | The run ends Failed with nothing restored because the request was refused: the spec is invalid (for example more than one loadOptions.includeSchemas entry), the operator account lacks privileges a full-instance restore needs (the message names them; re-checked about once a minute), or the scope changed before the Job was created. The confirm is not consumed, so a fixed spec runs with the same confirm. |
RestoreInPlaceBuildFailed | Warning | The restore credentials Secret or the restore Job spec could not be built (for example a backupRef whose profile is gone or moved to another backend). The run ends Failed with nothing restored; the confirm counts as used, so bump confirm to retry. |
RestoreInPlaceCanceled | Warning | spec.restoreInPlace was removed before the restore Job was created; the run ends Failed and nothing was restored. |
RestoreInPlaceSpecRemoved | Warning | spec.restoreInPlace was removed while its restore Job runs; the fence and guards hold until the Job finishes. |
RestoreInPlaceJobLost | Warning | The run's restore Job disappeared before its outcome was seen; the run ends Failed with the outcome unknown. |
RestoreInPlaceJobBusy | Warning | A Job with the in-place restore name from an earlier request is still running, so this run waits for it to finish instead of deleting a destructive restore in flight. Repeated until it finishes. |
RestoreInPlaceStaleJobRemoved | Normal | A finished Job with the in-place restore name, left by an earlier request, was deleted so this run can create its own. |
RestoreInPlaceConfirmRebound | Normal | spec.restoreInPlace.confirm advanced before the restore Job was created; the Job runs under the new confirm, recorded in status.restoreInPlace.runConfirm. |
RestoreInPlaceConfirmDeferred | Warning | spec.restoreInPlace.confirm changed while the run's restore Job is running; the Job is never interrupted. A newer confirm starts its own run once this one is terminal, and one that does not advance past it starts nothing. Repeated while the Job runs. |
RestoreInPlaceConfirmIgnored | Warning | After an in-place restore ended, spec.restoreInPlace.confirm was changed to a value that is not an RFC 3339 timestamp, so no new run starts and the previous terminal status stays. Set confirm to a valid timestamp newer than status.restoreInPlace.confirmTokenUsed. Admission refuses such a value, so only an object stored before that rule reaches it. |
RestoreInPlaceCredsRefreshFailed | Warning | The restore credentials Secret could not be refreshed while the run's restore Job exists. The Job is left running; the refresh is retried on the next pass. |
RestoreInPlaceDeferredPreviousQueue | Normal | A new in-place restore (bumped confirm) has not started because the previous restore's reclone queue is not settled yet: its Job's pods may still be running, or the primary is briefly unreadable (at most ten minutes). status.restoreInPlace still shows the previous run. |
RestoreOfflineFenceApplied | Normal | A full-instance in-place restore set offline_mode=ON on the primary and verified that no client session from before the fence remains; the restore Job is created next. |
RestoreOfflineFenceSessionsKept | Warning | The operator account lacks SYSTEM_USER, so it could not kill the named SYSTEM_USER accounts' sessions when the fence went up. They stay connected during the restore. |
RestoreOfflineFenceReleased | Normal | The restore finished and offline_mode on the primary is back to its prior value. |
RestoreOfflineFenceReleaseFailed | Warning | The operator could not put offline_mode back after the restore; it retries. Applications without CONNECTION_ADMIN cannot connect until it succeeds. |
RestorePeerReplicationStopped | Normal | A full-instance restore stopped the replication receiver of every queued peer in Fencing, before creating its Job. |
RestorePeerReplicationStoppedBehind | Warning | A peer's receiver was stopped before it received every transaction the primary committed before the fence (it was connecting, or did not catch up within 30 s). The message names the missing GTIDs; the peer keeps its pre-restore data without them. |
RestoreInPlacePeerStopFailed | Warning | The receivers of the peers could not be verified stopped within two minutes (unreachable peer, refused STOP). The run ends Failed without a Job, nothing was restored, the confirm is consumed, and the peers resume. |
Peer reclones after an in-place restore
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
RestoreRecloneRequested | Normal | The in-place restore's reclone queue requested the reclone of its next peer. |
RestoreRecloneWithdrawn | Normal | A newer full-instance restore was accepted; the old queue's reclone request that had not started was withdrawn. |
RestoreRecloneSucceeded | Normal | A queued peer was recloned from the restored primary and left the queue. |
RestoreRecloneFailed | Warning | A queued peer's reclone failed, or did not start within ten minutes. The site stays queued; the other queued sites go first and it is retried after a minute. |
RestoreReclonesDrained | Normal | Every peer of the in-place restore has been recloned; status.restoreReclones is cleared. |
RestoreReclonesDiscarded | Warning | The in-place restore that armed the queue did not succeed and left the primary unchanged: it never created its Job, or every pod of its Job terminated and the primary's gtid_executed still equals status.restoreReclones.fencedGtidExecuted. The peers resume replicating and are eligible for promotion again. |
RestoreReclonesRestored | Normal | A restore that had replaced an earlier restore's reclone queue ended without changing the primary. The earlier queue drains again. |
RestoreReclonesHeld | Warning | A full-instance restore ran its Job on the primary, failed, and the primary changed (or could not be compared). Every peer, plus any site of a replaced earlier queue, is held: replication receiver stopped, pre-restore data kept, excluded from promotion and the reader Services, nothing cloned. An administrator restores the primary (for example from a backup of a held site), sets status.restoreReclones.phase to Draining, or removes the queue (which resumes their replication from the current primary). |
RestoreReclonesAbandoned | Warning | A queued site became the primary (a manual promotion; a planned failover refuses a queued target) before it was recloned. It keeps its pre-restore data and the rest of the queue is dropped. |
PITR archiving
Recorded on MysqlFailoverGroup.
The archive identity reasons are refusals, not transient errors: they do not clear by themselves. They are listed in Archive identity.
A PITRArchiveNotReady โ PITRArchiveRecovered pair is expected, not an incident, when:
- A pod takes more than 60 s to come back during a rollout or node move (
SidecarUnreachable), for example while an RWO volume detaches and reattaches. - A new group's pods, or a bootstrapped site, take more than 5 minutes to start answering (
SidecarUnreachable). - PITR is enabled with
updateStrategy.type: Recreate, or a pod does not roll onto the PITR configuration through the ordered update (ArchiverDisabled). With the default ordered update, sites waiting for it reportUnknown/AwaitingRolloutwith no Event, for up to 30 minutes; a rollout still pending after that isArchiverDisabled, as is a PITR spec whoseprofileNamerenders no archiver. - MySQL takes more than about a minute to accept connections after a pod start, for example during a long InnoDB crash recovery (
ArchiverError,role check: ...).
Two failed archiver scans in a row (about 2 minutes at the default 60 s scan interval) also produce a pair if the third scan succeeds. Alert on a PITRArchiveNotReady without a following PITRArchiveRecovered within, say, 10 minutes, or on the condition itself.
| Reason | Type | Description |
|---|---|---|
PITRArchiveNotReady | Warning | The PITRArchiveReady condition entered False (from absent, Unknown or True). The message reads "PITRArchiveReady is False (ArchiveStoreInitFailed, an archive identity refusal such as ArchiveLegacy, ArchiveForeign, ArchiveCopy, ArchiveDescriptorMissing, ArchiveEncryptionMismatch, ArchiveKeyMismatch, ArchiveVersionUnsupported, ArchiveStoreUnsupported or ArchiveUnsafeHistory, then ArchiverError, SidecarOutdated, ArchiverDisabled, SidecarUnreachable) and every affected site. Not repeated while the condition stays False, even if the reason changes, and not re-fired by an operator restart. |
PITRArchiveRecovered | Normal | The PITRArchiveReady condition returned from False to True after 2 clean poll rounds: "PITRArchiveReady is True again: every site reports a running archiver with a ready store and no error". |
PITRArchiveUnsafeHistoryDetected | Warning | Edge-triggered on entering PITRArchiveReady=False / ArchiveUnsafeHistory: a site persisted its history-integrity marker after an out-of-band change (SET GLOBAL, a hand-edited my.cnf): such options in spec.mysqlConf are refused instead (PITRMysqlConfRefused). Fires in addition to PITRArchiveNotReady. The marker removes every restore target that needs that site's bytes, including targets before the violation. Remedy: undo the change, new prefix plus full backup (Unsafe history marker). |
PITRArchiveReissueRiskDetected | Warning | The active site changed to a site whose manifest holds divergent or quarantined rows. If it was recloned after a lossy failover it reissues the lost GTIDs; start a new archive prefix and take a full backup (Reissue risk after a reclone). |
PITRMysqlConfRefused | Warning | While PITR is enabled, group or site spec.mysqlConf sets an option that breaks PITR replay (a replication or binlog filter, binlog_formatโ ROW, binlog_row_imageโ FULL). The option is left out of the rendered my.cnf and PITRMysqlConfAccepted is False / PITRUnsafeSettingRefused; fires once per refused set. Remove the option from spec.mysqlConf. |
PITR archive encryption bindings
Recorded on MysqlFailoverGroup.
Events for the group's archive encryption bindings (condition PITRArchiveBindingReady).
| Reason | Type | Description |
|---|---|---|
PITRArchiveBound | Normal | A binding became Bound by sidecar proof or a check Job. Not emitted when status-loss recovery rebuilds a binding; watch for the pitr archive binding changed log line with cause=recovery instead (log schema). |
PITRArchiveBindingReleased | Normal | A binding was released and its key snapshots deleted. |
PITRArchiveEncryptionChangeRefused | Warning | A profile in archive use has encryption that differs from its Bound binding. Pods are unchanged. Edge-triggered. |
PITRArchiveKeyChangeRefused | Warning | A profile in archive use has passphrase bytes that differ from its Bound binding's key snapshot (rotation, alias, rename while PITR was off). Pods are unchanged. Edge-triggered. |
PITRArchiveBindingRefused | Warning | A binding was refused (check Job or correlated sidecar refusal), a write target is not eligible (ArchiveForeign/ArchiveCopy), or site Deployments without a decodable render annotation are deferred until a check verifies (the message names only those: "Deployments <names> are not updated until the archive accepts a key: ..."). Check refusals name the role: "... reported by the writer check of ..." or "... reported by the reader check of ...". |
PITRArchiveKeySourceRemoved | Warning | The profile's Secret or key is gone; the snapshot still serves. |
PITRArchiveKeySnapshotRecreated | Warning | A deleted key snapshot was re-created under the same name from matching live bytes; no roll. Not emitted when another object of that name appeared first. |
PITRArchiveKeyMaterialLost | Warning | A Bound binding's key snapshot is missing and cannot be reproduced ("Secret <s> held the accepted key of archive <id>: restore it from a backup of it, ..."), or is present but no longer passes the check of the accepted key ("... do not delete it, it may hold the only copy of that key; ..."). MySQL keeps running; the archiver fails closed. |
PITRArchiveKeySnapshotReadFailed | Warning | A key snapshot's acceptance annotation is malformed; it is never used for recovery and never deleted. |
PITRArchiveBindingReleaseRefused | Warning | A release annotation names a binding still in use; retried every minute. |
PITRArchiveBindingReleaseRejected | Warning | A release annotation is malformed or its confirm is not the group name; the annotation is removed. |
PITRArchiveBindingLimitReached | Warning | A destination in use gets no binding because 16 entries that stay (Bound, or used by a target) exist, or status-loss recovery found more accepted snapshots than free slots. |
Backup and verification scheduling
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
BackupScheduleInvalid | Warning | Schedule references an unknown backup profile. Admission refuses such a schedule, so only an object stored before that rule reaches it. |
BackupScheduleRefused | Warning | The schedule's profile is read-only (an unfinished initFromBackup archive profile, or a destination whose binding is foreign or refused ArchiveForeign/ArchiveCopy, or the archive profile of a succeeded initFromBackup restore whose destination has no binding): no CronJob is rendered. Once per change. |
BackupRetryScheduled | Normal | Retry scheduled for a failed backup. |
BackupRetryConflict | Warning | Existing deterministic retry name has different lineage; emitted when the schedule's retryConflict status marker changes. Only that retry is skipped while other schedules and rollups continue. |
BackupEncryptionInvalid | Warning | An encrypted backup profile's passphrase Secret is missing, lacks the configured key (default passphrase), holds an empty value, or holds a passphrase shorter than 32 bytes; the message names the profile and the problem. While the Secret or key is missing, backups of the profile wait Pending with PassphraseSecretMissing. A short passphrase only warns: backups keep using it, so replace it with a long random value. |
BackupPITRInvalid | Warning | spec.backup.pitr.enabled is true but spec.backup.pitr.profileName is empty or names no entry in spec.backup.profiles. Binlog archiving stays disabled and PITRArchiveReady reports ArchiverDisabled; set profileName to an existing profile. Admission refuses both, so only an object stored before that rule reaches it. |
VerificationScheduleInvalid | Warning | A backup profile sets verification.enabled: true but leaves verification.schedule empty, so no verification CronJob is created for it. Set a schedule or disable verification. Admission refuses it, so only an object stored before that rule reaches it. |
Credentials and secrets
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
CredentialReconcileFailed | Warning | Failed to reconcile MySQL users. |
SecretNotFound | Warning | Referenced Secret not found. |
SecretMissingKey | Warning | A credential Secret is missing a required key, has an empty username or password, sets only one of MYSQL_REPLICATION_USER and MYSQL_REPLICATION_PASSWORD, or names the operator account in MYSQL_REPLICATION_USER with a different MYSQL_REPLICATION_PASSWORD; the message names the Secret and key. |
S3CredentialsIncomplete | Warning | A credentialsSecretName the group references for S3 storage is missing, or lacks a non-empty AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY; the message names each Secret and key. S3CredentialsReady is False, and backup, restore and cleanup Jobs fail because they do not fall back to ambient cloud credentials. Add both keys to the Secret. The initFromBackup.source.s3 Secret is checked only until the bootstrap restore succeeds; after that it can be deleted. |
Operator lifecycle and license
Recorded on MysqlFailoverGroup.
| Reason | Type | Description |
|---|---|---|
GracefulShutdown | Normal | Graceful shutdown started or completed. |
LicenseVerified | Normal | A presented token newly verified offline. Also used when a valid token's update period has already ended (perpetual state). |
LicenseInvalid | Warning | A presented token failed offline verification. Operator behavior is unchanged. |
Backup lifecycle
Recorded on MysqlBackup.
| Reason | Type | Description |
|---|---|---|
BackupStarted | Normal | Backup Job created. |
BackupSucceeded | Normal | Backup completed successfully. |
BackupFailed | Warning | Backup Job failed, or it succeeded at a destination other than the recorded status.artifactStorage (Ready=False/DestinationMismatch). |
BackupMetadataIncomplete | Warning | Completion logs are unavailable, missing, or disagree with the Job. Check MetadataComplete; a recovered location remains usable, while DestinationUnavailable keeps Ready=False. |
EncryptionAttributionCorrected | Warning | Backup encryption status repaired from its producing Job's launch configuration. |
InFlightFailover | Warning | Active site changed while a backup was in progress. |
PITRArchiveStampSkipped | Normal | The backup was created with no PITR archive stamp (status.pitrArchiveID empty), so it is no point-in-time baseline: the site renders disagree or one is unreadable (a backup of a profile other than the active PITR profile, which does not wait for the roll), or the render is deferred (any site Deployment lacks a decodable render while the write binding is not Bound/Establishing: the pre-v1.7.0 deferral, which a backup of the active profile does not wait for). The message says why; when the renders disagree it also names Deployments of sites no longer in spec.sites ("... belong to sites no longer in spec.sites and are never re-rendered: scale them to zero or delete them"). |
RetentionDeferred | Normal | Retention kept a backup past its window because a verification is reading it. |
BackupJobConflict | Warning | A Job named mysqlbackup-<backup> exists but is not owned by this MysqlBackup (a Job orphaned by an earlier backup of the same name, or an older operator's cleanup Job for a backup named <backup> minus -cleanup). The backup waits in Pending with Ready=False/BackupJobConflict, never adopts that Job, and fails with PendingTimedOut after 30 minutes if it is still there. Emitted once on entering the wait. Repeats the condition reason. |
WaitingForHealthySource | Warning | No site qualifies as the backup's source (unreachable, not replicating, or lagging beyond maxLagSecondsForSource). The backup waits in Pending with Ready=False/WaitingForHealthySource and retries. Emitted once on entering the wait. Repeats the condition reason. |
NoHealthySource | Warning | Terminal: no healthy source appeared within 30 minutes, or a pinned spec.sourceSite cannot be used. The backup is Failed with Ready=False/NoHealthySource. Repeats the condition reason. |
PendingTimedOut | Warning | Terminal: the backup waited longer than 30 minutes for anything other than a healthy source. The backup is Failed with Ready=False/PendingTimedOut; the message reads pending for <duration> (<pending reason>): <message>. Repeats the condition reason. |
ProfileReadOnly | Warning | The backup's profile is read-only for this group (the initFromBackup archive profile before the restore succeeds or without a binding, or a destination whose binding names another group or was refused ArchiveForeign/ArchiveCopy). The backup is Failed with Ready=False/ProfileReadOnly and writes nothing. Repeats the condition reason. |
PITRArchiveRolloutPending | Warning | A backup of the active PITR profile waits while the site Deployments roll onto a new archive render, so it can become that archive's baseline. The backup waits in Pending with Ready=False/PITRArchiveRolloutPending and fails with PendingTimedOut after 30 minutes. Emitted once on entering the wait. Repeats the condition reason. |
DumpKeySnapshotConflict | Warning | The Secret the encrypted backup would use as its dump-key snapshot (mysqlbackup-<backup>-dump-key-<uid>) exists but is not labelled as this backup's snapshot. The backup waits in Pending with Ready=False/DumpKeySnapshotConflict and fails with PendingTimedOut after 30 minutes; delete or rename that Secret. Emitted once on entering the wait. Repeats the condition reason. |
PassphraseSecretMissing | Warning | The encrypted profile's encryption.passphraseSecretKeyRef Secret is missing, or its key is missing or empty, so no dump-key snapshot can be taken. The backup waits in Pending with Ready=False/PassphraseSecretMissing and fails with PendingTimedOut after 30 minutes. Emitted once on entering the wait. Repeats the condition reason. |
BackupStorageMismatch | Warning | Terminal: the profile's owned claim mysql-<group>-backup-<profile> has another StorageClass than pvc.provisioned.storageClassName, or requests less than pvc.provisioned.size (the claim was created for an earlier profile of the same name). The backup is Failed with Ready=False/BackupStorageMismatch and never writes to that claim; delete the claim or restore the profile's values. Repeats the condition reason. |
BackupStorageExpansionAccepted | Normal | The profile's owned claim requests more than pvc.provisioned.size, for example after it was expanded out of band. The backup uses it. Emitted when a backup's Job is created. |
Backup and verification setup
Recorded on MysqlBackup and MysqlBackupVerification.
| Reason | Type | Description |
|---|---|---|
GroupNotFound | Warning | The referenced MysqlFailoverGroup does not exist. The MysqlBackup is Failed with Ready=False/GroupNotFound, or the MysqlBackupVerification is Failed with Verified=False/GroupNotFound. Repeats the condition reason. |
ProfileNotFound | Warning | The named backup profile is not in the group's spec.backup.profiles. A MysqlBackup without a Job yet is Failed with Ready=False/ProfileNotFound; a MysqlBackupVerification is Failed with Verified=False/ProfileNotFound. Repeats the condition reason. |
BuildJobFailed | Warning | The operator could not build the Job spec. On a MysqlBackup the backup is Failed with Ready=False/BuildJobFailed, also when the backup Job is gone mid-run and a replacement built from the current profile would not write to the destination recorded at launch. On a MysqlBackupVerification the run is Failed with Verified=False/BuildJobFailed; a PITR refusal uses its refusal token as the reason instead. Repeats the condition reason. |
Artifact cleanup
Recorded on MysqlBackup.
| Reason | Type | Description |
|---|---|---|
ArtifactCleanupStarted | Normal | Cleanup Job created for backup artifact. |
ArtifactCleanupSucceeded | Normal | Artifact removed; the message gives the object count. |
ArtifactCleanupNothingFound | Warning | Cleanup found nothing at the recorded destination; the finalizer is released. |
ArtifactCleanupFailed | Warning | A cleanup Job failed, once per Job. The ArtifactCleanup condition names the cause; a new Job runs after 5 minutes (transient) or 30 minutes (refusal), and the finalizer blocks deletion until one succeeds. Counted by bloodraven_backup_artifact_cleanup_failures_total. |
ArtifactCleanupRetry | Normal | A failed cleanup Job is being replaced. |
ArtifactCleanupDeferred | Normal | A MysqlBackupVerification is still reading the artifact (ArtifactCleanup=False/HeldByVerification); cleanup waits until its pods stop. |
ArtifactCleanupStalledProducerStop | Warning | Deleting an abandoned backup (never succeeded, or its group or profile is gone) waits for its producing backup Job's pods to stop, and one has not stopped after 10 minutes (ArtifactCleanup=False/ProducerStopStuck). The deletion stays held; once the pod's node is confirmed down, force-delete the pod (kubectl delete pod --force --grace-period=0). |
ArtifactCleanupBlocked | Warning | Cleanup cannot start (DestinationMismatch, CredentialsUnavailable, OperatorImageUnset, CleanupJobConflict, CleanupJobInvalid); the message carries the reason. |
ArtifactCleanupSkipped | Warning | Referenced failover group or profile is gone, or no artifact destination can be recovered; or the best-effort cleanup of a backup that never succeeded (its stamp and any partial dump) could not run or failed, or found no stamp but objects at the location (BLOODRAVEN_CLEANUP_SKIPPED; nothing is deleted). Cleanup is skipped and the finalizer is released; the message names what is left (for the skipped verdict, the location, the stamp key and the kept dump-key snapshot). An encrypted backup that recorded neither its producing Job nor its artifact destination, or whose abandoned cleanup was skipped, keeps its dump-key snapshot (โฆ is kept because encrypted artifacts may exist; delete it once you have confirmed none remain). |
ArtifactRetained | Normal | The backup was deleted with spec.deletionPolicy: Retain: no cleanup ran, and the artifact and its dump-key snapshot Secret (an encrypted backup's key) are left in place; the message names both. The finalizer is released at once, or for a backup that never succeeded, once its producing Job's pods have stopped. Delete them by hand once they are no longer needed. |
Backup verification
Recorded on MysqlBackupVerification.
| Reason | Type | Description |
|---|---|---|
VerificationStarted | Normal | The verification Job was created and the run moved to Restoring; the message names the Job, backup and profile. |
VerificationSucceeded | Normal | The verification run reached Succeeded. |
VerificationFailed | Warning | The verification run reached Failed. A PITR failure's Verified condition reason is the refusal token, also when building the Job was refused (for example PITRTargetInvalid for a verification against a group with spec.backup.pitr disabled), not BuildJobFailed. |
BackupIdentityMismatch | Warning | A verification's recorded source cannot be reconciled with its Job inputs. The run fails without certifying a different backup. Repeats the condition reason. |
BackupNotAvailable | Warning | The verification has no usable backup: the backupRef backup is missing, not Succeeded, being deleted or for another group or profile, no Succeeded backup exists for the group and profile, or the pinned backup was deleted, replaced by a same-name backup or lost this verification's hold. The run is Failed with Verified=False/BackupNotAvailable; create a new verification. Repeats the condition reason. |
BlockedByActiveVerification | Warning | An older verification for the same group and profile has not finished, so this one is refused rather than run concurrently. The run is Failed with Verified=False/BlockedByActiveVerification. Repeats the condition reason. |
PITR verification refusals
Recorded on MysqlBackupVerification.
| Reason | Type | Description |
|---|---|---|
PITRArchiveBindingNotReady, PITRArchiveCopy, PITRArchiveDescriptorMissing, PITRArchiveDestinationShared, PITRArchiveDivergentHistory, PITRArchiveEncryptionMismatch, PITRArchiveForeign, PITRArchiveGap, PITRArchiveHistoryReset, PITRArchiveIdentityMismatch, PITRArchiveKeyMismatch, PITRArchiveLegacy, PITRArchiveListFailed, PITRArchiveNamespaceReuse, PITRArchiveNotFound, PITRArchiveObjectInvalid, PITRArchiveOrderConflict, PITRArchiveOrderInversion, PITRArchiveSplitBrain, PITRArchiveStoreUnsupported, PITRArchiveUnsafeHistory, PITRArchiveVersionUnsupported, PITRBackupAfterTarget, PITRBackupDumpKeyMissing, PITRBackupFiltered, PITRBackupIdentityMismatch, PITRBackupIdentityUnknown, PITRBackupIncomplete, PITRBackupInconsistent, PITRBackupNotOnLineage, PITRCoverageInvalid, PITRExcludeIntersectsBackup, PITRNoArchivedReplay, PITRPlanTooLarge, PITRReplayFailed, PITRReplayMismatch, PITRReplayUnsupported, PITRStopAmbiguous, PITRTargetInvalid, PITRTargetNotCovered | Warning | A point-in-time verification was refused: the Event repeats the Verified=False reason, the refusal token. The tokens are listed in Restore refusals; PITRReplayFailed is the replay script's own failure. Each repeats the condition reason. |
MysqlDatabase
Recorded on MysqlDatabase.
See Tenant databases for the lifecycle these Events report.
| Reason | Type | Description |
|---|---|---|
DatabaseDropped | Normal | Under deletionPolicy: Delete the operator dropped the database and the CR's accounts, only the database (owner left untouched), or only the accounts (the database is still declared by another CR); the message says which. The finalizer is released. |
DatabaseDropSkipped | Normal | Under deletionPolicy: Delete this CR never applied any DDL (status.databaseCreated is unset), so there is nothing of its own to drop. The finalizer is released and MySQL is untouched. |
DatabaseDropRefused | Warning | Under deletionPolicy: Delete another live MysqlDatabase on the group still declares the same databaseName, so the database is not dropped. The CR's own accounts are still vetted and dropped separately. |
DatabaseDropDeferred | Warning | Under deletionPolicy: Delete the DROP cannot run yet: the group has no active site, its primary is fenced by a restore or planned failover, a group credential Secret cannot be read, the primary is unreachable, a transient MySQL error hit, or the active site moved during the DROP. The finalizer stays and the drop is retried; set deletionPolicy: Retain to release the CR without dropping. |
DatabaseRetained | Normal | deletionPolicy: Retain: the finalizer is removed and the database, the owner and the users[] principals are left untouched in MySQL. |
DatabaseCleanupSkipped | Warning | Under deletionPolicy: Delete the MysqlFailoverGroup is gone (before the DROP, or it disappeared during it), so there is nothing to connect to. The finalizer is released; the database and accounts may remain in MySQL. |
OwnerUserRotated | Normal | The username in the owner Secret changed: the new owner user was created and granted, then the previous one was revoked and dropped. Also emitted when an abandoned rotation target is dropped before rotating to a newer username. |
OwnerHostsRemoved | Normal | Hosts were removed from spec.owner.hosts; the owner user was revoked and dropped on exactly those hosts. |
OwnerUserDropSkipped | Warning | An owner account was not dropped because something else still claims it: this CR's own spec.grants[], or another live MysqlDatabase (its owner Secret or user, its spec.grants[], or its users[] ledger). Emitted on deletion under deletionPolicy: Delete, and on an owner rotation or host removal, where the account still loses its rights on this database unless this CR's own spec.grants[] declares it. |
OwnerUserReservedSkipped | Warning | An owner account this CR recorded has since become a group-level principal, so it is never dropped. Emitted on deletion under deletionPolicy: Delete, and on an owner rotation or host removal, where its rights on this database are still revoked. |
UserRotated | Normal | The username in a spec.users[] Secret changed: the new principal was created and granted, then the previous one was revoked and dropped. Also emitted when an abandoned rotation target is dropped before rotating to a newer username. |
UserHostsRemoved | Normal | Hosts were removed from a spec.users[] entry's hosts; that principal was revoked and dropped on exactly those hosts. |
UserRemoved | Normal | A spec.users[] entry was removed; its principal was revoked and dropped on the hosts recorded for it. |
UserTransferred | Normal | A spec.users[] entry was removed, but its principal is now declared by another surface of this CR (another users[] entry, the owner or spec.grants[]), so the account is kept and no UserRemoved follows. |
UserDropSkipped | Warning | A users[] principal was not dropped because this CR's own spec.grants[] declares it or another live MysqlDatabase still claims it. Emitted on deletion under deletionPolicy: Delete, and on a rotation, entry removal or host removal, where the principal still loses its rights on this database unless this CR's own spec.grants[] declares it. |
UserReservedSkipped | Warning | A users[] principal this CR recorded has since become a group-level principal, so it is never dropped. Emitted on deletion under deletionPolicy: Delete, and on a rotation, entry removal or host removal, where its rights on this database are still revoked. |
PartialRevokeLifted | Normal | The operator removed a restriction on an impossible legacy escaped spelling of the database name from an account this CR recorded; such restrictions were left by older operator builds. |
TenantPrivilegeRestriction | Warning | The best-effort repair of legacy escaped-schema restrictions found something it will not or could not fix: the operator account is restricted on the database (repairs on that spelling are skipped for every account), a recorded tenant account has a restriction on the real schema that needs review by the privilege owner, or a repair step failed. Normal tenant reconciliation continues; see the privilege restriction recovery playbook. |
InvalidSpec | Warning | The spec failed validation at reconcile time (for example an identifier or a username from a Secret that does not match the allowed pattern, or a MySQL system schema as databaseName). The CR is Failed with Ready=False/InvalidSpec and no SQL runs. Repeats the condition reason. |
MySQLError | Warning | MySQL refused a statement with a non-transient error while applying the database, its accounts or grants, or while revoking or dropping a retired account. The CR is Failed with Ready=False/MySQLError and the message carries the error; retried every minute. Repeats the condition reason. |
DatabaseNameConflict | Warning | An older MysqlDatabase on the same group already declares this databaseName. This CR is Failed with Ready=False/DatabaseNameConflict before any SQL runs, and re-checked every minute. Repeats the condition reason. |
DatabasePreExists | Warning | The schema already exists and status.databaseCreated does not say this CR created it, so adoption is refused. The CR is Failed with Ready=False/DatabasePreExists; re-checked every minute. Repeats the condition reason. |
OwnerConflict | Warning | An older MysqlDatabase on the same group shares this CR's owner Secret or owner MySQL user. This CR is Failed with Ready=False/OwnerConflict before any SQL runs; give each MysqlDatabase a distinct owner. Repeats the condition reason. |
OwnerUserReserved | Warning | The owner Secret names a group-level principal (root, replicator, a MySQL system account or a spec.credentials user). The CR is Failed with Ready=False/OwnerUserReserved and no SQL runs; use a tenant-specific username. Repeats the condition reason. |
PreExistingOwnerUser | Warning | An owner account user@host already exists and no record of this CR names that user on that host, so adoption is refused. The CR is Failed with Ready=False/PreExistingOwnerUser; re-checked every minute. Repeats the condition reason. |
PreExistingUser | Warning | A spec.users[] account user@host already exists and no record of this CR names it on that host, so adoption is refused. The CR is Failed with Ready=False/PreExistingUser; re-checked every minute. Repeats the condition reason. |
UserClaimedBySibling | Warning | A spec.users[] username is already claimed by another MysqlDatabase on the group (as its owner, in its spec.grants[], or in its users[] ledger). The CR is Failed with Ready=False/UserClaimedBySibling before any SQL runs; share a group-level principal through spec.grants[] instead. Repeats the condition reason. |
UserReserved | Warning | A spec.users[] Secret names a group-level principal (root, replicator, a MySQL system account or a spec.credentials user). The CR is Failed with Ready=False/UserReserved and no SQL runs. Repeats the condition reason. |
GrantUserMissing | Warning | A spec.grants[] user does not exist at host %; the operator never creates it. The CR is Failed with Ready=False/GrantUserMissing; create the principal at the group level and the CR goes Ready on its own (re-checked every minute). Repeats the condition reason. |
PartialRevokesOff | Warning | The operator cannot confirm or enable partial_revokes=ON on the primary, so tenant changes are deferred. The CR is Pending with Ready=False/PartialRevokesOff and retries; existing accounts and grants are left intact. Repeats the condition reason. |
MysqlStandbyCluster
Recorded on MysqlStandbyCluster.
| Reason | Type | Description |
|---|---|---|
BucketScanned | Normal | A discovery scan changed status.discovered (the newest dump, the archive identity or its binlog window). The message gives the dump and the manifest and binlog counts. Rescans that find nothing new emit nothing. |
ManifestParseFailed | Warning | A binlog manifest under the source prefix could not be read or parsed. The source archive is refused: SourceConfigKnown=False with the archive reason (for example ArchiveLegacy, ArchiveVersionUnsupported or ArchiveUnreadable), and latestDump is published without binlogArchive. |
MetadataParseFailed | Warning | The newest dump's @.json metadata is unreadable or malformed. SourceConfigKnown is False with reason MetadataUnreadable; the bucket itself stays readable. |
PITRArchiveRefused | Warning | On a MysqlStandbyCluster: the source archive was refused (SourceConfigKnown=False with an archive reason); emitted when the reason changes. |
AuthFailed | Warning | The archive store for the source bucket could not be constructed (for example bad credentials or an unusable endpoint). BucketReadable and SourceConfigKnown are False with reason AuthFailed; discovery retries every discovery interval and the Event repeats on each failed attempt. Repeats the condition reason. |
ConfigError | Warning | The standby's storage backend could not be resolved: an invalid spec.source.s3 bucket, prefix or endpoint (a prefix must name more than the bucket root), an unreadable credentialsSecretName Secret, or a spec.source.decryption.passphraseSecretKeyRef Secret or key that is missing or empty. BucketReadable and SourceConfigKnown are False with reason ConfigError; retried every discovery interval. Repeats the condition reason. |
ListFailed | Warning | Listing the source bucket prefix failed. BucketReadable is False with reason ListFailed and status.discovered keeps the last successful scan; retried every discovery interval. Repeats the condition reason. |
Forwarding events with Kubewatch
Kubewatch can watch for Kubernetes Events and forward them to Slack, PagerDuty, webhooks, and more. Example configuration to forward all Bloodraven events to Slack:
apiVersion: v1
kind: ConfigMap
metadata:
name: kubewatch
data:
.kubewatch.yaml: |
handler:
slack:
channel: "#mysql-alerts"
resource:
event: true
namespaces:
- bloodraven
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubewatch
spec:
template:
spec:
containers:
- name: kubewatch
env:
- name: KW_SLACK_TOKEN
valueFrom:
secretKeyRef:
name: kubewatch-secrets
key: slack-token
KW_SLACK_TOKEN environment variable is read by Kubewatch at startup.Argo Events and Event Router are alternatives that offer richer filtering and routing capabilities.
Status API
The operator serves a JSON status API on :8082.
GET /status
Returns the current state of all failover groups:
curl http://localhost:8082/status
{
"default/orders": {
"activeSite": "iad",
"sites": [
{
"name": "iad",
"role": "PrimaryCandidate",
"state": "Writable"
},
{
"name": "pdx",
"role": "PrimaryCandidate",
"state": "ReadOnly"
}
],
"pollTime": "2025-01-01T00:00:00Z"
}
}
GET /active-site
Returns the active (writable) site for a specific failover group. Used by the sidecar's startup safety net.
curl "http://localhost:8082/active-site?namespace=default&group=orders"
{
"namespace": "default",
"group": "orders",
"activeSite": "iad"
}
| Status | Meaning |
|---|---|
200 | Failover group found. activeSite may be "" if no single writable site exists (first boot, split-brain). |
400 | Missing namespace or group query parameter. |
404 | Failover group not found on this operator instance. |
503 | Operator has no active topology managers (startup race or non-leader replica). |
GET /ws/status
WebSocket endpoint that streams the full topology of each failover group in
real time. A message is sent at the end of every operator poll cycle, not only
on state transitions, so dashboards can render live counters and health
indicators. The cadence is every 2 seconds per group (pollInterval), including
during a site outage: probe backoff is per site, so a site that stays down is
re-probed less often, but the poll loop and this stream keep the base cadence.
Each message is a JSON object with camelCase keys:
{
"namespace": "default",
"group": "orders",
"activeSite": "iad",
"sites": [
{
"name": "iad",
"role": "PrimaryCandidate",
"state": "Writable",
"lastSeenTime": "2026-04-10T00:00:00Z",
"replication": "NotApplicable"
},
{
"name": "pdx",
"role": "PrimaryCandidate",
"state": "ReadOnly",
"lastSeenTime": "2026-04-10T00:00:00Z",
"replication": "Healthy",
"secondsBehindSource": 0,
"gtidExecuted": "3e11fa47-71ca-11e1-9e33-c80aa9429562:1-45839"
}
],
"lastFailover": {
"time": "2026-04-09T22:10:00Z",
"target": "iad",
"promotionGtidExecuted": "3e11fa47-71ca-11e1-9e33-c80aa9429562:1-45839"
},
"pollTime": "2026-04-10T00:00:00Z"
}
The top level carries namespace, group, activeSite, sites,
lastFailover, alert, updatePhase and pollTime. lastFailover has the
shape of the CR's status.lastFailover (time in RFC 3339, target,
promotionGtidExecuted) and is omitted when the group has no failover record.
Each site entry carries name, role, state, lastSeenTime,
replication, secondsBehindSource, gtidExecuted, recoveryState,
divergentGtid and divergentTransactionCount.
sites[].replication is the CR's status.sites[].replication value
(Healthy, Lagging, Broken, Unknown or NotApplicable), debounced the
same way: Healthy or Lagging only after both replication threads have run
with a source set for consecutive polls, and NotApplicable on the writable
primary. It is not a per-poll reading of the replication threads. The former
boolean replicating field (the raw per-poll IO and SQL thread running
flag) and the top-level lastFailoverTarget and promotionGtidExecuted
fields are gone; lastSeen is now lastSeenTime.
When a site holds transactions the primary lacks (an old primary after an emergency failover, or a replica found by the replica GTID audit), the site entry includes recovery fields:
{
"name": "pdx",
"role": "PrimaryCandidate",
"state": "ReadOnly",
"lastSeenTime": "2026-04-10T00:00:00Z",
"replication": "Broken",
"recoveryState": "RecoveryBlocked",
"divergentGtid": "a1b2c3d4-0000-0000-0000-000000000000:11-15",
"divergentTransactionCount": 5
}
The stream does not say which check found the divergence. GET /status and
the CR's status.sites[] carry divergenceDetectedBy (OldPrimaryRecovery
or ReplicaAudit) and divergenceComparedTo; a replica-audit entry usually
has replication: Healthy.
const ws = new WebSocket("ws://localhost:8082/ws/status");
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(`${msg.group}: active=${msg.activeSite}`);
};
Both the REST and WebSocket endpoints use camelCase JSON keys, so the same field names work for either consumer.
Troubleshooting
Common conditions
| Symptom | Likely cause | Investigation |
|---|---|---|
Ready=False (reason NoWritablePrimary or ReplicationUnhealthy), Degraded=True | A site is unreachable (SiteUnhealthy) or replication is broken | Check status.sites[].state and MySQL pod logs |
Degraded=True, reason BootstrapFailed; Bootstrapping=False, reason BootstrapFailed | A clone into a site failed and the site does not replicate yet. Deployment leases are refused while it holds | Read the condition message for the target and the error, fix the cause (often replication credentials or the donor), then clone again with kubectl bloodraven reclone <group> <site> --cold. It clears once the site replicates or a new clone starts |
Ready=Unknown, Degraded=Unknown, reason Initializing | The operator just started and has not classified every PrimaryCandidate site yet (a writable answer needs recoveryThreshold polls) | Normally clears on the next poll. It lasts at most failureThreshold + recoveryThreshold probes of a site; a site still unclassified then alternates between answers and errors, and the group reports NoPrimary naming it. Check status.sites[].state and the operator logs. See Failover: Initializing after an operator start |
Ready=False, reason InitFromBackupInProgress | A new group's spec.initFromBackup restore has not succeeded yet. The seed is writable and status.activeSite, but applications are kept off it: no DNS record, no -primary/-replicas/per-site Service endpoints, no clones | Watch status.restore. See Backup and restore: Applications wait for the restore |
Ready=False, reason InitFromBackupFailed | The initFromBackup restore failed; the seed may hold partial data. The group stays out of service and nothing is cloned | Follow the condition message: read the restore Job log; if the load had started, empty the target (delete and recreate the group, or delete the group's MySQL pods and PVCs together and wait until the re-created claims are Bound and the pods Running), then delete the Job. See Backup and restore: A failed bootstrap restore |
Ready=False, both sites ReadOnly | No primary exists (may follow a failed failover) | See Operations: Manual promotion |
Ready=False, both sites Writable | Split brain | See Operations: Split brain recovery |
RecoveryPending=True, reason RecoveryInProgress | Old primary is being reconfigured as a replica | Wait for the condition to clear. If it stays true, check operator logs and status.sites[].replication. See Failover: Old primary recovery. |
RecoveryPending=True, reason DivergentTransactions | Old primary returned with divergent transactions | Check status.sites[].divergentGtid, then trigger a reclone: kubectl bloodraven reclone <group> <site> (it fills in the required divergentGtid prefix). See Failover: Old primary recovery. |
Degraded=True, reason DivergentTransactions, Ready=True | A site holds transactions the primary lacks; a replica can be replicating normally. Ordered updates and planned failovers will not promote it | Check status.sites[].divergentGtid and divergenceDetectedBy, preserve the errant binlog events, then reclone the site or make the primary contain the set. See Failover: Divergent replicas. |
Updating=True, reason OrderedUpdateStalled | An ordered update rolled a site whose new pod does not become Ready and stay Ready for 30s (often a sidecar that cannot start or crash-loops), or a site already on the new template is not Ready by that rule. The update rolls nothing else and the active site keeps serving | Read the condition message for the site, pod and cause, then kubectl describe pod and the container logs (--previous for a crash loop). Fix the cause or the spec; the update retries every 30s. See Failover: Ordered updates. |
DivergentPromotion=True | A divergent site was promoted; its extra transactions are now authoritative | Review the set in the condition message, then remove the condition. See Failover: Failover sequence. |
| Repeated failovers | Flapping network or unstable MySQL | Check bloodraven_state_transitions_total and consider increasing failureThreshold or failoverCooldown |
| High replication lag | Slow network, heavy write load, or undersized replica | Check bloodraven_replication_lag_seconds and MySQL performance metrics |
Operator logs
The operator logs structured JSON. Key fields to filter on:
kubectl logs -n bloodraven deploy/bloodraven | jq 'select(.msg == "failover")'
kubectl logs -n bloodraven deploy/bloodraven | jq 'select(.level == "ERROR")'
Sidecar logs
Check the sidecar container for self-fencing events:
IAD_POD=$(kubectl get pod -n orders \
-l app.kubernetes.io/name=mysql,bloodraven.shipstream.io/failover-group=orders,bloodraven.shipstream.io/site=iad \
-o jsonpath='{.items[0].metadata.name}')
kubectl logs -n orders "$IAD_POD" -c sidecar | jq 'select(.msg | contains("self-fence"))'

