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.
7.9 KiB
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:
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 raisesImproperlyConfiguredand 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:
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:
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:
- Checkout
- Create Kubeconfig — writes
secrets.KUBEC_CONFIG_BUILDX_NEW_2to$HOME/.kube/config. This is FIRST because the buildxkubernetesdriver needs it to create its builder pod in the cluster. - Set up Docker Buildx —
driver: kubernetes,namespace=gitea-runner. - Login to the registry —
secrets.REG_PASSWORD. - Build and Push —
linux/amd64only. 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. - Deploy — installs kubectl, applies
kube/*.yaml, creates the image-pull secret idempotently, thenrollout restart+rollout status. The rollout restart is required: the image tag in the manifest is:latest, sokubectl applyalone does not roll the pods. - Verify the deployed app — exercises
/healthzand/readyzfrom inside the pod, using the pod's own python (there is nocurlin the slim image).
The deploy step does not delete the namespace. That would destroy the hand-provisioned
beer-dbandbeer-appSecrets.
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:
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:
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:
# 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
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:
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:
# 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:
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:
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:
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:
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.