Skip to content

wordpress 1.0.0 — WordPress on a shared volumeset with bundled MariaDB - #527

Open
jacobecox wants to merge 5 commits into
mainfrom
claude/wordpress
Open

wordpress 1.0.0 — WordPress on a shared volumeset with bundled MariaDB#527
jacobecox wants to merge 5 commits into
mainfrom
claude/wordpress

Conversation

@jacobecox

Copy link
Copy Markdown
Contributor

WordPress on Apache + mod_php, on a shared (read-write-many) volumeset at /var/www/html, with a bundled MariaDB 11.

Testing

Two rounds on test-gvc and test-gvc-2. Round 1: 36 PASS / 2 FAIL. Round 2 after fixes: 13 PASS / 0 FAIL, run deliberately on a three-location GVC so the placement behaviour could actually be exercised.

Round 1's two failures, both now closed with measurements:

  • render-vs-stored gate. The workload sent a partial firewallConfig.external block, so the API backfilled three fields, and it never declared supportDynamicTags. Now ok workload/… with 0 differences. Deliberately did not declare outboundAllowPort — the measured diff shows the API does not backfill it on this workload, and declaring a field it does not store would recreate the drift the fix removes.
  • The private-access instructions did not work. Run verbatim, / returned 301 to http://localhost/, /wp-admin/ 302'd to an unresolvable *.cpln.local, and curl -L ended at final=000. The tunnel was fine — the site URL was not. Pairing the port-forward with wordpress.siteUrl: http://localhost:8080 inverts every one of those: 200, 200, and an authed dashboard. The "set it back before going public" step restores public browsing in 35 s, and skipping it genuinely breaks the site (control run).

Also fixed: the chart had no structural location confinement, so it relied entirely on a runtime guard that round 1 measured failing open on 1 boot in 5. It now ships defaultOptions.minScale/maxScale: 0 with a localOptions entry; on the 3-location GVC the two non-configured locations report This workload location is deactivated because maxScale is set to 0.

localOptions is stored verbatim — rendered and stored are field-for-field identical apart from the standard link normalization. That was the one place this fix could have introduced new drift, and it did not.

Known and stated honestly, not claimed solved

The bundled mariadb subchart cannot be pinned by a parent, so in a multi-location GVC MariaDB still starts in every location with its own volume — measured ready in all three within 47 s, genuinely independent instances. The README, values comments and briefing all say the protection is two-layer and best-effort rather than a guarantee, and carry the measured 1-in-5 fail-open and the drain-window hole.

Eleven render-vs-stored differences remain inside that subchart. They reproduced byte-for-byte across two different GVCs and region sets, so they are deterministic subchart behaviour, not environmental — same class as the nine in postgres 3.4.1.

The ~95–107 s outage on the first helm upgrade is the bundled MariaDB, not the web tier: a WordPress-only roll is transparent at both replica counts (200/200 and 300/300, each with a control proving replicas were actually replaced). wordpress.replicas does not mitigate it, and the README no longer implies it does.

For maintainer judgment

The guard hard-refuses a fresh install into a multi-location GVC, which is correct given the MariaDB split — but the user experience is a crash-looping workload whose only diagnostic is cpln logs. Worth deciding whether that should be softer, better-signposted, or conditional on whether a bundled database is in use.

🤖 Generated with Claude Code

jacobecox and others added 5 commits August 31, 2026 09:35
WordPress 1.0.0: Apache + mod_php on a `shared` (read-write-many) volumeset,
with a bundled MariaDB 11 via the `mariadb` 1.4.1 subchart.

The admin account is created from a required prerequisite dictionary secret
BEFORE Apache binds, so /wp-admin/install.php is never reachable and public
access can default on.

Build-stage probes (all five spec [UNPROVEN] items settled):
- A `standard` workload MAY mount a `shared` volumeset (reached ready: true).
- The shared volumeset is JuiceFS, mounted drwxrwxrwx root:root, and chown
  WORKS on it. The spec's `--no-same-owner` mitigation was harmful: it leaves
  root-owned dirs www-data cannot write into. Seed uses --owner www-data.
- No filesystemGroupId needed (mount is world-writable, container runs root).
- wp_install() driven from CLI verified end to end, with login + a wrong-password
  negative control, and the first-boot-only claim measured across a restart.
- curl present in the image; wget is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…opagation figure

Two review findings, no blockers.

1. templates/workload.yaml concatenated the user's internalAccess.workloads with
   this release's own workload links, so a user who listed the release's own
   workload (the natural thing to do, since the list is the only place that
   relationship is visible) got a duplicate array entry — a drift candidate
   against what the API stores. The own-workload links are now emitted first and
   a user-supplied link that matches one is skipped, matching
   calcom.internalFirewall and documenso's workload. Self-inclusion behaviour is
   unchanged; only the duplicate is gone.

2. The firewall propagation figure said "~30 seconds to several minutes" in the
   README and "~30 s to a few minutes" in values.yaml. The catalog's measured
   outer bound is 559 s (9m19s, cockroach), and "several minutes" invites exactly
   the "this knob is broken" conclusion the note exists to prevent. Both now read
   "~30 s to ~10 min", matching calcom, documenso and this template's own
   briefing, which already carried the correct figure.

Verified: the default `helm template --set global.cpln.gvc=test-gvc` render is
byte-identical to before (the dedupe only affects workload-list); a list naming
the release's own workload renders it once, and the conditional own-workload
entries (phpmyadmin, maria-backup) still appear when those subchart features are
on; all six README YAML blocks are still byte-identical substrings of
values.yaml; lint clean.
The key was emitted only under `workload-list`, so the DEFAULT `same-gvc`
install sent a PARTIAL `internal` block and the API completed it -- drift from
creation, on the shape most users get.

grafana-multi-location 2.0.0 shipped exactly this and it is worth recording why
it survived review there: a `helm upgrade` drift gate CANNOT see this class. It
reported `Unchanged` for both affected workloads the entire time the render and
the stored spec disagreed, because the field is backfilled once at creation and
then never churns. Only render-vs-stored finds it.

Both arms verified: `same-gvc` now emits `[]`, and `workload-list` still emits
this release's own workloads with user duplicates collapsed.

Note the bundled mariadb subchart's own workload has the same gap. That is the
`mariadb` template's defect and affects every chart vendoring it, so it is not
fixed here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… make the port-forward path work

Test round 2026-08-31 (36 PASS / 2 FAIL) plus a coordinator finding. Four fixes,
each against a measurement, not an inspection.

1. render-vs-stored FAIL (gate requires 0 differences; 15 measured, 4 ours).
   `templates/workload.yaml` sent a PARTIAL `firewallConfig.external` block, so
   the API completed it with `inboundBlockedCIDR`, `outboundBlockedCIDR` and
   `outboundAllowHostname` as `[]`, and an undeclared `supportDynamicTags`
   stored as `false` — four differences from creation on release test-wp. All
   four are now declared (shape copied from clickhouse 3.0.0). `outboundAllowPort`
   is deliberately still absent: the API did NOT backfill it, and declaring a
   field it does not store would create the drift this removes.
   The two no-op `helm upgrade`s were fully green throughout — this class is
   written once at creation and never churns, which is why both gates exist.
   The other 11 differences belong to the vendored `mariadb` 1.4.1 workload and
   cannot be fixed from a parent chart; recorded in the briefing instead.

2. No structural location confinement. The workload rendered a bare
   `minScale: {{ .Values.wordpress.replicas }}` with no `localOptions`, so it ran
   in EVERY location the GVC had and the boot-time guard was the only thing
   between a user and a second site — and the guard is best-effort (below).
   Adds a top-level `location` value with `defaultOptions.minScale/maxScale: 0`
   plus a `localOptions` entry carrying the real count (pgedge 2.0.0 /
   cockroach 2.0.0 / plane 1.0.0 shape), so an undeclared location starts nothing
   by construction. The bundled MariaDB still cannot be pinned by a parent and
   still runs everywhere — said plainly rather than claimed solved.

3. The guard is best-effort and now says so. It failed open on 1 boot in 5
   (`WARNING: could not read GVC … guard SKIPPED`) and passed during a
   location-removal drain, which let aws-us-east-2 replicas seed and run a second
   complete independent WordPress install (east-1: 7 posts incl. the test post and
   its attachment; east-2: the bare 3-post fresh-install set). README, values
   comments and briefing now describe a safety net, not a guarantee. The guard is
   kept — it covers the unpinned MariaDB and the opposite direction — and it now
   logs the HTTP status, so a 403, a timeout and an unparseable 200 are no longer
   one identical line. Also drops the new `mktemp` dependency for `$$` paths.

4. The README's port-forward instruction stranded the user. Run verbatim at
   `publicAccess: false`: `/` → 301 to `http://localhost/`, `/wp-admin/` → 302 to
   an unresolvable `*.cpln.local`, `curl -L` → final=000; only `/cpln-health.php`
   answered. The tunnel was fine (200 through it while the canonical endpoint
   returned 403) — WordPress rewrites from its stored site URL. Now paired with
   `wordpress.siteUrl: http://localhost:8080` on the same port (measured: front
   page 200, login 302, wp-admin 200), with the port agreement made explicit.

5. The ~95–107 s first-upgrade outage is the bundled MariaDB, not the web tier.
   A WordPress-only roll was transparent at both replica counts (200/200 and
   300/300, each with a control proving the replicas were replaced), so
   `wordpress.replicas` does not mitigate it. The Availability section said the
   opposite by implication; corrected.

Verified without deploying: default render clean (14 resources, no other values);
all four fields present on both publicAccess arms and the workload-list arm;
localOptions carries exactly defaultOptions' field set plus `location`; the boot
guard's branches exercised offline (1-loc ok / 2-loc fresh FATAL / 2-loc
initialised warn / pinned-location mismatch / fail-open) and `bash -n` clean;
12 existing validation negatives and 4 positive controls unchanged; 3 new
`location` negatives reject; every README YAML block byte-identical to
values.yaml; template-lint clean.

Needs a deploy to settle: whether the API stores `localOptions` verbatim (the
render-vs-stored gate must be re-run), and the guard's HTTP-status arm against
the real $CPLN_ENDPOINT — a local stub can only prove the parsing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The link-form check caught '//location/x' but not a comma-separated list
('aws-us-east-1,aws-us-east-2'), which Helm and the API both accept and
which stores a placement matching no real location -- nothing runs anywhere
and the logs are silent. Found on documenso in round 2; the same class
applied here.

One regex closes every arm: link form, comma list, uppercase, whitespace.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant