Implement app deployment improvements
This commit is contained in:
54
docs/django-on-railiance.md
Normal file
54
docs/django-on-railiance.md
Normal 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.
|
||||
@@ -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
|
||||
|
||||
48
docs/gitea-package-registry.md
Normal file
48
docs/gitea-package-registry.md
Normal 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
43
docs/operator-recipes.md
Normal 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
62
docs/operator-setup.md
Normal 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.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user