## What & why Two changes, made and verified together on a real cluster. **S-24 / #25 — a Helm chart for the platform.** One chart, `infra/helm/big-reference`, whose `values.yaml` is a near-literal transcription of `infra/docker-compose.yml`, rendered by three generic templates (Deployment, Job, Service) over a `workloads` map. Adding a service is a values edit. `make k8s-lint` renders and schema-checks the whole stack without a cluster. The issue asked for a *sketch*; this is deployed and verified end to end (see below), which is more than it asked for — the part it asked for that is **not** here is the production-posture write-up (HA, secrets, backup), see Known gaps. **#166 — Caddy replaces nginx in the portals.** nginx resolves a variable `proxy_pass` upstream itself, using only the `resolver` directive and never `/etc/resolv.conf`'s search domains. That had cost two workarounds in one script: rewriting the resolver address for rootless podman, and injecting a full FQDN so the bare `bff` name could resolve on Kubernetes. Caddy dials per request through the system resolver, so `reverse_proxy bff:8080` works on every engine unchanged; `apps/portal-nginx-resolver.sh` and the chart's `BFF_HOST` env are deleted. Closes #25 Closes #166 ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation — twice: the Caddyfile contract test before the Caddyfiles, `make k8s-lint` before the chart. - [x] Implementation makes the test pass. - [x] Conventional Commits referencing the issues (`refs #25` / `refs #166`). - [ ] CI green — awaiting the run on this PR (`make k8s-lint`, `dotnet format` and the new unit self-check pass locally; the compose e2e and mutation lanes are CI's). - [ ] `docker compose up` from a fresh clone reaches green health checks within 3 minutes — the portal images were rebuilt and verified standalone, but a full `make up` run has not been done on this branch. Please confirm in review or let CI's smoke test speak. - [x] Docs updated — `docs/runbooks/kubernetes-talos.md` (new), `frontend-decisions.md`, `demo-script.md`, and the docs that named nginx. - [x] ADR added — ADR-0033 (chart) and ADR-0034 (Caddy). - [ ] Demo note in `docs/demo-script.md` — not added: the deployment target is not a user-visible slice, and the Caddy swap is invisible to the demo script beyond the wording fix included here. ## How it was verified Brought up from scratch on a single-node Talos v1.14.0 VM (6 vCPU / 10 GB, virtio disk) under virt-manager: **29 pods ready and four bootstrap Jobs complete in under three minutes, zero restarts**, using ~4.4 GB of the VM's 10 GB. - Full Common Ground path: portal Caddy → BFF → domain → Flowable → ACL → OpenZaak + Objecten → NRC → event-subscriber → projection → public register (`INGEDIEND`, reference matching the submitted registration). - Werkbak read with an MFA'd medewerker token → 200. - The browser flow driven with Playwright against `http://localhost:30140`: secure context, `crypto.subtle` present, Keycloak form reached, login completed, **no console errors**. - Routing checked against a stub BFF: SPA fallback serves deep links, each portal proxies its own groups, and a portal does *not* proxy a neighbour's group. ## Notes for reviewers Three bugs this shook out, each fixed at the cause rather than the symptom: 1. **`command` vs `args`.** Compose's `command:` replaces the image CMD; Kubernetes' replaces the ENTRYPOINT. Transcribing one to the other broke every upstream image that relies on its entrypoint — postgres refused to run as root, Keycloak tried to exec `start-dev`. The chart now `fail`s at render time on `command`. 2. **Concurrent migrations.** Both `/setup_configuration.sh` and `/start.sh` run `manage.py migrate`; compose serialises them with `depends_on`, Kubernetes has no such edge, so the init Job and its web pod raced (`relation "zgw_consumers_service" already exists`). The four Django services now do both steps in order in the web pod — which also deletes four workloads. 3. **`emptyDir` databases are wiped by any pod-template change.** `make k8s-reseed` now also restarts `event-subscriber` and `projection-api`, which create the projection schema on start and otherwise keep writing to a schema-less database. Known gaps / follow-ups: - **Secrets.** `values.yaml` carries the dev credentials in plain text (`admin/admin`, the ZGW client secret, the two Objecten tokens) and the chart has no `Secret` objects. Fine for a laptop demo, and exactly what #25's "production posture" ADR should address — I suggest a follow-up issue rather than stretching this PR. - **No CI gate for the chart yet.** `make k8s-lint` exists but is not wired into `.gitea/workflows/ci.yaml`, and nothing enforces that the chart and the compose file stay in step. Worth a small follow-up. - **This is two slices in one PR.** They were built and verified together and the diff is entangled (the chart was written against Caddy from the start), so splitting now would mean re-creating an nginx-shaped chart to throw away. Happy to split if you'd rather. - **Rebased onto #161** (merged as #165) rather than merged, to keep the history linear. One conflict, in the `unit:` target where both branches add a self-check line — resolved by keeping both. #161's `infra/host-browser.yml` arrived with `/usr/share/nginx/html/config.json` and is fixed to `/usr/share/caddy/` inside the `feat(portals)` commit, so no commit on this branch leaves that overlay pointing at a path the images no longer have.Reviewed-on: #167
371 lines
16 KiB
Markdown
371 lines
16 KiB
Markdown
# Deploying the stack to a single-node Talos cluster
|
|
|
|
The Helm chart in `infra/helm/big-reference` is a port of `infra/docker-compose.yml`
|
|
(ADR-0033). This runbook is the walkthrough that was actually used to bring the stack up
|
|
on a Talos VM under virt-manager on a laptop, including the parts that bite.
|
|
|
|
Compose remains the CI-canonical stack — `make verify`, the acceptance lane and the
|
|
Playwright e2e all still drive it. Kubernetes is a second deployment target.
|
|
|
|
## 0. What you need
|
|
|
|
On the laptop, four static binaries, all installable to `~/.local/bin` without root:
|
|
|
|
```bash
|
|
curl -sSLo ~/.local/bin/talosctl https://github.com/siderolabs/talos/releases/download/v1.14.0/talosctl-linux-amd64
|
|
curl -sSLo ~/.local/bin/kubectl https://dl.k8s.io/release/v1.37.0/bin/linux/amd64/kubectl
|
|
curl -sSL https://get.helm.sh/helm-v3.16.4-linux-amd64.tar.gz | tar xz -O linux-amd64/helm > ~/.local/bin/helm
|
|
curl -sSL https://github.com/google/go-containerregistry/releases/download/v0.20.2/go-containerregistry_Linux_x86_64.tar.gz | tar xz -O crane > ~/.local/bin/crane
|
|
chmod +x ~/.local/bin/{talosctl,kubectl,helm,crane}
|
|
```
|
|
|
|
Match `talosctl` to the Talos ISO you booted (`talosctl version --insecure -n <ip>` reports
|
|
the server's tag). `crane` is what pushes images to a plain-HTTP registry without a
|
|
root-level Docker daemon change — see §2.
|
|
|
|
**VM sizing.** 6 vCPU / 10 GB RAM / 27 GB disk runs the whole stack with room to spare
|
|
(measured: ~4.4 GB used, 5.4 GB available with all 29 pods up). 4 GB is not enough. The
|
|
chart sets no resource requests or limits on purpose — on a single node the VM's RAM is the
|
|
only budget there is. Resize a stopped VM with:
|
|
|
|
```bash
|
|
virsh -c qemu:///system destroy talos # it's in maintenance mode; nothing is lost
|
|
virsh -c qemu:///system setmaxmem talos 10G --config
|
|
virsh -c qemu:///system setmem talos 10G --config
|
|
virsh -c qemu:///system setvcpus talos 6 --config --maximum
|
|
virsh -c qemu:///system setvcpus talos 6 --config
|
|
```
|
|
|
|
Two addresses matter throughout:
|
|
|
|
| Name | Meaning | Example |
|
|
|---|---|---|
|
|
| `TALOS_HOST` | the VM's IP — used by the browser, `talosctl` and `kubectl` | `192.168.122.33` |
|
|
| `K8S_REGISTRY` | `TALOS_HOST:30500` — the in-cluster registry (§2) | `192.168.122.33:30500` |
|
|
|
|
Find the VM's address with `virsh -c qemu:///system net-dhcp-leases default`.
|
|
|
|
## 1. Install Talos onto the VM
|
|
|
|
### The virt-manager trap
|
|
|
|
virt-manager treats the install ISO as one-shot: on the VM's **first shutdown** it ejects
|
|
the CD and rewrites the boot order to `hd`. A Talos VM booted from `metal-amd64.iso` runs
|
|
entirely in RAM, so the disk is still empty — the next start lands on
|
|
`Boot failed: not a bootable disk`. Put the ISO back before installing:
|
|
|
|
```bash
|
|
virsh -c qemu:///system change-media talos sda /path/to/metal-amd64.iso --config --insert
|
|
virt-xml -c qemu:///system talos --edit --boot cdrom,hd
|
|
virsh -c qemu:///system start talos
|
|
```
|
|
|
|
Wait for the maintenance-mode API, then confirm the install disk's device name — on virtio
|
|
it is `/dev/vda`, and Talos's default selector expects `/dev/sda`:
|
|
|
|
```bash
|
|
talosctl get disks --insecure -n <TALOS_HOST> -e <TALOS_HOST>
|
|
```
|
|
|
|
### Generate the machine config
|
|
|
|
Talos 1.14 moved several v1alpha1 fields into their own config documents. In particular
|
|
`machine.install` is now `UnattendedInstallConfig`, and patching the old field is rejected
|
|
with *"UnattendedInstallConfig config is incompatible with v1alpha1 config"*. Write
|
|
`patch.yaml` as a multi-document patch:
|
|
|
|
```yaml
|
|
machine:
|
|
certSANs:
|
|
- 192.168.122.33
|
|
registries:
|
|
mirrors:
|
|
# The in-cluster registry (§2) speaks plain HTTP.
|
|
"192.168.122.33:30500":
|
|
endpoints:
|
|
- http://192.168.122.33:30500
|
|
---
|
|
apiVersion: v1alpha1
|
|
kind: UnattendedInstallConfig
|
|
provisioning:
|
|
diskSelector:
|
|
match: disk.dev_path == "/dev/vda"
|
|
```
|
|
|
|
```bash
|
|
talosctl gen config big https://<TALOS_HOST>:6443 --output-dir ~/.talos/big --config-patch @patch.yaml
|
|
talosctl apply-config --insecure -n <TALOS_HOST> -e <TALOS_HOST> --file ~/.talos/big/controlplane.yaml
|
|
```
|
|
|
|
Talos installs to the disk and **kexecs straight into the installed system**, so the CD
|
|
boot order doesn't get in the way here. Then point the client at the node and bootstrap:
|
|
|
|
```bash
|
|
talosctl config merge ~/.talos/big/talosconfig
|
|
talosctl config endpoint <TALOS_HOST>
|
|
talosctl config node <TALOS_HOST>
|
|
talosctl bootstrap # wait for `talosctl version` to answer first
|
|
talosctl kubeconfig -f ~/.kube/config
|
|
```
|
|
|
|
A single-node cluster must run workloads on the control plane, or CoreDNS never schedules:
|
|
|
|
```bash
|
|
kubectl taint node --all node-role.kubernetes.io/control-plane-
|
|
```
|
|
|
|
Finally, make a VM restart boot the installed system rather than the ISO (takes effect at
|
|
the next full power cycle):
|
|
|
|
```bash
|
|
virsh -c qemu:///system change-media talos sda --eject --config
|
|
virt-xml -c qemu:///system talos --edit --boot hd
|
|
```
|
|
|
|
## 2. A registry the node can pull from
|
|
|
|
Talos has no Docker daemon and no way to side-load an image, so this repo's images have to
|
|
come from a registry. The registry runs **inside the cluster**, published on NodePort
|
|
30500 (`infra/helm/registry.yaml`):
|
|
|
|
```bash
|
|
make k8s-registry
|
|
```
|
|
|
|
Why in-cluster rather than on the laptop: a laptop-side registry needs an inbound port
|
|
opened on firewalld's `libvirt` zone (`sudo firewall-cmd --zone=libvirt --add-port=5000/tcp`),
|
|
which needs root. Pushing from the laptop *to* the node is outbound and always allowed, and
|
|
the node pulls from its own NodePort. If you do open that port, put a registry on the
|
|
laptop instead and point `K8S_REGISTRY` at `<laptop-ip>:5000` — the mirror patch in §1 has
|
|
an entry ready for it.
|
|
|
|
Its storage is `emptyDir`, so if the registry pod is ever replaced, re-run `make k8s-images`.
|
|
|
|
## 3. Build and push the images
|
|
|
|
```bash
|
|
make k8s-images K8S_REGISTRY=<TALOS_HOST>:30500
|
|
```
|
|
|
|
This builds the nine images with `docker compose build` — same contexts and Dockerfiles as
|
|
compose, no second build definition — then `docker save | crane push --insecure` each one.
|
|
`docker push` is not used: the registry speaks plain HTTP, which the Docker daemon refuses
|
|
without a root-level `insecure-registries` entry, while crane just takes `--insecure`.
|
|
|
|
## 4. Deploy
|
|
|
|
```bash
|
|
make k8s-up TALOS_HOST=<TALOS_HOST> K8S_REGISTRY=<TALOS_HOST>:30500
|
|
```
|
|
|
|
That does two things:
|
|
|
|
1. `make k8s-seed` — creates the ConfigMaps the chart mounts, from the config files that
|
|
already live in this repo (`infra/helm/seed-configmaps.sh`): the four
|
|
`setup_configuration/data.yaml` files, the Keycloak realm exports, the BPMN + DMN, and
|
|
the two bootstrap scripts. Re-run it after editing any of them.
|
|
2. `helm upgrade --install` of the chart into namespace `big`.
|
|
|
|
First bring-up takes a few minutes: the four Django services migrate their databases and
|
|
apply their `setup_configuration`, Flowable creates its schema, and the bootstrap Jobs
|
|
deploy the BPMN/DMN, seed the zaaktype and register the NRC abonnement.
|
|
|
|
```bash
|
|
kubectl -n big get pods -w
|
|
kubectl -n big get jobs # all four must reach COMPLETIONS 1/1
|
|
```
|
|
|
|
The Jobs are the stack's wiring; if one is not complete, the flow is broken somewhere
|
|
specific:
|
|
|
|
| Job | What breaks without it |
|
|
|---|---|
|
|
| `flowable-init` | no `registratie` process, no diploma DMN |
|
|
| `registerrecord-init` | the register has no RegisterRecord objecttype, so writes are refused |
|
|
| `seed-zaaktype` | the ACL can't resolve `BIG-REGISTRATIE`, so no zaak is created |
|
|
| `nrc-subscribe` | register writes never reach the projection — the public register stays empty |
|
|
|
|
## 5. Use it
|
|
|
|
### The portals must be reached over `localhost`
|
|
|
|
The portals' OIDC flow uses PKCE, which needs `crypto.subtle` — and browsers only expose
|
|
that in a **secure context**: HTTPS, or an origin on `localhost`/`127.0.0.1`. A NodePort on
|
|
the VM's IP is neither, so `http://<TALOS_HOST>:30140` fails before it can even build the
|
|
authorize URL:
|
|
|
|
```
|
|
ERROR TypeError: Cannot read properties of undefined (reading 'digest')
|
|
at t.calcHash → t.generateCodeChallenge → t.createUrlCodeFlowAuthorize
|
|
```
|
|
|
|
So deploy with `TALOS_HOST=localhost` — which pins Keycloak's issuer and the portals'
|
|
`config.json` authority to `http://localhost:30180` — and forward the browser-facing
|
|
services to those same ports:
|
|
|
|
```bash
|
|
make k8s-up TALOS_HOST=localhost K8S_REGISTRY=<TALOS_HOST>:30500
|
|
make k8s-portals # stays in the foreground; Ctrl-C stops all five forwards
|
|
```
|
|
|
|
| URL (needs `make k8s-portals`) | What |
|
|
|---|---|
|
|
| `http://localhost:30140` | self-service portal (DigiD) |
|
|
| `http://localhost:30141` | openbaar register (anonymous) |
|
|
| `http://localhost:30142` | behandel portal (medewerker) |
|
|
| `http://localhost:30143` | beheer portal (medewerker) |
|
|
| `http://localhost:30180` | Keycloak (admin/admin) |
|
|
|
|
The port numbers are deliberately the NodePort numbers: Keycloak's issuer is one fixed
|
|
string, so the port the browser uses has to match the one baked into `config.json`.
|
|
|
|
This is the same mechanism `infra/host-browser.yml` uses for the compose stack (which pins
|
|
`localhost:8180`); only the addresses differ.
|
|
|
|
### The admin UIs work straight off the NodePorts
|
|
|
|
These are server-rendered and need no secure context, so they are reachable at the VM's
|
|
address with no forwarding:
|
|
|
|
| URL | What |
|
|
|---|---|
|
|
| `http://<TALOS_HOST>:30000` | OpenZaak admin (admin/admin) |
|
|
| `http://<TALOS_HOST>:30001` | Open Notificaties admin (admin/admin) |
|
|
| `http://<TALOS_HOST>:30020` / `:30021` | Objecttypen / Objecten admin |
|
|
| `http://<TALOS_HOST>:30080` | BFF (`/health`) |
|
|
| `http://<TALOS_HOST>:30090` | Flowable REST (rest-admin/test) |
|
|
|
|
### Credentials
|
|
|
|
Log in with the test users from `docs/synthetic-data.md` (all password `test123`, e.g.
|
|
`jan-burger` for self-service, `merel-behandelaar` for behandel). The `medewerker` realm
|
|
enforces MFA (ADR-0031) — print a current code with
|
|
`python3 infra/keycloak/check_realms.py otp`. Walk the flow in `docs/demo-script.md`.
|
|
|
|
`TALOS_HOST` is not cosmetic: it pins Keycloak's issuer (`KC_HOSTNAME`) and the portals'
|
|
OIDC authority to the same string, which is what makes a browser token pass the BFF's
|
|
validation (ADR-0010). Change it and you must re-run `make k8s-up` — the chart rolls the
|
|
portals for you, because their `config.json` is a subPath mount and would otherwise keep
|
|
serving the old authority.
|
|
|
|
### Smoke-test the whole chain without a browser
|
|
|
|
With the forwards running:
|
|
|
|
```bash
|
|
TOK=$(curl -s -X POST http://localhost:30180/realms/digid/protocol/openid-connect/token \
|
|
-d grant_type=password -d client_id=big-portal \
|
|
-d username=jan-burger -d password=test123 -d scope=openid | jq -r .access_token)
|
|
|
|
# through the portal's Caddy, so this also proves the BFF reverse proxy
|
|
curl -s -X POST http://localhost:30140/self-service/registrations \
|
|
-H "Authorization: Bearer $TOK" -H 'Content-Length: 0'
|
|
# → {"registrationId":"…","status":"Ingediend"}
|
|
|
|
curl -s http://localhost:30141/openbaar/register
|
|
# → [{"id":"…","status":"INGEDIEND","reference":"<the registrationId>"}]
|
|
```
|
|
|
|
The second call proves the whole Common Ground path: portal → BFF → domain → Flowable →
|
|
ACL → OpenZaak + Objecten → NRC → event-subscriber → projection → openbaar register.
|
|
|
|
## 6. Keeping the databases (recommended if you iterate on the chart)
|
|
|
|
By default every database is an `emptyDir`: no CSI driver needed, and the data lives as
|
|
long as the pod. Note what that means in practice — **any** change to a database pod's
|
|
template (an image policy, an env value, a probe) recreates the pod and wipes it. The stack
|
|
then needs its bootstrap re-run:
|
|
|
|
```bash
|
|
make k8s-reseed TALOS_HOST=... K8S_REGISTRY=...
|
|
```
|
|
|
|
which re-runs the four Jobs *and* restarts `event-subscriber` + `projection-api`, because
|
|
those two create the projection schema on start and otherwise keep writing to a
|
|
schema-less database (`relation "processed_notifications" does not exist`). For persistence, install Rancher's local-path-provisioner — on Talos it
|
|
must write under `/var` and its namespace needs the privileged Pod Security label:
|
|
|
|
```yaml
|
|
# kustomization.yaml
|
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
kind: Kustomization
|
|
resources:
|
|
- github.com/rancher/local-path-provisioner/deploy?ref=v0.0.31
|
|
patches:
|
|
- patch: |-
|
|
kind: ConfigMap
|
|
apiVersion: v1
|
|
metadata:
|
|
name: local-path-config
|
|
namespace: local-path-storage
|
|
data:
|
|
config.json: |-
|
|
{ "nodePathMap":[ { "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths":["/var/local-path-provisioner"] } ] }
|
|
- patch: |-
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: local-path-storage
|
|
labels:
|
|
pod-security.kubernetes.io/enforce: privileged
|
|
```
|
|
|
|
```bash
|
|
kubectl apply -k .
|
|
make k8s-up TALOS_HOST=... K8S_REGISTRY=... K8S_SET='--set persistence.storageClass=local-path'
|
|
```
|
|
|
|
The PVCs carry `helm.sh/resource-policy: keep`, so `make k8s-down` leaves the data behind;
|
|
`make k8s-purge` drops the namespace and with it the volumes.
|
|
|
|
## 7. Day-to-day
|
|
|
|
```bash
|
|
make k8s-lint # render + schema-check the chart, no cluster needed
|
|
make k8s-portals # forward the portals + Keycloak to localhost (browser access)
|
|
make k8s-images K8S_REGISTRY=... # after changing a service or a portal
|
|
make k8s-up TALOS_HOST=... K8S_REGISTRY=...
|
|
make k8s-seed # after editing a data.yaml, a realm export, or the BPMN
|
|
make k8s-reseed TALOS_HOST=... K8S_REGISTRY=... # re-run the bootstrap Jobs + reset the projection schema
|
|
make k8s-down # uninstall, keep the database PVCs
|
|
make k8s-purge # uninstall and drop the namespace
|
|
```
|
|
|
|
This repo's images are pulled with `imagePullPolicy: Always` (the `dev` tag is mutable), so
|
|
`kubectl -n big rollout restart deploy/<name>` after `make k8s-images` picks up a rebuild.
|
|
Upstream images stay `IfNotPresent`: their tags are pinned, and keeping them out of the pod
|
|
template avoids needless churn — a changed template makes a Job unpatchable.
|
|
|
|
`k8s-reseed` is also the path for *changing* a Job in the chart: a Job's pod template is
|
|
immutable, so `helm upgrade` is rejected with `cannot patch "…" with kind Job`.
|
|
|
|
## 8. When it doesn't work
|
|
|
|
| Symptom | Cause |
|
|
|---|---|
|
|
| `Boot failed: not a bootable disk` | virt-manager ejected the install ISO on first shutdown — see §1 |
|
|
| The VM comes back in maintenance mode after a restart | the ISO is still attached and boots first; eject it and set `--boot hd` (§1) |
|
|
| `apply-config` rejects the patch with *"incompatible with v1alpha1"* | Talos ≥1.14 owns that field in its own config document — patch the document, not `machine.*` (§1) |
|
|
| CoreDNS `Pending` forever | the control-plane taint is still on the only node (§1) |
|
|
| `ImagePullBackOff` … `pull QPS exceeded` | transient: the kubelet rate-limits pulls when ~30 pods start at once. It recovers on retry |
|
|
| `ImagePullBackOff` on a `register-referentie/*` image | the registry mirror patch is missing: `talosctl get registriesconfig` |
|
|
| Pod stuck in `ContainerCreating`, event names a ConfigMap | `make k8s-seed` |
|
|
| `seed-zaaktype` retrying | publishing a zaaktype validates the resultaattype against `selectielijst.openzaak.nl`, so this one Job needs outbound internet from the VM (ADR-0006) |
|
|
| `TypeError: Cannot read properties of undefined (reading 'digest')` on a portal | not a secure context: `crypto.subtle` is absent on `http://<ip>`. Use `localhost` + `make k8s-portals` (§5) |
|
|
| Login redirects but the portal stays logged out, or the BFF answers 401 | `TALOS_HOST` doesn't match the address in the browser's URL bar — issuer mismatch. Re-run `make k8s-up` with the right value |
|
|
| A portal returns 502 on `/self-service/…` | the BFF is unreachable from the portal pod: check `kubectl -n big get svc bff` and the BFF's own readiness |
|
|
| Public register empty after a submit | usually a wiped `emptyDir` database (§6): `make k8s-reseed`. Confirm with `kubectl -n big logs deploy/event-subscriber \| grep 42P01` |
|
|
| `helm upgrade` fails with `cannot patch … with kind Job` | see §7 — use `make k8s-reseed` |
|
|
| Pods `Evicted` / `OOMKilled` | the VM is too small (§0) |
|
|
| A Job shows `BackoffLimitExceeded` | read it: `kubectl -n big logs job/<name>` |
|
|
|
|
## What is not ported
|
|
|
|
- **Observability** (Tempo, Prometheus, Grafana) is defined but disabled — those are built
|
|
images too, so switching them on means pushing them as well:
|
|
`K8S_SET='--set workloads.tempo.enabled=true --set workloads.prometheus.enabled=true --set workloads.grafana.enabled=true'`.
|
|
The .NET services still export OTLP; the exporter fails harmlessly when Tempo is absent.
|
|
- **The verify/e2e lanes.** `make verify*` and the Playwright e2e drive compose, not the
|
|
chart. The Kubernetes path is verified with §5's smoke test.
|
|
- **Ingress, TLS, and resource requests.** See the ponytail ceiling in ADR-0033.
|