Repository navigation
infra: add the deploy path (deploy user, forced command, limited account) - #13
Conversation
AbdikhaniAbdirashid
left a comment
There was a problem hiding this comment.
Ran your two checks plus a few more rather than taking the list on trust.
The script on the server is byte-identical to the committed file. Every permission is what you say it is: patch deployments, create services, patch ingresses, get replicasets, get pods and patch configmaps all yes; get secrets, delete deployments, create statefulsets, anything in kube-system, namespaces and nodes all no.
The part I'll remember is why RBAC alone isn't enough - a Deployment can mount a Secret the account can't read - and that gate 2 is what actually closes that, by only ever applying YAML that went through a reviewed PR. The three layers each answering one question is a clean way to hold it in your head.
On the Role's verbs
They look right to me for apply and rollout undo, and nothing is there that isn't needed. apply wants get + create + patch, rollout status wants watch on deployments and replicasets, rollout undo patches the deployment and reads the old replicaset. No delete is the right call.
One consequence worth writing down somewhere: without delete, removing a manifest from k8s/app/ doesn't remove the resource from the cluster. It keeps running until someone deletes it by hand. Not a problem, just a thing we'll both forget in three weeks.
Two things on relay-deploy.sh
1. rollout undo only reverts half of what step 4 applied.
Step 4 applies Deployments, Services, ConfigMaps and Ingresses from the new commit. kubectl rollout undo reverts the Deployment's pod template - the image - and nothing else.
So if a ConfigMap change is what breaks the rollout, the undo brings back the old pods and leaves them with the new config, and they fail the same way. The deploy reports a failure and rolls back, but the cluster is now a mix: old image, new everything else.
Does that matter before the pipeline deploys ConfigMaps that can break things? Probably not for 1.7. But it's the kind of thing that is much easier to reason about now than at 2am during the VG demo. A git checkout of the previous commit and a re-apply would be a real rollback; rollout undo is a partial one.
2. deployment/relay-api is hardcoded.
Fine today, since k8s/app/ only has the API. In phase 4 the worker arrives and k8s/app/ has two Deployments. Then:
applyupdates both;rollout statusonly waits for the API, so a worker that never becomes ready is reported as a successful deploy;rollout undoonly reverts the API, leaving API and worker on different commits.
Worth a note in the script now so phase 4 doesn't inherit it quietly - something like iterating over the Deployments the apply touched, or at least a TODO.
Smaller
flock is taken before gate 1, so an invalid SHA still waits up to 5 minutes for the lock before being rejected. Only reachable by someone who already has the deploy key, so it doesn't matter - but moving gate 1 above the lock costs nothing.
One question from my side
relay-deploy only applies k8s/app/, and my dashboard manifest is in k8s/dashboard/. So as it stands the pipeline deploys your half and nothing deploys mine.
Should the dashboard move into k8s/app/ and ride the same path, or get its own? Either is fine by me - I'd just rather decide it now than find out at 1.8. The G requirement says every merge is deployed automatically, and right now that would only be true for the API.
The deployer Role covers Deployments, Services, ConfigMaps and Ingresses, which is exactly what my manifest needs, so moving it in looks like it would just work.
From my side
The README is the clearest thing in the repo so far - the deploy-path diagram and the "if the deploy key leaks" section are exactly what I'd want to read at the defense, and the by-hand deploy command is good to have.
Approving. Neither point blocks 1.7, I'd just rather we decide on the rollback semantics before the pipeline is the only thing deploying.
|
Thanks for running the checks yourself, and for the failure case we hadn't tested. You're right on all four points. They'll go into one small follow-up PR before 1.8, so the pipeline never deploys with the old behaviour:
On the dashboard: it goes in That has one consequence for 1.8: every merge to main must build both images with the same SHA. Otherwise a dashboard-only commit would point at a When you push your manifest, it should:
#12 (the pipeline) is rebased onto main now: one commit, two files, checks green. It's ready for your review. |
#14) Follow-up to #13, from Abdikhani's review. It changes what `relay-deploy` does when a deploy fails, before the pipeline starts deploying in step 1.8. ## What changes - **A real rollback.** After every successful deploy, the script writes the commit to `/home/deploy/.relay-last-good`. If a later rollout fails, it deploys that commit again the same way (checkout, apply, wait). So ConfigMaps, Services and Ingresses go back together with the image. `kubectl rollout undo` only reverted the pod template. - **Every Deployment.** The script waits for each Deployment that `kubectl apply -o name` reports, not only `relay-api`. That covers the dashboard, and later the worker. - **Gate 1 above the lock.** An invalid SHA is rejected at once, without waiting for the lock. - **One function for deploy and rollback.** Steps 3–5 are now `deploy_commit`. Every step in it ends with `|| return 1`, because `set -e` is off inside a function that is called from `if`. - **`k8s/README.md`:** - the dashboard lives in `k8s/app/` too; - every merge builds every image; - nothing is deleted automatically, so we delete by hand; - how the rollback works. - **`infra/server/test-relay-deploy.sh`:** tests the script's decisions with fake `git`, `kubectl` and `flock`. No server and no cluster needed. ## The three endings of a failed deploy All of them exit 1, so the CI job turns red, because the commit that was asked for isn't live: - no last good commit yet → "no last good commit to roll back to" - the rollback worked → "rolled back to <commit>" - the rollback failed too → "rollback to <commit> failed" ## Tested `bash infra/server/test-relay-deploy.sh infra/server/relay-deploy.sh` → 11 passed, 0 failed: 1. A good deploy → exit 0, and it's remembered as last good. 2. A broken deploy → exit 1, the last good commit is put back, and the note is unchanged. 3. A broken first deploy → exit 1, and no note is invented. 4. The rollback fails too → exit 1, after trying the last good commit. 5. `kubectl apply` fails → exit 1, and it's not recorded as good. Test 5 guards the `set -e` trap. Without `|| return 1` on the apply line, it fails: exit 0, and the broken commit is saved as last good. The real rollback gets tested in 1.8, with a deliberately broken commit. ## After merging Install the script on the server again (see "install or update" in `k8s/README.md`), then check that the two copies match: `ssh relay 'cat /usr/local/bin/relay-deploy' | diff - infra/server/relay-deploy.sh` ## Added after an independent review of Phases 0–1 - **The gates are now tested** (test 6 and 7): - `whoami`, an injection, an uppercase SHA and a SHA with a trailing space all stop at gate 1, with nothing checked out. - A commit that isn't on main stops at gate 2, with nothing checked out and nothing applied. - **A failed rollout shows why.** The script prints the pods and the latest 20 events into the CI log (test 8). For that, the `deployer` Role gets read-only `get`/`list` on events (`k8s/platform/deployer.yaml`), which one of us applies by hand after merging. - **The last-good note is checked like gate 1** before a rollback (test 9). Its content goes into `git` and `sed`, and GNU sed can even run commands (`e`), so only a 40-hex value may reach it. - The result: `26 passed, 0 failed`, and `shellcheck` is clean on all three scripts. After merging, also apply `deployer.yaml` by hand, before reinstalling the script.
Step 1.6: the path the pipeline will use to deploy. Nothing deploys yet:
k8s/app/arrives in step 1.7, and the deploy job in the workflow in step 1.8.What's in it
k8s/platform/deployer.yaml: namespacerelay, ServiceAccountdeployer, and a Role + RoleBinding. It may update Deployments, Services, ConfigMaps and Ingresses, and read ReplicaSets and Pods (to follow and undo a rollout). No delete, no Secrets, nothing outsiderelay.infra/server/setup-deploy-user.sh: one-time setup of the Linux userdeploy(no password, no sudo). Its key is locked withrestrict,command="/usr/local/bin/relay-deploy", and it gets a kubeconfig with thedeployertoken and a clone of the repo.infra/server/relay-deploy.sh: the only program the deploy key can run. It runs a lock, then gate 1 (exactly 40 hex characters), then gate 2 (the commit is in main's history). Then it checks out that commit, appliesk8s/app/with__GIT_SHA__replaced, and waits for the rollout, or undoes it.k8s/README.md: which folder is applied how, and by whom.docs/CONTEXT.md: "Where we are", plus the deploy path under infrastructure facts.Already on the server:
deployer.yamlis applied, the setup script has run, and the deploy script is installed as root. The GitHub environmentproduction(main only) holds the secretDEPLOY_SSH_KEYand the variablesDEPLOY_HOSTandDEPLOY_KNOWN_HOSTS.Why this design
Port 6443 stays closed. Three layers each answer one question:
relayonly.RBAC alone isn't enough, because a Deployment could mount a Secret. That's why the script only applies YAML from main, where every change has been reviewed.
Tested
kubectl auth can-i … --as=system:serviceaccount:relay:deployer: patch deployments → yes. Get secrets, delete deployments, create statefulsets, anything inkube-system→ no.deployuser: patch deployments → yes, get secrets → no.whoami→ rejected by gate 1.; touch /tmp/pwned→ rejected by gate 1, and the file was never created.Check it yourself
From your laptop, on this branch:
Please look at
applyandrollout undo, and nothing more?relay-deploy.sh.