k8s: work with kubectl apply -f as well as -k

- The secrets template moves to k8s/secrets/, and the filled-in copy to
  k8s/secrets/secrets.yaml (gitignored). `kubectl apply -f k8s/` does
  not recurse, so neither the placeholder template nor the real secrets
  can be applied by accident.
- The server image digest is pinned in server.yaml instead of through
  kustomize's images override, which `-f` ignores (it would have pulled
  a nonexistent :latest).
- README and kustomization.yaml say to use `-k`: plain `-f` never
  creates the generated Grafana ConfigMaps.
- The IngressRoutes drop `tls: {}`, like the other routes on this
  Traefik: TLS is configured on the websecure entrypoint.

Manifests validated both ways (14 resources rendered with kustomize,
11 as plain files — the difference is the Grafana ConfigMaps).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
2026-10-17 21:36:17 +02:00
co-authored by Claude Opus 5.5
parent 46dccee1de
commit 84fb3aac21
7 changed files with 26 additions and 24 deletions
+2 -1
View File
@@ -40,5 +40,6 @@ mise.local.toml
/EVHardware/.gradle /EVHardware/.gradle
/EVHardware/**/build /EVHardware/**/build
# Filled-in Kubernetes Secrets (copy of k8s/secrets.example.yaml). Never commit. # Filled-in Kubernetes Secrets (copy of k8s/secrets/secrets.example.yaml). Never commit.
/k8s/secrets.yaml /k8s/secrets.yaml
/k8s/secrets/secrets.yaml
+3 -2
View File
@@ -40,7 +40,7 @@ car ──live telemetry───▶ car.k8s.stinnesbeck.com (Go) ──▶ ABRP
- [x] Manifests validated offline (including Traefik CRDs). **Not applied.** - [x] Manifests validated offline (including Traefik CRDs). **Not applied.**
- [x] Car app 2.4.0 (pairing by QR): unit tests pass; **not yet tried on the car**. - [x] Car app 2.4.0 (pairing by QR): unit tests pass; **not yet tried on the car**.
- [x] Image `mg4-server:2.1.0` (with pairing) pushed to git.stinnesbeck.com (public; pinned by - [x] Image `mg4-server:2.1.0` (with pairing) pushed to git.stinnesbeck.com (public; pinned by
digest `sha256:e5f6e2d2…` in `k8s/kustomization.yaml`). digest `sha256:e5f6e2d2…` in `k8s/server.yaml`).
- [ ] Check Grafana's host in `k8s/ingress.yaml` (placeholder `car-grafana.k8s.stinnesbeck.com`). - [ ] Check Grafana's host in `k8s/ingress.yaml` (placeholder `car-grafana.k8s.stinnesbeck.com`).
- [ ] First pairing and drive with 2.4.0: telemetry arrives in InfluxDB, Grafana and ABRP. - [ ] First pairing and drive with 2.4.0: telemetry arrives in InfluxDB, Grafana and ABRP.
@@ -89,7 +89,8 @@ car ──live telemetry───▶ car.k8s.stinnesbeck.com (Go) ──▶ ABRP
app 2.4.0 shows the QR code and collects its token. Tested end to end against InfluxDB. app 2.4.0 shows the QR code and collects its token. Tested end to end against InfluxDB.
- 2026-10-17 — Incident: a commit swept the filled-in `secrets.example.yaml` (InfluxDB admin token, - 2026-10-17 — Incident: a commit swept the filled-in `secrets.example.yaml` (InfluxDB admin token,
INGEST_TOKEN) into git and pushed it. Commit amended and force-pushed; both values regenerated in INGEST_TOKEN) into git and pushed it. Commit amended and force-pushed; both values regenerated in
the now gitignored `k8s/secrets.yaml`. Filled-in secrets live only there from now on. the now gitignored `k8s/secrets.yaml` (since moved to `k8s/secrets/secrets.yaml`, outside
`kubectl apply -f k8s/`). Filled-in secrets live only there from now on.
- 2026-10-17 — Temporary on-screen diagnostics (b2a5b32) to debug blank values without adb; - 2026-10-17 — Temporary on-screen diagnostics (b2a5b32) to debug blank values without adb;
removed again (1002962) once values showed on the car. Revert 1002962 to bring them back. removed again (1002962) once values showed on the car. Revert 1002962 to bring them back.
+11 -8
View File
@@ -11,19 +11,19 @@ is applied automatically. Apply them yourself.
| `grafana.yaml` | Grafana 13.2.3 Deployment (1Gi PVC), Service; datasource and dashboards provisioned | | `grafana.yaml` | Grafana 13.2.3 Deployment (1Gi PVC), Service; datasource and dashboards provisioned |
| `grafana/` | The provisioned InfluxDB datasource and the MG4 dashboards (Live, Consumption, Charging) | | `grafana/` | The provisioned InfluxDB datasource and the MG4 dashboards (Live, Consumption, Charging) |
| `ingress.yaml` | Traefik `IngressRoute`s for car.k8s.stinnesbeck.com and Grafana, TLS from Traefik's default certificate | | `ingress.yaml` | Traefik `IngressRoute`s for car.k8s.stinnesbeck.com and Grafana, TLS from Traefik's default certificate |
| `secrets.example.yaml` | **Template** for the Secrets; placeholders only, not in `kustomization.yaml` | | `secrets/secrets.example.yaml` | **Template** for the Secrets; placeholders only, not in `kustomization.yaml` |
| `secrets.yaml` | Your filled-in copy — **gitignored**, never committed | | `secrets/secrets.yaml` | Your filled-in copy — **gitignored**, never committed |
| `kustomization.yaml` | Everything except the Secrets; pins the server image, generates the Grafana ConfigMaps | | `kustomization.yaml` | Everything except the Secrets; generates the Grafana ConfigMaps |
## Before the first apply ## Before the first apply
1. **Check `ingress.yaml`:** Grafana's host (placeholder `car-grafana.k8s.stinnesbeck.com`). 1. **Check `ingress.yaml`:** Grafana's host (placeholder `car-grafana.k8s.stinnesbeck.com`).
Both routes use the `traefik-internal` class, and both hosts must be Both routes use the `traefik-internal` class, and both hosts must be
covered by Traefik's default certificate. covered by Traefik's default certificate.
2. **Create the Secrets.** `cp k8s/secrets.example.yaml k8s/secrets.yaml` (gitignored), replace 2. **Create the Secrets.** `cp k8s/secrets/secrets.example.yaml k8s/secrets/secrets.yaml` (gitignored), replace
every `CHANGE-ME` there (`openssl rand -hex 32`): `influxdb-auth`, `mg4-server` (ingest token, every `CHANGE-ME` there (`openssl rand -hex 32`): `influxdb-auth`, `mg4-server` (ingest token,
approval password for pairing, your ABRP API key and user token) and `grafana-admin`. Keep approval password for pairing, your ABRP API key and user token) and `grafana-admin`. Keep
`secrets.example.yaml` itself on placeholders only. the template itself on placeholders only.
3. **Registry access.** The `mg4-server` package on git.stinnesbeck.com is public, so no pull 3. **Registry access.** The `mg4-server` package on git.stinnesbeck.com is public, so no pull
secret is needed. If you make it private, add an `imagePullSecrets` entry to `server.yaml`. secret is needed. If you make it private, add an `imagePullSecrets` entry to `server.yaml`.
@@ -31,10 +31,13 @@ is applied automatically. Apply them yourself.
```sh ```sh
kubectl apply -f k8s/namespace.yaml kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/secrets.yaml kubectl apply -f k8s/secrets/secrets.yaml
kubectl apply -k k8s/ kubectl apply -k k8s/
``` ```
Use **`-k`**, not `-f k8s/`: plain `-f` applies the files without kustomize, so the Grafana
ConfigMaps (datasource and dashboards) are never created and Grafana does not start.
## Notes ## Notes
- InfluxDB's setup mode runs only on an empty volume. Changing `admin-token` in the Secret later - InfluxDB's setup mode runs only on an empty volume. Changing `admin-token` in the Secret later
@@ -43,5 +46,5 @@ kubectl apply -k k8s/
for Grafana, read/write for the server) can replace it later. for Grafana, read/write for the server) can replace it later.
- The dashboards are read-only in Grafana's UI. Change the JSON in `grafana/dashboards/`; the - The dashboards are read-only in Grafana's UI. Change the JSON in `grafana/dashboards/`; the
ConfigMap name changes with its content, which rolls Grafana. ConfigMap name changes with its content, which rolls Grafana.
- The server image is pinned by digest in `kustomization.yaml`. To upgrade, push a new tag and - The server image is pinned by digest in `server.yaml`. To upgrade, push a new tag and put its
put its digest there (`skopeo inspect docker://git.stinnesbeck.com/nils/mg4-server:<tag>`). digest there (`skopeo inspect docker://git.stinnesbeck.com/nils/mg4-server:<tag>`).
+3 -4
View File
@@ -1,5 +1,6 @@
# Routes on the traefik-internal class, like the rest of *.k8s.stinnesbeck.com. TLS uses # Routes on the traefik-internal class, like the rest of *.k8s.stinnesbeck.com. No tls section
# Traefik's default certificate, so there is no per-host Certificate here. # and no per-host Certificate: TLS is configured on Traefik's websecure entrypoint, which serves
# the default certificate.
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
kind: IngressRoute kind: IngressRoute
metadata: metadata:
@@ -18,7 +19,6 @@ spec:
name: mg4-server name: mg4-server
namespace: mg4 namespace: mg4
port: 80 port: 80
tls: {}
--- ---
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
kind: IngressRoute kind: IngressRoute
@@ -39,4 +39,3 @@ spec:
name: grafana name: grafana
namespace: mg4 namespace: mg4
port: 80 port: 80
tls: {}
+2 -6
View File
@@ -1,5 +1,5 @@
# Render with `kustomize build k8s/` (or `kubectl kustomize k8s/`) and apply it yourself. # Apply with `kubectl apply -k k8s/` — not `-f k8s/`, which skips the Grafana ConfigMaps below.
# The Secrets are not included: create them from secrets.example.yaml first. # The Secrets are not included: create them from secrets/secrets.example.yaml first.
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
@@ -8,10 +8,6 @@ resources:
- server.yaml - server.yaml
- grafana.yaml - grafana.yaml
- ingress.yaml - ingress.yaml
images:
- name: git.stinnesbeck.com/nils/mg4-server
# 2.1.0, pinned by digest so a re-pushed tag cannot change what runs.
digest: sha256:e5f6e2d2ef9dc2650055a0823a3f86d8903e46918a76e990540482edf819766d
generatorOptions: generatorOptions:
labels: labels:
app.kubernetes.io/part-of: mg4 app.kubernetes.io/part-of: mg4
+3 -2
View File
@@ -1,5 +1,6 @@
# TEMPLATE ONLY — not part of kustomization.yaml. Copy it to k8s/secrets.yaml (gitignored), # TEMPLATE ONLY — not part of kustomization.yaml. Copy it to k8s/secrets/secrets.yaml
# fill in real values there, and apply that file yourself. Never put real values in here. # (gitignored), fill in real values there, and apply that file yourself. Never put real values
# in here. This folder is outside `kubectl apply -f k8s/`, which does not recurse.
# #
# openssl rand -hex 32 for every token and password below # openssl rand -hex 32 for every token and password below
apiVersion: v1 apiVersion: v1
+2 -1
View File
@@ -29,7 +29,8 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: server - name: server
image: git.stinnesbeck.com/nils/mg4-server:latest # pinned by kustomization.yaml # 2.1.0, pinned by digest so a re-pushed tag cannot change what runs.
image: git.stinnesbeck.com/nils/mg4-server@sha256:e5f6e2d2ef9dc2650055a0823a3f86d8903e46918a76e990540482edf819766d
ports: ports:
- name: http - name: http
containerPort: 8080 containerPort: 8080