Skip to content

Epic: one version ships, both halves promote together, and the schema leads it #1289

Description

@ExtraToast

The api and the frontend reach production independently today, and the schema arrives with whichever pod boots first. Keel polls a moving :latest per Deployment and rolls each one on its own schedule, so two services that were built from one commit can be minutes apart in production. Flyway runs at api boot, which means a migration is discovered pod by pod rather than gated before any pod serves.

This epic makes a release one artefact: one version, both halves promoted together only when both are healthy, and the schema moved once, before either half takes traffic.

The decisions

Settled by design review rather than assumed, and recorded here because several of them close off options that look reasonable:

  • The deploy ref is a digest, written by CI on the release branch. release-please owns the version; it does not write the deployed image. The release images are built from release-please--branches--main before the PR merges, so the manifest never points at a tag that does not exist yet. Nothing pushes to main.
  • The release branch is owned by one app identity. A ruleset restricts it to a dedicated GitHub App, and a required check recomputes the pin so a force-push that drops it makes the PR unmergeable.
  • Flagger promotes the pair. Each service is a blue/green canary; paired confirm-promotion gates read the sibling's phase, so a release promotes both or neither. Promotion copies the canary spec onto the -primary Deployment, which then rolls — so a sub-minute endpoint skew survives, and one release of backward compatibility remains the contract.
  • Liquibase, baselined, YAML. The 96 Flyway migrations are not ported; today's schema becomes changeset 1 and production is marked in sync. PostgreSQL is a someday, so the baseline is a MariaDB snapshot and only new work is written as portable changeTypes.
  • Expand/contract, no schema rollback. The migration Job runs before the canary starts, so the currently-serving version is live against the new schema for the whole analysis. That makes backward compatibility a precondition rather than a courtesy, and it is why reverting a release never reverts the schema. Enforced by a structured check over changeTypes, not a regex over SQL.
  • No pre-production. One VPS. The effort goes into making the canary gate real — generated load, metric thresholds, the pre-rollout Job — rather than into a staging environment with fake data.

What this work carries

  • Renovate, not Dependabot. build(deps): one bot updates this repo, and it reaches the cluster too #1234 settled that one bot updates this repo. renovate.json already has a kubernetes manager over platform/cluster/flux/**, automerge: false for that path, and a rule disabling updates for ^ghcr\.io/esa-blueshell/ whose stated reason — "the site's own images run on the :latest tag" — stops being true here. The rule stays; the reason on it needs rewriting. One bot updates this repo, and only one #1277 neighbours this.
  • The first Flagger apply costs an outage, roughly one api cold start: Flux prunes the hand-written Services in the same apply that creates the Canaries, and Flagger only recreates them once its -primary Deployments are Ready.
  • The Canaries do not set revertOnDeletion, deliberately. With the flag at its default of false, anything that deletes a Canary — a revert, a Flux prune after a path or Kustomization rename, a hand deletion while debugging — leaves the source Deployment at zero replicas while Flux recreates a Service whose selector matches nothing, and the site stays down until someone scales it back by hand. Judged an acceptable risk when api and frontend promote as a pair, or not at all #1294 shipped, on the grounds that a rollback is very unlikely; the flag is one line per Canary if that judgement ever changes. Worth adding the moment anyone restructures apps/stateless, since a rename is the way this fires without anyone deciding to roll anything back.

Where the work already is

PR #1316 carries both #1313 and #1294 as one commit, rebased onto current main. Their changes overlap inside the Deployments, so splitting them would mean an intermediate state with the hand-written Services already gone and no Flagger to recreate them. #1293 remains separate: it reworks the hand-bumped tag into a CI-written digest, at which point release-please stops writing the deploy ref.

Flux applies main within a minute and there is no other gate, so a merge is a deploy. The runbook now carries a suspend-merge-resume procedure; #1294 in particular must not be merged without it.

Slices

Release path

Schema ownership

Measurement and supply chain

#1290, #1291, #1292, #1295, #1301, #1305 and #1313 have no blockers and can all start at once. #1292 is worth taking first regardless: until it lands, no manifest in apps/stateless is built by CI at all.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestepicA parent issue holding a set of slices

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions