Repository navigation
infra: deploy every merge to main and check that it's live - #16
Conversation
|
Not reviewing this yet since it's a draft and you've said it changes on the 1. The handover outage needs a time, not just a step. Merge plan step 5: "delete the hello app from the cluster by hand. That's our only public hostname, and the demo is Wednesday. I'm not arguing Two things make me think it could run longer than 1–2 minutes:
Can we do the switch-over together, at a set time, with the staging issuer tried 2. The comment above it says: worst case is the lock (5 min) + rollout (3) + rollback (3)
At three it goes over 20 and GitHub kills the job — during the rollback, which is This is the same root cause as my line-30 comment on #14, where Keeping this as a plain comment rather than line comments on purpose — you're |
a8867a8 to
67b29dd
Compare
Follow-up to #14, from Abdikhani's review of it. ## `infra/server/relay-deploy.sh` - **Lock (line 30):** waits 15 minutes instead of 5, which is longer than the worst case of a deploy plus its rollback. If it still can't get the lock, it says "nothing was changed, re-run this job", so the failure is obviously retryable. In CI this rarely waits at all, because the pipeline's `concurrency` group already runs main's deploys one after the other. The lock protects against an admin deploying by hand at the same moment. The deploy job's timeout goes from 20 to 30 minutes in #16. - **Namespace (line 43):** a comment saying every kubectl call gets namespace `relay` from the kubeconfig's context, set by `setup-deploy-user.sh`. - **`return 0` (line 81):** `deploy_commit` now ends with an explicit `return 0`, so "0 on success" holds by construction. - **180 s per rollout:** unchanged on purpose. With one node, an image is downloaded once, and each commit only changes our small code layers. The first deploy's log will show the real time. ## `infra/server/test-relay-deploy.sh` - **Test 10:** when another deploy holds the lock, the script exits 1, tells you to re-run, and changes nothing. Result: `29 passed, 0 failed`, and `shellcheck` is clean. ## `k8s/README.md` - **Renames** are the dangerous case without `--prune`: the old object stays and keeps serving. A rename = the new name in the PR, plus deleting the old object right after it merges. - **The rule from your review:** server and platform changes are applied by the one who merges, right after merging, and the check output is posted on the PR. After merging: install the script again and post the `diff` check here.
AbdikhaniAbdirashid
left a comment
There was a problem hiding this comment.
Approving. Both things I raised while this was a draft are handled: the lock arithmetic in the comment above timeout-minutes now matches #19's 15 minutes and counts two Deployments, and needs: [image, deploy-script] means a broken deploy script can't reach the server. Adding deploy-script-test to CI is better than what I asked for — I only wanted the numbers to stop drifting, and you made the script's own tests run on every PR instead.
What I checked
needs: [image, deploy-script]is wired as the comment claims.- The version-check loop ends with
::error::andexit 1, so a check that never sees the commit turns the job red rather than passing quietly. concurrencyon main queues rather than cancels, so a running deploy is never killed halfway.- The hello Ingress still holds
api.myrelay.techright now — the handover in merge-plan step 5 is still ahead of us.
One line comment on the version-check timing. It's arithmetic, not a design problem.
On merge-plan step 5, repeating what I said before only because it's the part with no undo: a 1–2 minute outage on our only public hostname, with a fresh certificate in the middle of it. Can we do that together, at a time we pick, rather than as a side effect of clicking merge? I'd like us both watching when api.myrelay.tech goes dark.
| # relay-deploy already waited for ready pods inside the cluster. This checks the whole | ||
| # public path a customer uses (DNS, certificate, Traefik, Service, pod) from outside. | ||
| # An old pod can still answer for a few seconds after the rollout, and the first deploy | ||
| # also waits for a brand-new certificate, so we ask for up to 36 × 5 s ≈ 3 minutes. |
There was a problem hiding this comment.
36 × 5 s ≈ 3 minutes counts the sleep but not the curl.
Each turn of the loop is curl --max-time 10 plus sleep 5, so the worst case per attempt is 15 s, not 5:
| fast curl | slow curl | |
|---|---|---|
| per attempt | 5 s | 15 s |
| 36 attempts | 3 min | 9 min |
Normally curl returns in milliseconds and 3 minutes is right. But the slow case isn't independent of the failing case — they're the same case. If Traefik isn't answering yet during the Ingress handover, connections hang until --max-time fires, and that's also when we'd actually use all 36 attempts.
That matters for the job budget:
deploy succeeds: lock 15 + rollout 6 = 21 min
+ version check (worst case) 9 min
= 30 min <- exactly timeout-minutes: 30
So on the one run where the verification is struggling, the job can be killed by the timeout instead of printing your ::error:: line — and we'd lose the per-attempt log that says what it saw, which is the whole point of the loop.
Easiest fix is probably --max-time 5 plus a shorter sleep, keeping the wall-clock budget but making it predictable. Or state the real worst case in the comment and raise the job timeout. Either is fine; I mainly don't want the number in the comment to be the one we trust when we're debugging the first deploy at 23:00 on Tuesday.
There was a problem hiding this comment.
Fixed in the latest push. The deploy timeout is now 35 minutes, and the comments include curl’s timeout: 36 × (10s curl + 5s sleep) = up to 9 minutes, plus overhead.
For two Deployments, the successful path budgets 30 minutes, leaving 5 minutes for overhead. The rollback path budgets 27 minutes and exits without running the public check.
I’ll coordinate the switch-over time with you before removing hello and merging
AbdikhaniAbdirashid
left a comment
There was a problem hiding this comment.
Re-approving for the new push — require_last_push_approval is on, so my approval of 67b29dd doesn't carry over to d00063d.
The budget reads right now, and more importantly it shows its working:
| path | budget | headroom to 35 |
|---|---|---|
| success | 15 lock + 6 rollout + 9 public check = 30 | 5 min |
| rollback | 15 + 6 + 6 = 27, no public check | 8 min |
Naming the curl timeout in the comment is the part that matters. The next person to change --max-time or the attempt count can now see what else moves with it, which is exactly what went wrong the first time round.
Thanks for picking up the switch-over too — I'll be around whenever suits you.
Step 1.8: every merge to main is deployed, then checked from outside. Rebased onto main now that #12, #14 and #15 are merged. Merge only after the switch-over below.
What changes
api.yml→pipeline.yml. It now deploys too, and the dashboard's jobs will join it later (needs:only works inside one workflow file).main. Every merge is a release: a deploy needs an image tagged with exactly the merged commit, even when onlyk8s/app/or the docs changed.deploy-script-teston every PR and merge. It runsinfra/server/test-relay-deploy.sh(relay-deploy's gates and rollback, with fake git and kubectl) andshellcheckon the server scripts. The deploy needs it, so a broken deploy script never reaches the server.deploy, afterapi-imageanddeploy-script-test, only on a merge to main:environment: production: the only way to getDEPLOY_SSH_KEY, and only on main.permissions: {}: no GitHub access at all.timeout-minutes: 30: the worst case is the server's lock (15 min, infra: deploy script follow-ups from the #14 review #19) plus a rollout and a rollback.known_hosts, thenssh … deploy@<vps> "$GITHUB_SHA"(StrictHostKeyChecking=yes,BatchMode=yes). The exit code ofrelay-deploybecomes the step's.https://api.myrelay.tech/versionuntil it answers this commit, for up to 36 × 5 s, because the first deploy also waits for a new certificate.timeout-minutes: 10onapi-testandapi-image(from the infra: add API pipeline (test, build, push to GHCR) #12 review).k8s/hello/is deleted, together with its row ink8s/README.md. The API's Ingress takes overapi.myrelay.tech.Tested
actionlint1.7.7 (with shellcheck on therun:scripts): no findings.26 passed, 0 failed, andshellcheckis clean.bash -eo pipefailagainst a local container:Already done before merging
deployer.yamlapplied: the deployer can read events, and still can't read Secrets or delete.relay-deployinstalled, byte-identical to main.relay-api:a8274c1.Switch-over (right before merging)
default: the Ingress, Service and Deployment, plus its leftover TLS Secretapi-myrelay-tech-tls.api.myrelay.techis down for about 1–2 minutes, once./versionanswers the commit.After the first deploy
a78934cby hand. It has no image, so the rollout fails andrelay-deployredeploys the last good commit, while a curl loop shows only 200s.api-testanddeploy-script-testrequired checks.