fix(ops): stop a compose replica from blocking the blue/green release
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m57s
CI / tests-unit (push) Successful in 2m3s
CI / tests-ui (push) Successful in 2m49s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m48s
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m57s
CI / tests-unit (push) Successful in 2m3s
CI / tests-ui (push) Successful in 2m49s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m48s
The deploy failed after the build, the migrations and the browser gate:
"Port 3002 is already in use". The holder was `epicnext-cms`, a compose
replica of release 6bffc537 that the daily scripts/docker-update.sh cron
had recreated at 03:30 with restart=unless-stopped. nginx serves the green
slot on 3003, so that replica was squatting the blue slot the next
candidate needed, and live traffic never noticed.
It got there because the updater's CI-ownership guard only tested
epicnext-cms-app. After a cutover to the green slot that container is
stopped, renamed and deleted, so the guard stopped firing while the host
stayed CI-managed.
- scripts/docker-update.sh: refuse a compose deployment on a CI host by
checking both slot containers and the nginx upstream, which is the only
thing that still marks the host as blue/green while a slot is idle.
- scripts/ci-deploy.sh: retire a compose replica of this checkout from
the candidate port before starting the candidate, so a stray replica
can never block a release again. Never a slot container, never the port
nginx serves; anything else still fails loudly in assert_port_free.
- Tests cover both directions: a squatting replica is removed and the
release lands, a replica on the live port is left alone.
This commit is contained in:
1 parent
11ad6d4376
commit
5b2eb91c5c
7 files changed
+186
-9
No files matched your search
@@ -56,6 +56,22 @@ function simulate(scenario: string, args: string[] = []) {
|
||||
join(dir, "scripts/docker-prune.sh"),
|
||||
);
|
||||
writeFileSync(join(dir, ".env"), "HOTEL_NAME=Test\n");
|
||||
// De CI-eigendomscontrole leest het nginx-upstream-bestand, net als
|
||||
// scripts/ci-deploy.sh. Tests draaien op de productiehost, dus het
|
||||
// scenario 'ci-upstream' levert zelf een bestand met een slot erin en alle
|
||||
// andere scenario's een leeg bestand — anders zou elke test hier op een
|
||||
// echte blue/green-host onterecht stoppen.
|
||||
writeFileSync(
|
||||
join(
|
||||
dir,
|
||||
scenario === "ci-upstream"
|
||||
? "cms_upstream_servers.conf"
|
||||
: "no-such-upstream.conf",
|
||||
),
|
||||
scenario === "ci-upstream"
|
||||
? "server 127.0.0.1:3003 max_fails=2 fail_timeout=10s;\n"
|
||||
: "",
|
||||
);
|
||||
if (scenario.startsWith("saved-"))
|
||||
writeFileSync(
|
||||
join(dir, ".docker-install"),
|
||||
@@ -82,6 +98,12 @@ function simulate(scenario: string, args: string[] = []) {
|
||||
: scenario.startsWith("registry")
|
||||
? "registry.test/team/cms"
|
||||
: "",
|
||||
CMS_UPSTREAM_FILE: join(
|
||||
dir,
|
||||
scenario === "ci-upstream"
|
||||
? "cms_upstream_servers.conf"
|
||||
: "no-such-upstream.conf",
|
||||
).replaceAll("\\", "/"),
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -169,6 +191,8 @@ describe("Docker clone updates", () => {
|
||||
it.each([
|
||||
"dirty",
|
||||
"ci-active",
|
||||
"ci-green",
|
||||
"ci-upstream",
|
||||
"pull-failure",
|
||||
"build-failure",
|
||||
"migration-failure",
|
||||
@@ -177,6 +201,20 @@ describe("Docker clone updates", () => {
|
||||
expect(r.status, r.output).not.toBe(0);
|
||||
expect(r.calls).not.toContain("compose up");
|
||||
});
|
||||
// Regressie: na een cutover naar het groene slot bestaat epicnext-cms-app
|
||||
// niet meer en zag deze controle alleen een vrijgekomen blauwe container. De
|
||||
// dagelijkse `docker-update.sh` startte toen een compose-replica op poort
|
||||
// 3002 en blokkeerde elke volgende release. De blauwe container alleen
|
||||
// controleren is dus geen bewijs dat de host niet door CI beheerd wordt.
|
||||
it.each(["ci-green", "ci-upstream"])(
|
||||
"refuses a compose deployment while CI owns the host (%s)",
|
||||
(scenario) => {
|
||||
const r = simulate(scenario);
|
||||
expect(r.status, r.output).not.toBe(0);
|
||||
expect(r.output).toContain("This host is managed by CI");
|
||||
expect(r.calls).not.toContain("compose up");
|
||||
},
|
||||
);
|
||||
it.each(["wrong-image", "wrong-release", "wrong-public", "recreate-failure"])(
|
||||
"never reports success for %s",
|
||||
(scenario) => {
|
||||
|
||||
Reference in new issue
Block a user