Commit Graph
4354 Commits
Author SHA1 Message Date
k.rennerandClaude Opus 4.6 111dac637d fix: Pass --insecure to ocis init to skip interactive prompt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 07:42:25 +02:00
k.rennerandClaude Opus 4.6 99445f286e fix: Remove duplicate command key in ownCloud init container
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 07:38:36 +02:00
k.rennerandClaude Opus 4.6 6c1aee6c15 feat: Deploy ownCloud Infinite Scale to K8s
ownCloud oCIS 8.0.1 with:
- Authentik OIDC integration (auto-provisioning, role mapping)
- 200Gi Ceph PVC for user data
- Ingress with 10G upload limit, Cloudflare tunnel
- CSP and proxy config via ConfigMap
- SOPS-encrypted secrets (admin password, OIDC credentials)
- ONLYOFFICE excluded for initial deployment (can add later)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 07:32:02 +02:00
k.rennerandClaude Opus 4.6 82b1d3aa40 feat: Decommission Gatus LXC (237) — migrated to K8s
ci/woodpecker/push/deploy-infrastructure Pipeline was successful
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
ci/woodpecker/push/security-scan Pipeline was successful
Gatus is now running in K8s with:
- FluxCD-managed deployment
- Ceph PVC for data
- ingress-nginx with CrowdSec bouncer
- Let's Encrypt TLS via cert-manager
- Internal DNS via ExternalDNS (UniFi)
- External DNS via ExternalDNS (Cloudflare tunnel)

First service fully migrated from Proxmox LXC to Kubernetes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 07:15:44 +02:00
k.rennerandClaude Opus 4.6 fcbb6a9c55 feat: Remove status.renner.uno CNAME from OpenTofu
ci/woodpecker/push/security-scan Pipeline is running
ci/woodpecker/push/deploy-infrastructure Pipeline was successful
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
DNS for status.renner.uno is now managed by K8s ExternalDNS.
ExternalDNS will create an A record pointing to the K8s ingress VIP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:27:37 +02:00
k.rennerandClaude Opus 4.6 630dca5310 fix: Enable debug logging for UniFi webhook
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:23:12 +02:00
k.rennerandClaude Opus 4.6 fb9c4097ab fix: Change UniFi ExternalDNS policy to sync
upsert-only won't update existing records. sync will update the
existing CNAME (ingress.renner.uno) to point to the K8s ingress VIP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:16:55 +02:00
k.rennerandClaude Opus 4.6 115c1fc912 fix: Define webhook container port for health probes
The chart probe references named port http-wh-metrics which needs
to be defined on the container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:11:15 +02:00
k.rennerandClaude Opus 4.6 b7a509b48b fix: Use numeric port for UniFi webhook health probes
Named port http-wh-metrics not found on container. Use port 8080.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:09:08 +02:00
k.rennerandClaude Opus 4.6 8a5ff19aad feat: Add ExternalDNS with UniFi webhook for internal DNS
Automatically creates UniFi DNS records for all K8s Ingresses,
pointing to the ingress VIP. Replaces OpenTofu ingress_cnames.

- All Ingresses → UniFi DNS (internal, no annotation needed)
- Annotated Ingresses → Cloudflare DNS (external tunnel)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:03:51 +02:00
k.rennerandClaude Opus 4.6 f2c38dded2 feat: Add network policies for ingress namespace isolation
- Cloudflared: egress only to ingress-nginx (80/443), DNS, and
  Cloudflare tunnel endpoints (world on 7844/443)
- ingress-nginx: egress to apps namespace, CrowdSec LAPI, DNS,
  and external (OCSP/ACME)
- Default deny ingress to ingress namespace from other namespaces,
  allow from world (external traffic), host (health probes), and
  same namespace

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 23:52:47 +02:00
k.rennerandClaude Opus 4.6 dd02b4b180 fix: Set Cloudflared namespace to ingress
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:19:05 +02:00
k.rennerandClaude Opus 4.6 633cf6a84f refactor: Move Cloudflared to infrastructure
Cloudflared is part of the ingress path, not an application.
Add SOPS decryption to infrastructure Kustomization for the
tunnel credentials secret.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:14:23 +02:00
k.rennerandClaude Opus 4.6 5a2a5fe138 feat: Use dedicated K8s tunnel, separate from old Caddy tunnel
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
Create new tunnel 'k8s-homelab' for K8s services. The old tunnel
stays with the Caddy/LXC setup. No remote config conflicts —
local config.yaml is the only config for this tunnel.

- New tunnel ID: d2386ccd-a665-41e7-8631-603fdafcb006
- New credentials (SOPS encrypted)
- ExternalDNS default target updated to new tunnel
- Only status.renner.uno for testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:42:09 +02:00
k.rennerandClaude Opus 4.6 238ac9ec1b fix: Use HTTPS for tunnel to K8s ingress, skip TLS verify
ingress-nginx redirects HTTP→HTTPS (308). Configure tunnel to
connect via HTTPS with noTLSVerify (internal cluster traffic).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:26:56 +02:00
k.rennerandClaude Opus 4.6 7b2d322a88 fix: Force default targets in ExternalDNS
ExternalDNS ignores default-targets when the Ingress has a
LoadBalancer IP. Use --force-default-targets to always use the
tunnel CNAME instead of the internal VIP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:23:18 +02:00
k.rennerandClaude Opus 4.6 0f24ca0552 fix: Move ExternalDNS to apps, configure tunnel ID globally
- Move ExternalDNS from infrastructure to apps (not a prerequisite)
- Set --default-targets with tunnel ID so Ingresses only need
  the annotation external-dns.alpha.kubernetes.io/cloudflare-proxied
- Simplify Gatus Ingress annotation (no tunnel ID needed)
- SOPS secret for Cloudflare token co-located with ExternalDNS

To expose via tunnel: add one annotation to Ingress.
To switch to NetBird-only: remove the annotation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:10:18 +02:00
k.rennerandClaude Opus 4.6 32ca0b605c feat: Add ExternalDNS for Cloudflare tunnel DNS management
ExternalDNS watches Ingress annotations and creates proxied CNAME
records in Cloudflare pointing to the tunnel endpoint.

- Deploy ExternalDNS with Cloudflare provider
- Annotate Gatus Ingress with tunnel target
- annotationFilter ensures only explicitly annotated Ingresses get
  public DNS (opt-in, not opt-out)
- policy: upsert-only prevents deleting records not managed by K8s

To expose a service via Cloudflare tunnel, add to Ingress:
  external-dns.alpha.kubernetes.io/target: <tunnel-id>.cfargotunnel.com
  external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"

To switch to NetBird-only: remove the annotations. No DNS record = VPN only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:05:01 +02:00
k.rennerandClaude Opus 4.6 327030c4e4 feat: Switch Cloudflared to locally-managed tunnel config
Replace remote-managed tunnel (--token) with local config file.
Ingress rules defined in ConfigMap — GitOps managed.

status.renner.uno routes to K8s ingress (test).
Legacy services route to existing Caddy backends.

As services migrate to K8s, update the ConfigMap to point
their hostname to the K8s ingress service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:47:38 +02:00
k.rennerandClaude Opus 4.6 58badab5a1 fix: Remove talosconfig from git, fix gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:39:17 +02:00
k.rennerandClaude Opus 4.6 f7869e455d fix: Replace Cloudflared ExternalSecret with SOPS-encrypted secret
Add SOPS decryption to apps Kustomization. All secrets now managed
via SOPS + age, no ESO dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:38:40 +02:00
k.rennerandClaude Opus 4.6 81ccc623de fix: Remove accidentally committed Talos secrets, add to gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:28:58 +02:00
k.rennerandClaude Opus 4.6 02ce513b4a feat: Switch to SOPS + age for secret management
Replace External Secrets Operator + Vault with SOPS-encrypted secrets
in git, decrypted by FluxCD using an age key.

- Add .sops.yaml with age public key for encryption
- Encrypt Cloudflare API token with SOPS
- Remove ESO HelmRelease and Vault ClusterSecretStore
- Add decryption config to infrastructure-crds Kustomization
- Bootstrap step: create sops-age secret from local age key

No external dependencies. Age private key lives on workstation,
injected during bootstrap via:
  kubectl create secret generic sops-age -n flux-system \
    --from-file=age.agekey=$HOME/.config/sops/age/keys.txt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:28:22 +02:00
k.rennerandClaude Opus 4.6 5ead1d772b feat: Use Vault Kubernetes auth for ESO (no manual secrets)
Replace token-based auth with Kubernetes ServiceAccount auth.
ESO authenticates using its ServiceAccount JWT. Talos cluster
secrets stored in Vault for PKI reuse across recreations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:07:57 +02:00
k.rennerandClaude Opus 4.6 51ca20f067 fix: Remove clusterwide network policies that broke DNS/egress
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
CiliumClusterwideNetworkPolicy with empty endpointSelector and
egress rules acts as a default-deny egress for ALL namespaces,
breaking DNS resolution and inter-service communication.

Keep only namespace-scoped policies for apps (default-deny ingress
+ allow from ingress-nginx). Clusterwide policies need more careful
design.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:38:03 +02:00
k.rennerandClaude Opus 4.6 a25428393f fix: Allow kubelet health probes in network policy
Kubelet liveness/readiness probes come from the host network.
Add host and remote-node entities to the default-deny policy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:29:00 +02:00
k.rennerandClaude Opus 4.6 98bdcde0af feat: Add Cilium Network Policies for pod microsegmentation
- Default deny ingress to apps namespace (only same-namespace allowed)
- Allow ingress-nginx to reach app pods
- Allow Prometheus to scrape metrics from all namespaces
- Allow all pods to reach CoreDNS for DNS resolution

Replaces the Caddy DMZ VLAN isolation with K8s-native microsegmentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:23:58 +02:00
k.rennerandClaude Opus 4.6 26aa3ab806 feat: Deploy Cloudflared tunnel to K8s
Cloudflare tunnel connector as a K8s Deployment (2 replicas).
Tunnel token pulled from Vault via ExternalSecret.
All Ansible vault secrets migrated to HashiCorp Vault.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:22:21 +02:00
k.rennerandClaude Opus 4.6 cab557f5da fix: Remove ansible_playbook from Vault LXC, use run-ansible pipeline
ci/woodpecker/push/deploy-infrastructure Pipeline was successful
ci/woodpecker/manual/run-ansible Pipeline was successful
ci/woodpecker/push/security-scan Pipeline was successful
The tofu provisioner can't run Ansible with dynamic inventory in
the CI container. Use the dedicated run-ansible pipeline instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:08:01 +02:00
k.rennerandClaude Opus 4.6 e78c941aaf fix: Use correct playbook path for Vault LXC provisioning
ci/woodpecker/push/deploy-infrastructure Pipeline was successful
ci/woodpecker/manual/deploy-infrastructure Pipeline failed
ci/woodpecker/push/security-scan Pipeline failed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:01:33 +02:00
k.rennerandClaude Opus 4.6 de307c5fc1 fix: Use ESO v1 API version (v1beta1 removed in ESO 2.x)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:00:09 +02:00
k.rennerandClaude Opus 4.6 629a303d6b fix: Disable CrowdSec PVCs to break Ceph circular dependency
ci/woodpecker/manual/deploy-infrastructure Pipeline failed
CrowdSec is in infrastructure stage which must complete before
Ceph cluster deploys. CrowdSec PVCs need Ceph. Disable PVCs
for now — CrowdSec state is ephemeral (bouncer decisions rebuild
quickly from the crowd).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:52:53 +02:00
k.rennerandClaude Opus 4.6 e55b42c66e feat: Add HashiCorp Vault + External Secrets Operator
ci/woodpecker/push/deploy-infrastructure Pipeline was successful
ci/woodpecker/push/security-scan Pipeline was successful
Replace SealedSecrets with Vault-backed External Secrets:

- New LXC 240 (vault.srv.renner.uno) for HashiCorp Vault
- Ansible role + playbook for Vault with Raft storage backend
- External Secrets Operator HelmRelease in K8s
- ClusterSecretStore pointing to Vault
- ExternalSecret for Cloudflare API token
- Remove SealedSecrets from infrastructure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:50:21 +02:00
k.rennerandClaude Opus 4.6 0c85f6ef1d fix: Use static bouncer key Secret instead of Job
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
Replace the fragile kubectl-exec Job with a simple static Secret
in both crowdsec and ingress namespaces. CrowdSec LAPI pre-registers
the bouncer via BOUNCER_KEY_ingress env var. The key is for internal
cluster communication only.

Also removes RBAC resources that were needed for the Job.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:36:59 +02:00
k.rennerandClaude Opus 4.6 cfcd5048a6 feat: Deploy monitoring stack (Prometheus, Grafana, Loki, Alloy)
- kube-prometheus-stack: Prometheus (50Gi, 90d retention), Grafana
  with ingress at dash.renner.uno, Alertmanager, Node Exporter,
  kube-state-metrics. Scrapes existing Proxmox infrastructure.
- Loki: SingleBinary mode, 80Gi storage, 90d retention
- Alloy: DaemonSet collecting K8s pod logs to Loki
- Grafana datasources: Prometheus (default) + Loki
- Disabled etcd/scheduler/controller-manager scraping (Talos)
- Monitoring namespace set to privileged PodSecurity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:27:01 +02:00
k.rennerandClaude Opus 4.6 1a6f537a46 feat: Proper CrowdSec bouncer integration with ingress-nginx
ci/woodpecker/cron/security-scan Pipeline was successful
Move CrowdSec to infrastructure stage with a Job that:
1. Waits for CrowdSec LAPI to be ready
2. Generates a bouncer API key via cscli
3. Creates a K8s Secret in the ingress namespace

ingress-nginx HelmRelease depends on CrowdSec, uses the CrowdSec
controller image, and reads the bouncer key from the provisioned
secret. No SealedSecrets or hardcoded keys needed.

Flow: CrowdSec deploys → Job creates bouncer key → ingress-nginx
starts with bouncer plugin authenticated to LAPI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 10:44:43 +02:00
k.rennerandClaude Opus 4.6 a46f37834e fix: Re-seal all secrets for current cluster key
SealedSecrets were encrypted with the previous cluster's key.
Re-sealed with the current sealed-secrets controller key after
VM recreation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 10:19:28 +02:00
k.rennerandClaude Opus 4.6 eca3152351 fix: Remove CrowdSec bouncer from ingress-nginx infrastructure
The bouncer init container blocks ingress startup when the secret
doesn't exist yet (CrowdSec deploys after infrastructure). Deploy
ingress-nginx as standard first; CrowdSec bouncer integration
needs to be solved as a post-deploy step.

CrowdSec still monitors logs and can ban IPs at the network level.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:48:38 +02:00
k.rennerandClaude Opus 4.6 5ee87e8a09 fix: Make CrowdSec bouncer optional for ingress-nginx startup
- Add optional: true to secretKeyRef so ingress pods start without
  the bouncer key (CrowdSec deploys after infrastructure)
- Move ingress bouncer SealedSecret to crowdsec app (creates in
  ingress namespace when CrowdSec deploys)
- Ingress starts without CrowdSec, bouncer activates after secret appears

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:42:17 +02:00
k.rennerandClaude Opus 4.6 bda07b0705 fix: Move ingress bouncer SealedSecret to post-crds stage
SealedSecret CRD doesn't exist when infrastructure kustomization
first applies. Move to post-crds which depends on infrastructure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:35:14 +02:00
k.rennerandClaude Opus 4.6 d7587ed2a4 fix: Use SealedSecrets for CrowdSec bouncer API key
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
ci/woodpecker/cron/update-all Pipeline failed
Replace hardcoded bouncer key with SealedSecrets in both crowdsec
and ingress namespaces. CrowdSec LAPI pre-registers the bouncer
via BOUNCER_KEY_ingress env var, ingress-nginx reads the same key
from its namespace's secret.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:32:22 +02:00
k.rennerandClaude Opus 4.6 fe429bf60f test: Set CrowdSec bouncer API key (temporary, needs proper secret management)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:27:42 +02:00
k.rennerandClaude Opus 4.6 319ad82d7a fix: Move CrowdSec to dedicated privileged namespace
CrowdSec agents need hostPath volumes to read container logs, which
requires a privileged PodSecurity namespace. Move from apps to
dedicated crowdsec namespace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:20:09 +02:00
k.rennerandClaude Opus 4.6 2b1897161b fix: Use Docker Hub for CrowdSec controller image
The crowdsecurity/controller image is on Docker Hub, not GHCR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:27:14 +02:00
k.rennerandClaude Opus 4.6 419ba5a225 feat: Deploy CrowdSec with ingress-nginx bouncer
- Add CrowdSec Helm chart (LAPI + agent) in apps/crowdsec/
- Agent monitors ingress-nginx and app pod logs
- Collections: nginx, http-cve, appsec-virtual-patching, appsec-generic-rules
- Switch ingress-nginx to CrowdSec controller image (v1.13.2) with
  Lua bouncer plugin for request-level threat blocking
- LAPI data persisted on ceph-block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:19:25 +02:00
k.rennerandClaude Opus 4.6 c3b4cc2ef0 feat: Add Cloudflare API token SealedSecret for cert-manager
Add encrypted Cloudflare DNS API token for Let's Encrypt DNS-01
challenges. Set ACME account email on ClusterIssuers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:03:19 +02:00
k.rennerandClaude Opus 4.6 4d01ce2718 fix: Include L2 announcements in Talos Cilium inline manifest
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
Add l2announcements.enabled and externalIPs.enabled to the Cilium
helm template used for Talos bootstrap. This ensures LoadBalancer
VIPs work immediately without needing a Cilium agent restart after
FluxCD upgrades the HelmRelease.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:29:54 +02:00
k.rennerandClaude Opus 4.6 b14ab6cc6c feat: Replace MetalLB with Cilium L2 announcements
ci/woodpecker/manual/deploy-infrastructure Pipeline was successful
Cilium with kubeProxyReplacement=true has native LoadBalancer support
with L2 announcements. MetalLB L2 VIPs weren't reachable because
Cilium was handling the service datapath.

- Enable l2announcements and externalIPs in Cilium HelmRelease
- Add CiliumLoadBalancerIPPool (10.2.40.64-95) and L2AnnouncementPolicy
- Remove MetalLB from infrastructure
- Remove MetalLB annotation from ingress-nginx service

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 19:53:50 +02:00
k.rennerandClaude Opus 4.6 db35c6d462 feat: Deploy Gatus status monitor to K8s
First app migration to Kubernetes. Gatus monitors all homelab services
via HTTP/TCP/DNS health checks. Deployed as a Deployment with ConfigMap
for endpoints config, 1Gi ceph-block PVC for SQLite, and Ingress on
status.renner.uno with cert-manager TLS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 19:44:20 +02:00
k.rennerandClaude Opus 4.6 ff83cf5027 docs: Add K8s bootstrap documentation
- kubernetes/README.md: Complete bootstrap procedure, architecture,
  directory structure, known gotchas, and rebuild instructions
- CLAUDE.md: Add Kubernetes layer section, update overview to reflect
  K8s migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 19:41:20 +02:00