Repository navigation
infra(traefik): move Traefik from k3s addon to ArgoCD (follow-up to 2026-09-26 outage) #106
Description
Context
2026-09-26, 03:04-06:21 UTC (~3h15m): api.nan.builders and api.helmcode.com answered 404 on every inference route. LiteLLM was healthy. Root cause: nan-eu001 took the k3s controller lease and re-applied its stale on-disk /var/lib/rancher/k3s/server/manifests/traefik-config.yaml (no stripstream plugin); Traefik dropped every LiteLLM router (plugin: unknown plugin type: stripstream).
Short-term fix (nan-devops PR "fix(traefik): sync traefik-config.yaml to every k3s server from git"): a DaemonSet keeps that file identical to git on the three servers, the Traefik image is pinned, and new alerts cover routers going missing and config drift.
That still leaves Traefik managed by the k3s addon/helm-controller machinery: config lives on node disks, whichever server holds the lease re-renders the HelmChart from its own k3s binary, and deleting the file garbage-collects the live config. This issue removes that machinery.
Goal
Traefik is an ordinary ArgoCD-managed Helm release (pinned chart + image, values in git), with k3s no longer deploying or reconciling it.
Plan (maintenance window, both tenants affected)
- Pre-work (no prod impact)
- Align k3s: upgrade nan-eu001 v1.34.6 -> v1.35.4 (same as eu006/eu007), one server at a time, etcd health checked between steps.
- Build the ArgoCD app
kube-system/traefik(upstreamtraefik/traefikchart, pinned version matching the running one; values = the current HelmChartConfig values + what the k3s HelmChart injects: priorityClassName, tolerations, publishedService, image).helm templatediff against the live Deployment/Service/RBAC/IngressClass must be empty or explained. - Decide CRD ownership (
traefik-crdchart today): Argo app withReplace=true/SSA, or keep CRDs static. - Rehearse the whole cutover on a throwaway k3s (3 servers) incl. rollback.
- Cutover (window)
- Take over the existing Helm release in place (same release name
traefik, namespacekube-system) so Deployment/Service are adopted, not recreated: no ClusterIP change, cloudflared keeps working. - Add
--disable=traefikto the k3s config on all 3 servers and restart k3s one at a time. Before that, confirm on the rehearsal cluster what k3s does to the existing release/HelmChart objects when disabled (it deletes the HelmChart; the helm-delete job must NOT uninstall the release Argo now owns). - Remove
traefik.yaml/traefik-config.yamlfrom the manifests dirs and retire the traefik-config-sync DaemonSet and its alert.
- Take over the existing Helm release in place (same release name
- Verification
- Gatus all green;
traefik-router-missing-*silent; SSE stream test (no coalescing, no 60s cut) on both hosts; access logs still in VictoriaLogs; stripstream headers still stripped. - Force a lease change (restart k3s on the current leader) and confirm Traefik does not roll.
- Gatus all green;
- Rollback: re-enable traefik in k3s config, restore
traefik-config.yamlfrom git to the manifests dirs, restart k3s on one server.
Acceptance
- k3s servers on the same version
- Traefik deployed only by ArgoCD; no Traefik files in any
server/manifests - Lease change drill does not touch Traefik (2026-10-02: the leader restart re-took the lease, so the lease never moved; the manifests re-apply did not touch Traefik)
- Runbook + postmortem updated (
platform/devops/docs/postmortems/,docs/INCIDENTS.md)
Target: week of 2026-09-28.
Activity
sre-helmcode commentedon Oct 2, 2026
Cutover done (2026-10-02, 18:14–18:48 UTC)
Traefik is the ArgoCD app
prod-traefik:traefik/traefik39.0.7, Server-Side Apply,prune: false;crds/.k3s no longer deploys it. The three servers run with
/etc/rancher/k3s/config.yaml.d/50-traefik-cutover.yaml(disable-helm-controller: true,disable: [traefik]). No HelmChart, HelmChartConfig, Addon, Helm release secret or Traefik manifest file remains. The adopted objects are the same ones as before: Deployment UIDff21b294…, Service ClusterIP10.245.78.152.docs/runbooks/traefik-k3s-to-argocd-cutover.md(helmcode/nan-devops#730).Incident: ~23 s of 502 (128 requests on both public APIs) during the adoption sync. A fresh Traefik pod went Ready in 3 s, before the LiteLLM NetworkPolicies admitted its IP. Fixed with
readinessProbe.initialDelaySeconds: 60(#733); its own rollout had no burst. Postmortem:docs/postmortems/2026-10-02-traefik-cutover-502-burst.md.Open: a real lease-change drill. The leader restart re-took the lease within 15 s. Forcing a move means keeping eu001 down past the ~50 s CNPG deadline, so it was not done.