Implement app deployment improvements

This commit is contained in:
2026-05-22 22:25:40 +02:00
parent 60a9e37a86
commit 934770cb68
15 changed files with 552 additions and 25 deletions

View File

@@ -0,0 +1,54 @@
# Django on Railiance
This is the short recipe for Django workloads running as S5 apps on the
Railiance cluster.
## Probe Host Header
Production Django usually runs with `DEBUG=False` and a narrow
`ALLOWED_HOSTS`. Kubelet HTTP probes do not use the public app host by
default; they send the pod IP as the `Host` header. Django rejects that
before the request reaches the health view, so kubelet sees `HTTP 400`
and restarts an otherwise healthy pod.
Set the probe `Host` header to a value that is also present in
`ALLOWED_HOSTS`:
```yaml
env:
ALLOWED_HOSTS: app.example.org,localhost
probes:
enabled: true
path: /health/
port: 8000
hostHeader: app.example.org
```
The `charts/vergabe-teilnahme` chart already renders this as:
```yaml
httpGet:
path: /health/
port: 8000
httpHeaders:
- name: Host
value: app.example.org
```
When changing the public hostname or `ALLOWED_HOSTS`, change
`probes.hostHeader` in the same review.
## Database URL Secrets
CNPG-generated role passwords may contain URL-reserved characters such
as `=`, `+`, and `/`. If the app consumes `DATABASE_URL`, URL-encode the
password when building the env Secret:
```bash
make vergabe-db-url-secret
```
For new charts, prefer either this helper or separate PostgreSQL env
vars (`POSTGRES_HOST`, `POSTGRES_USER`, `POSTGRES_PASSWORD`,
`POSTGRES_DB`) so there is no URL parser in the path.

View File

@@ -9,8 +9,8 @@ HTTPS.
Registry-specific Gitea settings are carried in
`helm/gitea-registry-values.yaml`, a non-secret overlay applied after the SOPS
values file by `make gitea-deploy`. It explicitly enables packages, permits
container uploads without an app-level size cap, clears globally disabled repo
units, and moves `ROOT_URL` to the HTTPS host.
container and PyPI uploads without an app-level size cap, clears globally
disabled repo units, and moves `ROOT_URL` to the HTTPS host.
Image names should use the Gitea owner and package path:
@@ -60,6 +60,12 @@ kubectl create secret docker-registry gitea-registry \
Reference it from workloads as `imagePullSecrets: [{name: gitea-registry}]`.
## Python Packages
The same Gitea package service is used for Python wheels. See
`docs/gitea-package-registry.md` for the publish/install recipe and the
`issue-core` migration notes from `RAILIANCE-WP-0004 I03`.
## Current Storage Notes
The live Gitea pod mounts `gitea-shared-storage` at `/data`; package blobs land

View File

@@ -0,0 +1,48 @@
# Gitea Package Registry
Gitea package support is enabled by `helm/gitea-registry-values.yaml`.
That overlay is applied after the encrypted base values by
`make gitea-deploy` and enables both container packages and Python
packages.
## Python Packages
Publish Python wheels to the organization package endpoint:
```bash
python -m build
TWINE_USERNAME=<gitea-user> \
TWINE_PASSWORD=<package-token> \
python -m twine upload \
--repository-url https://gitea.coulomb.social/api/packages/coulomb/pypi \
dist/*
```
Install from the simple index:
```bash
pip install \
--extra-index-url https://<gitea-user>:<package-token>@gitea.coulomb.social/api/packages/coulomb/pypi/simple/ \
issue-core
```
For CI, store the token as a secret and inject it into the package index
URL at build time. Do not commit tokenized index URLs.
## issue-core Migration
The portable deployment path for `vergabe-teilnahme` is:
1. Release `issue-core` from its source repo as a wheel, for example
`0.2.0`.
2. Publish the wheel to the Gitea Python package registry.
3. Change `vergabe-teilnahme/pyproject.toml` from the local path
dependency to `issue-core>=0.2,<0.3`.
4. Remove the Docker BuildKit `--build-context issue-core=...`
requirement from image-build instructions.
5. Build the image on a clean runner that has no sibling `issue-core`
checkout.
Only the registry enablement lives in `railiance-apps`; the package
release and application dependency change belong to the `issue-core` and
`vergabe-teilnahme` repos.

43
docs/operator-recipes.md Normal file
View File

@@ -0,0 +1,43 @@
# Operator Recipes
## Service-IP Smoke Checks
Avoid one-shot `kubectl run --rm -i` probes for service connectivity.
The container can exit before the connection result is reliable, which
creates false negatives during rollout debugging.
Use a persistent pod, wait for readiness, then exec the probe:
```bash
NAMESPACE=vergabe-teilnahme \
tools/smoke-service.sh http://vergabe-teilnahme.vergabe-teilnahme.svc/health/
```
Reuse the same pod for a debugging session:
```bash
NAMESPACE=vergabe-teilnahme POD_NAME=service-smoke \
tools/smoke-service.sh http://vergabe-teilnahme.vergabe-teilnahme.svc/health/
```
Clean it up when finished:
```bash
kubectl delete pod service-smoke -n vergabe-teilnahme
```
Or set `CLEANUP=true` for a single checked run.
## Manifest Server Dry-Run
Schema drift in live CRDs is caught by server-side dry-run, not by Helm
rendering alone:
```bash
make k8s-server-dry-run
```
The command expects a representative Kubernetes API server with the same
CRDs as the Railiance cluster. CI should run it against a disposable kind
cluster seeded with CNPG, cert-manager, Traefik, and any other CRDs used
by changed manifests.

62
docs/operator-setup.md Normal file
View File

@@ -0,0 +1,62 @@
# Operator Setup
Run these checks before deploying or rotating any S5 workload:
```bash
make check-tools
make check-sops
```
## Required Tools
- `kubectl`
- `helm`
- `sops`
- `python3`
Install the CNPG plugin for better database diagnostics:
```bash
kubectl krew install cnpg
```
`make check-tools` fails when required tools are missing and warns when
`kubectl cnpg` is unavailable. The Makefile status targets fall back to
plain Kubernetes resources, but the plugin output is the preferred view
for primary/replica health and backup state.
## SOPS Age Key Bootstrap
SOPS-encrypted values in this repo expect an age identity at:
```text
~/.config/sops/age/keys.txt
```
Bootstrap procedure:
1. Receive the operator age identity through an out-of-band channel.
2. Create the directory with owner-only permissions:
```bash
mkdir -p ~/.config/sops/age
chmod 700 ~/.config/sops ~/.config/sops/age
```
3. Write the identity to `~/.config/sops/age/keys.txt`.
4. Restrict the file:
```bash
chmod 600 ~/.config/sops/age/keys.txt
```
5. Verify decryption:
```bash
make check-sops
```
Do not commit age identities, decrypted values, or copied SOPS plaintext
to this repo.
## Rotation
To rotate access, add the new recipient to the relevant SOPS files,
re-encrypt, verify with both old and new operators, then remove the old
recipient in a separate change. Keep at least one known-good recovery
operator key available during the transition.

View File

@@ -36,13 +36,9 @@ K8s Secrets, not in committed values files.
2. Mirror the new password into `vergabe-teilnahme/vergabe-app-credentials`.
3. Rebuild `DATABASE_URL` in `vergabe-teilnahme-env`, **URL-encoding
the password** (the base64 character set breaks the URL parser
otherwise see `RAILIANCE-WP-0004 I01`):
otherwise - see `RAILIANCE-WP-0004 I01`):
```bash
PW=$(kubectl get secret vergabe-app-credentials -n vergabe-teilnahme -o jsonpath='{.data.password}' | base64 -d)
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$PW")
kubectl patch secret vergabe-teilnahme-env -n vergabe-teilnahme \
--type=merge \
-p "{\"stringData\":{\"DATABASE_URL\":\"postgresql://vergabe:$ENCODED@apps-pg-rw.databases:5432/vergabe_db\"}}"
make vergabe-db-url-secret
kubectl rollout restart deploy/vergabe-teilnahme -n vergabe-teilnahme
```
@@ -103,6 +99,7 @@ Most likely the probe's `Host` header doesn't match
vergabe-teilnahme.whywhynot.de` precisely to avoid this — if you
change `ALLOWED_HOSTS` in values, also update `probes.hostHeader`.
Symptom in `kubectl logs`: kube-probe requests returning HTTP 400.
See `docs/django-on-railiance.md` for the reusable pattern.
### `dj-database-url` error: "The database name 'XYZ...' is longer than 63 characters"
@@ -156,4 +153,8 @@ configuration belongs to `railiance-platform`).
- Improvements backlog: `workplans/railiance-apps-WP-0004-app-deployment-improvements.md`
- Shared DB cluster: `railiance-platform/docs/apps-pg.md`
- Container registry: `docs/gitea-container-registry.md`
- Python package registry: `docs/gitea-package-registry.md`
- Django deployment recipe: `docs/django-on-railiance.md`
- Operator setup: `docs/operator-setup.md`
- Operator recipes: `docs/operator-recipes.md`
- App source: https://gitea.coulomb.social/coulomb/vergabe-teilnahme