Losing a whole cluster, and the go-live gate

Fence the lost source on two independent signals, bootstrap elsewhere from the bucket, and then decide — on evidence — whether playground is fit to go live.

By the end of this topic you can

  1. Fence a lost source on two independent signals before bootstrapping a DR group
  2. Bootstrap a disaster-recovery group for playground from object storage and cut DNS over to it
  3. Walk a production hardening checklist and say which items you would block a launch on

playground has survived everything you have thrown at it inside one cluster: a killed primary promoted in 12.0 s, a split brain resolved by sitePriorities, five shapes of partition, the operator itself going away, a datadir wiped and restored from a bucket. Every one of those had an operator watching. Now the cluster hosting playground is gone — the API server does not answer, the nodes do not answer, and what you still have is an S3 prefix in another region. There is no failover group left to fail over.

Fence the source first, because that is where the danger is

Your instinct is to restore. Resist it for ten minutes. A restore into a DR cluster while the source is still accepting writes gives you two writable copies of playground, and Bloodraven v1 does not automatically detect or resolve cross-cluster split brain (objective 13). Inside one cluster the operator sees every site every 2 s and the decision matrix flags SPLIT BRAIN the instant more than one core site is writable. Across clusters, nothing holds both halves. No operator watches both. The sidecar fencing layer only knows the peers in its own group. There is no component that will catch this mistake for you.

So the fencing decision is the safety mechanism, and it is yours. The checklist demands at least two of three independent signals before you declare the source dead:

CompareThe three source-fencing signals — take any two
  1. Source operator /active-site returns 5xx
  1. Source cluster API server unreachable
  1. Source MySQL TCP-dead from a third vantage point
What it actually provesThe source operator cannot reach any writable site — or you cannot reach the source operator.You cannot administer the source cluster right now.A network location outside both clusters cannot open port 3306 against the source.
How it lies on its ownThe operator is not on the request path. It can be dead while MySQL serves writes perfectly.Control plane and data plane fail separately. Kubernetes will not even delete pods on an unreachable node — the containers keep running and keep writing to the PV.A one-way partition means your loss of reachability is not your application's loss of reachability.

Two signals is not bureaucracy. Each one alone is a known false positive. If a signal is ambiguous — the API server answers but slowly — you wait. Waiting ten minutes costs you ten minutes. Getting this wrong costs you what it cost GitHub in October 2018: a 43-second partition left East and West each holding writes the other had never seen, and reconciling them took over 24 hours. That was one company, one tooling stack, one partition. You would be doing it by hand, across two clusters, with no shared GTID history to reason from.

MysqlStandbyCluster is a dashboard, not a lifeboat

You will find a CRD called MysqlStandbyCluster and assume it is the DR mechanism. It is not. Today it is observability only. Its controller re-scans the source bucket on spec.freshness.discoveryInterval (default 5m) and publishes exactly two conditions:

ConditionTrue means
BucketReadableThe DR cluster listed the source prefix and could read it.
SourceConfigKnownThe dump metadata parsed — status.discovered now carries dump name, location, GTID set, and the archived binlog window.

That is the whole of it: no MySQL contact, no restore Jobs, no activation. A standby cluster sitting at BucketReadable=True has told you that a DR bootstrap would be possible and roughly how far back it could reach. It has not proven the dump restores, and it will not lift a finger when the source dies. Treat it as the pre-flight gauge it is.

The bootstrap is the restore path you already have

There is no DR-specific machinery to learn (objective 14). You create a new MysqlFailoverGroup in the DR cluster, shaped for the DR cluster’s own nodes, IPs and zones, and point spec.initFromBackup at the same bucket the dead cluster was writing to:

spec:
  sites:                                    # DR-cluster topology, not the source's
    - name: east-1
      role: primary-candidate
      lbIP: 10.1.20.11
    - name: east-2
      role: primary-candidate
      lbIP: 10.1.20.12
  dns:
    hostname: playground-east.example.com
    ttl: 60                                 # shipped default, not the playground's 10
  initFromBackup:                           # the same one-shot restore field from Unit 6
    source:
      s3:
        bucket: shipstream-backups
        prefix: playground/west/playground-nightly-20260520
        region: us-west-2
        credentialsSecret: s3-dr-readonly-creds
    decryption:
      passphraseSecret:
        name: playground-backup-passphrase      # mirrored into the DR namespace in advance
    pointInTime:
      stopDatetime: "2026-05-20T14:32:00Z"  # omit to recover to the dump's GTID

initFromBackup is one-shot and gates bootstrap: nothing else proceeds until status.restore.phase reads Succeeded. Then normal bootstrap — clone, replication, fencing — runs exactly as it did on day one of this course.

Try itkubectl bloodraven status playground on the DR cluster (excerpted)
kubectl bloodraven status playground --context=dr -n bloodraven-dr
MysqlFailoverGroup: bloodraven-dr/playground
  Active site: east-1
  Ready: True
  DNS: playground-east.example.com (TTL 60s)

Sites:
  NAME    ROLE               ZONE        STATE      REPL  LAG  RECOVERY  LAST-SEEN
  east-1  primary-candidate  us-east-1a  writable   no    -    -         2s
  east-2  primary-candidate  us-east-1b  read-only  yes   0s   -         2s

Initial restore (initFromBackup):
  Phase: Succeeded
  Target site: east-1
Recorded output. Run reveals what is already on the page — nothing executes, and no cluster is contacted.

Then DNS, by the same DNSEndpoint path from Unit 4: one object named bloodraven-playground, server-side-applied every poll, one A record at spec.dns.ttl. The catch is the same catch — the operator cannot accelerate DNS propagation, and here it is worse, because the operator only owns the per-cluster record. The global application-facing name is yours to flip, by weight, CNAME or GSLB. A perfect restore behind a stale CNAME is still an outage.

The day-2 surface you can hand to on-call

kubectl bloodraven has exactly seven subcommands: status, promote, reclone, backup, verify-backup, version, help. Nothing else. The design property that makes it safe to put in a runbook is stated in the plugin’s own header: it only writes resources the operator already reads — annotations on MysqlFailoverGroup, plus MysqlBackup and MysqlBackupVerification CRs — and it never talks to MySQL directly. There is no back door. kubectl bloodraven promote behaves identically to the annotation, obeys the cooldown, the reader refusal, the lag gate, every gate you already know. An on-call engineer cannot use the plugin to do something the operator would have refused.

The go-live gate

Now commit to a verdict on each of these. Not “noted” — block or accept-with-a-named-owner (objective 15).

ItemWhy it is not what you assumedMy verdict
sync_binlog=1An overridable default, written into the base my.cnf before your spec.mysqlConf. An override wins silently.Block until you have read it off the running instance.
Backups on a PVC onlyPVC-local backups are not durable; the failure that takes the PVC takes them.Block.
No backup ever verifiedAn unverified backup is an assumption. Schrödinger backups.Block.
The application-side write gapNo shipped alert covers it. Unowned, it is invisible until an incident.Block until somebody owns it by name.
replication.maxLagSeconds (whatever you set)Drives only the ReplicationLagging condition. It is not a promotion gate — a replica beyond the threshold is still promoted. playground sets 30; a group that omits the field gets 300. Either way it gates nothing.Accept, owner must know this.
Runbook timings from the playgroundThe playground overrides the shipped defaults: failoverCooldown: 30s (vs 5m), maxLagSeconds: 30 (vs 300), dns.ttl: 10 (vs 60). No timing you measured there transfers.Accept, owner re-measures on real config.
A role: read-only reader in the groupIt can neither be promoted nor source a backup. It is not a spare.Accept, owner must know this.

Disagree with any of my verdicts if you can say why. That is the point of the exercise.

What you can now say about playground

playground began as three sites and a counter application on a laptop. It is now a group whose failure modes you can enumerate, whose alerts do not lie, whose backups have been restored at least once, and which you could hand to an on-call rotation tonight — with an honest statement attached: it promotes unattended in about 12 seconds, it switches over on purpose at RPO 0 by construction, it reports the exact lost-transaction count in divergentGtid, and it will not save you from a DNS record you forgot to flip or a second cluster you fenced by guesswork. That statement — not the failover, not the backup — is what you take away from this course.

Flashcards

The bar for declaring a DR source dead

At least two of three independent signals: the source operator's /active-site returns 5xx, the source cluster's API server is unreachable, and source MySQL is TCP-unreachable from a third vantage point outside both clusters.

1 / 12

Quiz

Question 1 of 5

The cluster hosting playground has stopped answering: kubectl --context=source get nodes times out. That is your only signal so far. What does the source-fencing checklist say to do next?

Show answer

Answer: Get a second independent signal before touching the DR cluster, because nothing in Bloodraven will catch a cross-cluster split brain if you are wrong

Two of three signals is the bar because Bloodraven v1 does not automatically detect or resolve cross-cluster split brain — no operator watches both clusters, so your judgement is the only safety mechanism. Option 1 confuses the control plane with the data plane: Kubernetes will not even delete pods on an unreachable node, so the containers keep running and keep writing to the PV. Option 3 misapplies site roles: role-based fencing only acts on sites inside one failover group, and the DR group has no visibility of the source at all. Option 4 expects the decision matrix to work across clusters, but SPLIT BRAIN is raised only when more than one core site of a single group is writable — and in any case you cannot receive an alert from a cluster you cannot reach. (objective 13)

Question 2 of 5

A MysqlStandbyCluster in your DR cluster reports BucketReadable=True and SourceConfigKnown=True. This proves the source archive can be restored into the DR cluster.

Show answer

Answer: False

The reversal: those two conditions prove only that the DR cluster could read the bucket and parse the dump metadata. MysqlStandbyCluster is observability only today — no MySQL contact, no restore Jobs, no activation. It tells you a DR bootstrap would be possible and roughly how far back the dump plus archived binlog window could reach; it has not loaded a byte of that dump into a mysqld. Proving restorability is a separate act, and the only thing that proves it is a verification that actually restored the artifact. (objective 14)

Question 3 of 5

The source cluster is fenced and you are standing up a fresh MysqlFailoverGroup named playground in the DR cluster, to be populated from the S3 prefix the dead cluster was writing to. Which field does that?

Show answer

Answer: spec.initFromBackup, pointed at the source bucket prefix

spec.initFromBackup is the one-shot restore-on-first-boot field: it gates bootstrap until status.restore.phase reads Succeeded, then clone and replication proceed normally. Option 1 names the wrong restore entry point — restoreInPlace is re-runnable and operates against an already-live active primary, which a brand-new DR group does not have. Option 3 is the trap the standby CRD sets: spec.template does hold the shape of the group that a future activation would materialise, but activation is not implemented, so nothing consumes it. Option 4 confuses the archive with the restore — PITR governs whether binlogs were shipped at the source, and it must have been enabled there for a pointInTime request to be accepted at all, but it does not itself restore anything. (objective 14)

Question 4 of 5

You are writing the on-call runbook for playground and want every step to be a kubectl bloodraven invocation. Which of these is not a subcommand the plugin has?

Show answer

Answer: restore

The surface is exactly seven: status, promote, reclone, backup, verify-backup, version, help. There is no restore, and the reason is structural rather than an oversight — restore is not a CR at all, it is two fields on the failover group's spec, and the plugin's design rule is that it only writes resources the operator already reads and never talks to MySQL directly. promote and reclone exist because both are annotation-driven on the group, so the plugin can write them and inherit every gate. verify-backup exists because a verification really is its own CR that the operator reconciles into a throwaway instance. A runbook step that says kubectl bloodraven restore will fail at 3am with unknown command. (objective 15)

Question 5 of 5

You are signing off playground for production tonight. Four findings are open: (a) nobody can say what sync_binlog is on the running instances; (b) spec.replication.maxLagSeconds is set and nobody has said what it gates; (c) the nightly S3 backup has never been verified; (d) the runbook quotes a 30-second anti-flap cooldown, taken from a playground run. Which do you block the launch on, and what do you attach to the ones you accept?

Show answer

Answer:

Block on (a) and (c). sync_binlog=1 is only an overridable default — it is written into the base my.cnf before spec.mysqlConf is applied, so any override in the group's config wins silently. Until somebody reads the value off a running instance, the durability claim in the runbook is unverified, and it is the claim every RPO statement rests on. (c) is the same failure in a different place: an unverified backup is an assumption, and GitLab's 2017 outage is what an assumption looks like when it is finally tested. Accept (b) and (d) with a named owner. For (b) the owner must know that maxLagSeconds drives only the ReplicationLagging condition and is not a promotion gate — a replica beyond 300 seconds is still promoted, because no writable site is almost always worse. For (d) the owner re-measures every runbook timing against the shipped defaults, since the playground overrides failoverCooldown to 30s (against 5m), maxLagSeconds to 30 (against 300) and dns.ttl to 10 (against 60), so no playground timing transfers unchanged.

A full-credit answer shows: A strong answer blocks on (a) and (c) and accepts (b) and (d) with a named owner, and gives the mechanism in each case: sync_binlog is an overridable default written before spec.mysqlConf so an override wins silently; an unverified backup is an assumption; maxLagSeconds drives only the ReplicationLagging condition and is explicitly not a promotion gate; the playground overrides failoverCooldown, maxLagSeconds and dns.ttl so its timings do not transfer. A different split is acceptable if the reasoning names the mechanism — for example blocking on (d) because a runbook with wrong timings misleads during an incident. An answer that merely labels the items without a mechanism, or that treats maxLagSeconds as a promotion gate, is weak.

The gate is about verdicts, not notes. The two blockers are the ones where an unexamined assumption sits underneath a durability claim: an unread sync_binlog and an untested backup. The two acceptable items are dangerous only through ignorance — a lagging replica really is promoted, and playground timings really are not the shipped defaults — so they are survivable when a named human holds them and fatal when nobody does. (objective 15)

Erase saved progress?

This erases all quiz scores, reading progress, project checklists, and your name on the certificate. It cannot be undone, and it affects only this course in this browser.