diff --git a/K8S.md b/K8S.md index 754c2622..0e6b3152 100644 --- a/K8S.md +++ b/K8S.md @@ -1,59 +1,56 @@ # Deploying dynamicweb on Kubernetes -[k8s/dynamicweb.yaml](k8s/dynamicweb.yaml) describes what runs: the web app, the Celery worker, -Redis, a volume for uploaded media, and an Ingress. PostgreSQL 18 is **not** included: use your -own database and point the app at it. +The Helm chart in [charts/dynamicweb/](charts/dynamicweb/) runs the web app, a Celery worker, +Redis, a volume for uploaded media and an Ingress, and runs the database migrations as a Job +before each install or upgrade. PostgreSQL 18 is **not** in the chart: bring your own (an +operator such as CloudNativePG, a managed service, or any Postgres) and point the app at it. -> Tested on a local `kind` cluster with a scratch Postgres (migrate Job, then web and the admin -> answer 200). Not tested on your cluster, ingress controller or real data. +> Tested on a local `kind` cluster with a scratch Postgres. Not tested on your cluster, your +> ingress controller or real data. ## Steps -1. **Push the image** (build it once, from the repo): +1. **Push the image** (built once from the repo): ```sh docker compose build docker tag dynamicweb:latest /dynamicweb: docker push /dynamicweb: ``` -2. **Make an env file** from `.env.docker` with your real values. Change at least: +2. **Database.** Create an empty database and user. To keep old data, restore the dump into it now + (`psql -h -U < dump.sql`). The Job in step 5 migrates it to the new schema. +3. **Env file and Secret.** Copy `.env.docker`, set your real values, then store it: ``` DEBUG=False SEED_DUMMY_DATA=False DJANGO_SECRET_KEY= - POSTGRES_HOST= # and POSTGRES_PORT/DB/USER/PASSWORD + POSTGRES_HOST= POSTGRES_PORT=5432 POSTGRES_DB / POSTGRES_USER / POSTGRES_PASSWORD BEHIND_TLS_PROXY=True # TLS ends at the ingress UNGLEICH_SITE_CONFIGS= ``` - plus your OpenNebula, LDAP, Stripe, email and reCAPTCHA values. Leave the Redis URLs alone; - the manifest creates a service called `redis`. -3. **Store it as a Secret:** + plus your OpenNebula, LDAP, Stripe, email and reCAPTCHA values. Leave the Redis URLs alone. ```sh kubectl create secret generic dynamicweb --from-env-file= ``` -4. **Edit the manifest:** your image name (it appears three times) and the Ingress hosts (one rule - for each domain, with `www.` variants). Set `ingressClassName` to your controller. -5. **Migrate, then start.** If you have old data, restore it into the empty database first (for - example `psql -h -U < dump.sql`). Then: +4. **Values.** Edit `image.repository`, `image.tag` and `ingress.hosts` (one per domain, with `www.` + variants) in [charts/dynamicweb/values.yaml](charts/dynamicweb/values.yaml), or pass them with `--set`. +5. **Install.** Helm starts the pods and runs the migrate Job (on upgrades, before the rollout). The site answers once the Job completes: ```sh - kubectl apply -f k8s/dynamicweb.yaml - kubectl logs -f job/dynamicweb-migrate # takes a few minutes; ends when the Job completes - kubectl rollout restart deploy/dynamicweb-web - kubectl logs -f deploy/dynamicweb-web # ready at "Listening at" + helm install dynamicweb charts/dynamicweb + kubectl logs -f job/dynamicweb-migrate # a few minutes + kubectl logs -f deploy/dynamicweb-web # ready at "Listening at" ``` - The `dynamicweb-migrate` Job runs the migrations once. The web pods don't migrate, so restart - them after the Job completes (a web pod that started earlier serves errors until you do). -6. **Check:** `curl -I -H 'Host: ungleich.ch' http:///en-us/` returns 200. Admin - is at `/en-us/admin/login/` on any site's address. +6. **Check:** `curl -I -H 'Host: ungleich.ch' http:///en-us/` returns 200. Admin is + at `/en-us/admin/login/` on any site's address. ## Good to know -- **Scaling:** `kubectl scale deploy/dynamicweb-web --replicas=2`, with the media volume changed - to `ReadWriteMany` so every pod sees the same uploads. -- **Ingress must keep the original `Host` header** (the default). The site is chosen from the - host, and every domain must be in `UNGLEICH_SITE_CONFIGS` and in `ALLOWED_HOSTS` - (`dynamicweb/settings/prod.py` lists the current ones). -- **Updating:** push a new image tag, change the image in the manifest, delete the old Job - (`kubectl delete job dynamicweb-migrate`) and apply again, then restart web. -- **Logs:** `kubectl logs deploy/dynamicweb-web` and `kubectl logs deploy/dynamicweb-celery`. -- **Don't name a Service `postgres` without `enableServiceLinks: false`.** Kubernetes would inject - `POSTGRES_PORT=tcp://...` and the app crashes. The manifest already sets it on every pod. +- **Update:** push a new tag, then `helm upgrade dynamicweb charts/dynamicweb --set image.tag=`. + The migrate Job runs again first. +- **Scale web:** `--set web.replicas=2 --set media.accessMode=ReadWriteMany` (every pod must see the + same uploads, so the storage class has to support it). +- **Keep the original `Host` header** at the ingress (the default). The site is chosen from the host, + and every domain must be in `UNGLEICH_SITE_CONFIGS` and `ALLOWED_HOSTS` (`dynamicweb/settings/prod.py`). +- **The Secret must exist before `helm install`** (the Job reads it). +- **Logs:** `kubectl logs deploy/dynamicweb-web`, `deploy/dynamicweb-celery`. +- **Why a database Service named `postgres` is safe:** the pods set `enableServiceLinks: false`; + otherwise Kubernetes injects `POSTGRES_PORT=tcp://...` and the app crashes.