The moving parts

An operator that polls, a sidecar that fences, four kinds of Service, and three site roles. Which component can do what — and which one can act when the other is gone.

By the end of this topic you can

  1. Trace a write from the application through mysql-playground-primary to the pod that currently owns it
  2. Say what the sidecar does that the operator cannot, and why the binlog archiver lives there
  3. Tell primary-candidate, dr-only and read-only sites apart by what each is allowed to become

playground is running. Three sites — iad, pdx and reader — and a counter whose page reads every two seconds and whose one button writes. What you cannot yet say is which pod that button’s next UPDATE lands on, or who decided it should be that one.

Four things stand between the application and a MySQL data directory. Meet them in the order a write meets them.

First contact: the Services

The counter does not connect to a pod. It connects to mysql-playground-primary.

A write as four numbered arrows: counter app, to the -primary Service, to the IAD pod labelled role=primary, to disk. A small sidecar box sits on the pod.
The write path. The Service is a name. The operator owns the label that makes one pod match it.
Anatomymysql-playground-primary
prefixmysql-
Fixed. Every Service the operator creates for a group starts here.
group nameplayground
The metadata.name of the MysqlFailoverGroup.
role suffix-primary
One of four suffix shapes: -primary, -replicas, -, and --internal. This one selects whichever pod currently carries shipstream.io/role=primary.

Bloodraven creates four kinds of Service per group. Four kinds, not four objects. Two kinds are per-site, two are group-wide, so the count is 2 × len(sites) + 2. For playground that is eight Services.

ServiceScopeWhat it is for
mysql-playground-primarygroupThe write endpoint. Exactly one pod behind it, or none.
mysql-playground-replicasgroupThe read endpoint. Every replica currently fit to serve.
mysql-playground-<site>per siteSite-local access — mysql-playground-iad, and so on.
mysql-playground-<site>-internalper siteThe stable in-cluster address replication and the sidecars point at.

-primary selects on two labels: app.kubernetes.io/instance=playground and shipstream.io/role=primary. -replicas selects on three: instance, shipstream.io/role=replica, and shipstream.io/healthy=yes.

That third label is not decoration. For a read-only reader the operator stamps healthy=yes only when the site is actually replicating from the active primary and is not too far behind. Fail the check and the pod silently leaves the read endpoint.

The per-site Services do not look at role. mysql-playground-<site> selects on name, instance and site — plus healthy=yes, but only when that site’s role is read-only. The -internal Service has no health gate on any site, which is the point of it: peers and sidecars must reach a pod that is not serving yet.

During an in-place restore or the draining phase of a planned failover the operator stamps the affected pod shipstream.io/role=fenced. That value matches neither shared selector. The pod keeps running, keeps its disk, keeps its IP, and appears behind neither endpoint. Fencing at the Service layer is one label write.

TreeEight Services for a three-site group
  • playground
    • group-wide (2)
      • mysql-playground-primary
      • mysql-playground-replicas
    • iad
      • mysql-playground-iad
      • mysql-playground-iad-internal
    • pdx
      • mysql-playground-pdx
      • mysql-playground-pdx-internal
    • reader
      • mysql-playground-reader
      • mysql-playground-reader-internal

Confirm the count:

kubectl get svc -n bloodraven-playground \
  -l app.kubernetes.io/instance=playground -o name

Eight lines. Six of them carry a site name.

Second: the operator

Something has to decide which pod wears role=primary. That is the operator: a single Deployment, one replica, with leader election enabled. It polls every site, evaluates what it sees, and writes labels, status and DNS. Unit 2 takes the poll loop apart.

The operator is not on the request path. A healthy primary and replica keep serving with zero operator involvement. Kill it and your application does not notice.

The bill comes due elsewhere. While the operator is down, nothing gets promoted.

Third: the sidecar

Every MySQL pod runs a second container beside mysqld. Its independence is the whole point.

The sidecar can set super_read_only=ON on its own MySQL with the operator dead, unreachable or mid-crash-loop. It does not ask permission. That is the thing the operator structurally cannot do: an operator that cannot reach a site cannot fence it, and the sites you most need fenced are exactly the ones you cannot reach.

The binlog archiver lives there for a physical reason. Point-in-time recovery needs sealed binlog files, and finding them means watching the data directory on disk. That disk is ReadWriteOnce — one node, not one pod. A central operator on some other node cannot mount it. The archiver has to run where the data is.

Fourth: the roles

Each site declares a role. The enum has three values. It defaults to primary-candidate. One rule separates them: promotability is exactly role == primary-candidate.

RoleMay be promoted?Counted in the topology tallies?In playground
primary-candidateYes — the only role that mayYesiad, pdx
dr-onlyNeverYes — it counts, it just cannot winnone
read-onlyNeverNo — excluded from the talliesreader

dr-only is the one people get wrong. It is a full participant in the topology view. It simply cannot win a promotion. read-only goes further: invisible to the decision, never a backup source. Any non-candidate found writable is routed straight to fencing.

Where you now stand

You can trace the counter’s write: mysql-playground-primary → the pod labelled role=primary → whichever of iad or pdx is the active site. You can name every pod’s role in playground without guessing: iad and pdx are promotable, reader never is, and only one is writable at any moment.

Next: why this shape, and what Bloodraven will refuse to do for you.

Flashcards

The mysql-playground-primary Service selects on which pod labels?

Two: app.kubernetes.io/instance=playground and shipstream.io/role=primary. No health label.

1 / 12

Quiz

Question 1 of 5

A nightly reporting job against playground runs long analytical SELECTs and must never issue a write. It can tolerate a few seconds of staleness but not minutes. Which Service should it connect to?

Show answer

Answer: mysql-playground-replicas

mysql-playground-replicas is the group read endpoint — instance, shipstream.io/role=replica and shipstream.io/healthy=yes — so it pools every replica currently fit to serve and drops one silently when it is not. Pointing at mysql-playground-primary works but wastes the primary's capacity on reads and puts an accidental write one bug away from succeeding. mysql-playground-reader is the near miss, and worth being exact about: because reader is a role: read-only site, its per-site Service does carry the same healthy=yes gate, so staleness is covered — but it pins the job to one pod, and the moment that pod loses the label the Service has no endpoints at all rather than falling back to pdx. mysql-playground-reader-internal is the one with no health gate: it exists for sidecar and peer traffic and sets publishNotReadyAddresses: true, so it will hand you a pod that is not serving yet. (objective 4)

Question 2 of 5

During the draining phase of a planned failover, the operator stamps the iad pod shipstream.io/role=fenced. Which Service still resolves to that pod?

Show answer

Answer: mysql-playground-iad, the per-site Service, which selects on site rather than role

The per-site Services select on name, instance and shipstream.io/site — never on role — so mysql-playground-iad and mysql-playground-iad-internal still reach the pod, which is how the operator and the sidecars keep talking to a fenced instance. (iad is a primary-candidate; on a role: read-only site the per-site Service adds a healthy=yes conjunct, but role is still not in the selector.) -primary requires role=primary and -replicas requires role=replica; fenced is neither, which is the entire mechanism. Nothing is deleted: the pod keeps running, keeps its PVC and keeps its IP — only its label changed. (objective 4)

Question 3 of 5

A dr-only site holds the freshest GTID set when the primary dies, so Bloodraven will promote it rather than lose those transactions.

Show answer

Answer: False

The reversal: GTID freshness never rescues a non-candidate, because promotability is checked first and is exactly role == primary-candidate. A dr-only site is counted in the topology tallies — unlike a read-only site it is a full core participant — which is what makes this tempting, but counting and being promotable are different properties. If such a site ever comes up writable it is routed to fencing rather than accepted. To make a site promotable you change its declared role; you cannot earn it with fresh data. (objective 6)

Question 4 of 5

The binlog archiver runs inside the per-pod sidecar rather than centrally in the operator. Give the physical reason, and say what would break if it were moved into the operator.

Show answer

Answer:

The archiver has to inotify-watch mysql-bin.index in /var/lib/mysql and read the sealed binlog files directly off disk, so it needs the MySQL data PVC mounted. That PVC is ReadWriteOnce, which binds it to a single node — the node running that site's MySQL pod. A central operator scheduled on any other node simply could not mount it, so it would have no way to see rotations or read the files. The sidecar is co-located with the data by construction, so it gets the mount (read-only) for free.

A full-credit answer shows: A strong answer covers: (a) the archiver needs the data PVC mounted, for inotify on the binlog index and for direct file reads; (b) the PVC is ReadWriteOnce, meaning one node, so a central component elsewhere cannot mount it; (c) the sidecar is in the same pod, hence on the same node as the data. Credit also for noting the mount is read-only, or that the archiver gates on @@read_only so only the primary uploads. Do not credit 'to reduce operator load' or 'for scalability' alone — the constraint is a mount constraint, not a performance one.

The decision is forced by storage topology, not by design taste: inotify and direct binlog reads both require the data volume, and ReadWriteOnce means one node, not one pod. Anything that must touch the data directory has to run where the data directory is. (objective 5)

Question 5 of 5

The operator Deployment in playground is scaled to zero and stays down for an hour while the counter application keeps writing. What happens?

Show answer

Answer: Writes keep flowing through mysql-playground-primary, and each site's sidecar can still fence its own MySQL, but nothing will be promoted if the primary dies

The operator is on the failure-detection and promotion path, not the request path, so a healthy primary and replica keep serving with zero operator involvement; correctness is still held by the sidecars, which fence locally without asking anyone. Writes do not stop — nothing is proxied through the operator, and its single replica with leader election exists for safe single-writer decisions, not for traffic. Endpoints do not shed either: shedding is an active label write the operator performs, and a dead operator writes nothing, so the labels simply freeze as they were. And the sidecars never elect anything — they enforce locally and have no cross-site view to decide a promotion with. (objectives 4, 5)

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.