Files
beer_data/RUNBOOK.md
T
hermes 4d1e070d74 docs: document the analysis page and the CI deploy flow
The RUNBOOK still said the workflow did not deploy to the cluster. It does
now - it builds, pushes, applies the manifests, rolls the pods and verifies
the app from inside the pod. The manual deploy path is documented alongside
it, along with the dry-run step that catches the schema errors kubectl apply
silently swallows.
2026-11-09 13:12:51 +10:00

241 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RUNBOOK — Brew Log (beer_data)
Operational notes for the `beer` namespace deployment.
## What runs where
| | |
|---|---|
| Namespace | `beer` |
| Deployment | `beer-deployment` (3 replicas) |
| Image | `git.aridgwayweb.com/armistace/beer-data:latest` |
| Service | `beer-service`, NodePort **30015** → container port 5000 |
| Database | MongoDB at `192.168.178.229`, database `beer_db` |
| Secret | `beercred` (registry pull credentials) |
Access: `http://<any-cluster-node>:30015/`
The pods are pinned to `archlinux-k3s-3` (`.161`) via `nodeSelector`.
## Environment variables
| Variable | Value | Source |
|---|---|---|
| `MONGO_HOST` | `192.168.178.229` | Deployment manifest |
| `MONGO_USER` | `root` | Deployment manifest |
| `MONGO_PASS` | *(see below)* | Deployment manifest |
| `SECRET_KEY` | *(see below)* | Deployment manifest — Flask session signing key. **Required** |
| `FLASK_ENV` | `production` | Dockerfile |
| `FLASK_DEBUG` | `0` | Dockerfile |
`SECRET_KEY` and `MONGO_PASS` must be set in the live Deployment. They are
**not** committed to this repo — read them from the running cluster:
```bash
kubectl -n beer get deploy beer-deployment -o jsonpath='{.spec.template.spec.containers[0].env}'
```
> **The app refuses to start without a real `SECRET_KEY`.** There is no default
> and no placeholder: a missing or placeholder value raises
> `ImproperlyConfigured` and the container exits. A known session-signing key
> would let anyone forge a login cookie, so a misconfigured deployment must
> fail loudly rather than run insecurely.
>
> Generate one with:
> ```bash
> python -c 'import secrets; print(secrets.token_hex(32))'
> ```
### Moving the credentials into a Kubernetes Secret
The live Deployment currently carries `MONGO_PASS` as a plain environment
value. Moving it to a Secret is the better shape and is **not** done here
because it would mean replacing the live hand-provisioned values — a change
that needs the operator's sign-off. When it is done:
```yaml
env:
- name: MONGO_PASS
valueFrom:
secretKeyRef:
name: beer-db
key: mongo_pass
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: beer-app
key: secret_key
```
## Deploying
Pushing to `master` triggers `.gitea/workflows/build_push.yml`, which builds
the image, pushes it, **deploys it to the cluster and verifies it**. There is
no manual step.
The workflow is a single job, in this order:
1. **Checkout**
2. **Create Kubeconfig** — writes `secrets.KUBEC_CONFIG_BUILDX_NEW_2` to
`$HOME/.kube/config`. This is FIRST because the buildx `kubernetes` driver
needs it to create its builder pod in the cluster.
3. **Set up Docker Buildx** — `driver: kubernetes`, `namespace=gitea-runner`.
4. **Login to the registry** — `secrets.REG_PASSWORD`.
5. **Build and Push** — `linux/amd64` only. A multi-arch build runs qemu,
which crashes the buildkit pod on this NUC (exit 255, `SandboxChanged`)
and the build then hangs for many minutes.
6. **Deploy** — installs kubectl, applies `kube/*.yaml`, creates the
image-pull secret idempotently, then `rollout restart` + `rollout status`.
The rollout restart is required: the image tag in the manifest is
`:latest`, so `kubectl apply` alone does not roll the pods.
7. **Verify the deployed app** — exercises `/healthz` and `/readyz` from
inside the pod, using the pod's own python (there is no `curl` in the
slim image).
> The deploy step does **not** delete the namespace. That would destroy the
> hand-provisioned `beer-db` and `beer-app` Secrets.
### Deploying by hand
If you need to deploy without going through CI — for example to test a
manifest change — do the same thing the workflow does:
```bash
kubectl apply -f kube/beer_namespace.yaml
kubectl apply -f kube/beer-deployment.yaml
kubectl apply -f kube/beer-service.yaml
kubectl -n beer rollout restart deployment/beer-deployment
kubectl -n beer rollout status deployment/beer-deployment --timeout=180s
```
Validate the manifests before applying them. `kubectl apply` **silently
rejects** a manifest with a schema error, so a broken manifest leaves the
old Deployment in place while the job still reports success:
```bash
kubectl apply --dry-run=client -f kube/
```
### Verify the deploy actually took
CI green and pods `Ready` do **not** prove the new code is running. Check the
running digest against the registry:
```bash
# what the pod is running
kubectl -n beer get pod -l app=beer-data \
-o jsonpath='{.items[0].status.containerStatuses[0].imageID}'
# what the registry has as :latest
curl -s -u "armistace:$REG_PASSWORD" -I \
"https://git.aridgwayweb.com/v2/armistace/beer-data/manifests/latest" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
| grep -i docker-content-digest
```
If they differ, the registry `:latest` is stale — re-run the build rather than
re-applying the manifest.
### Check the app is alive
```bash
kubectl -n beer get pods
kubectl -n beer logs deploy/beer-deployment --tail=50
curl -s http://<node>:30015/healthz # -> {"status":"ok"}
curl -s http://<node>:30015/readyz # -> {"status":"ready"}
```
`/healthz` is the **liveness** probe and never touches Mongo — it answers "is
the process up". A database outage must not make the kubelet restart a healthy
process.
`/readyz` is the **readiness** probe and pings Mongo, returning 503 when the
database is unreachable. Wire it to the Service so a pod with no database is
pulled out of the endpoints rather than serving errors:
```yaml
readinessProbe:
httpGet:
path: /readyz
port: 5000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 5000
initialDelaySeconds: 10
periodSeconds: 20
```
## The run metadata collection
The `beer_runs` collection holds one document per brew run with the details
that cannot be derived from the readings: the canonical name, the tap number,
the recorded original gravity and the brew notes.
It starts empty on a fresh install. Populate it from the existing readings:
```bash
# from the app's source tree, with the usual env vars set
python scripts/backfill_run_metadata.py # dry run
python scripts/backfill_run_metadata.py --apply # write it
```
The script is idempotent and only ever inserts into `beer_runs`. It does not
touch `beer_data`.
To see what is in it:
```bash
mongosh "mongodb://root:$MONGO_PASS@192.168.178.229:27017/beer_db" \
--eval 'db.beer_runs.find().pretty()'
```
## Data safety
The `beer_db.beer_data` collection holds the actual brewing records. There is
no migration step and no schema version — documents are read as they are
written, and every field is treated as optional.
To take a backup before any risky change:
```bash
mongodump --uri="mongodb://root:$MONGO_PASS@192.168.178.229:27017/beer_db" \
--out=/tmp/beer_db_backup_$(date +%Y%m%d)
```
## Common problems
### The login page loads but nothing else does
The Mongo connection failed. Check the app logs for an auth error and confirm
the credentials:
```bash
kubectl -n beer logs deploy/beer-deployment --tail=100 | grep -i mongo
```
### A run shows a negative alcohol figure
That was the old formula (`(max - min) / 7.36 + 0.5`) applied to a run whose
current gravity is above the assumed original gravity of 1012. Fixed in the
current version: the standard `(OG - FG) × 131.25` formula is used, and a run
with no known OG shows a dash instead of a figure.
### The pod will not start after a deploy
The most likely cause is a missing `SECRET_KEY` — the app exits rather than
run with a default. Check the logs:
```bash
kubectl -n beer logs deploy/beer-deployment --tail=20
# ImproperlyConfigured: SECRET_KEY is not set. ...
```
### The chart on the run page is blank
The chart library is served from `/static/js/chart.umd.js` — it is vendored in
the repo, not loaded from a CDN, so the container works with no outbound
network. Check the browser console for a 404 on that path.