agentleFS
sammasaksammasak/claude-code-skills/skills/kubernetes-gitops/SKILL.md

kubernetes-gitops

Skill1 starsChanged 28 days agoMITView on GitHub

Use when changing or debugging anything in the homelab k3s cluster: manifests, Flux Kustomizations, HelmReleases, ingress, the native-OTLP telemetry collector and its privacy filters, scaling, or node problems. Not for SOPS encryption (secrets-management) or confirming a finished deploy is live (verify-service).

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If the KEDA interceptor is down, every scaled app 502s at once.

    line 35
  2. Very specific:

    Run cluster commands on Lenovo (k3s server: flux and the kubeconfig live there).

    line 39

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Kubernetes GitOps (homelab)

~/homelab-gitops (Flux, pushes to main) is the source of truth and its CLAUDE.md is the detailed reference. Change the cluster through Git; a manual kubectl apply gets reverted by Flux.

Topology

NodeRole
acer-swiftthe only always-on worker; every app pod runs here, so losing it is a total app outage
lenovo-21cb001pmxcontrol-plane, tainted; hosts ntfy, Tailscale subnet router, AdGuard DNS
msi-ms7758intermittent opt-in worker, taint sammasak.dev/intermittent=true:NoSchedule; scheduled power window Mon-Fri ~09:00-21:00 (WoL from lenovo), otherwise off; never WoL it at night
black-piRaspberry Pi 4 (4GB, arm64) opt-in worker, label node-pool=arm64, taint sammasak.dev/arch=arm64:NoSchedule; workloads need both plus a linux/arm64 image. Ethernet end0 .136, Wi-Fi wlan0 .137; Cilium devices must list both

Platform: Flux, Cilium (CNI + kube-proxy replacement), MetalLB (pool 192.168.10.202-204), Traefik v3 on .203 (*.sammasak.dev), cert-manager (letsencrypt-prod, Cloudflare DNS-01), Authentik, KEDA + HTTP add-on, Kyverno, zot. Gone (do not reintroduce without an ADR): ingress-nginx, Harbor, Falco, Tempo, Pushgateway, Cloudflare tunnel, VM workloads, incident-responder, sandboxes, forge, collab-vault.

Conventions

  • New app: cp -r apps/_template apps/<app>, then add <app>/ to apps/kustomization.yaml (forgetting this deploys nothing, silently). Full walkthrough: docs/adding-an-app.md.
  • Validate before pushing: devenv tasks run gitops:validate (kustomize build + kubeconform of the working tree; ~15 s). The repo's hooks re-run it on the staged tree and the pushed sha (gitops-validate <rev>); there is no Justfile.
  • chat and llm are separate Flux Kustomizations so slow model pulls cannot block apps.
  • Ingress: ingressClassName: traefik, traefik.ingress.kubernetes.io/* annotations, router.tls: "true", cert-manager.io/cluster-issuer: letsencrypt-prod. Authentik-gated apps add traefik.ingress.kubernetes.io/router.middlewares: "authentik-authentik-forward-auth@kubernetescrd". ntfy is deliberately un-gated.
  • Resources: CPU/memory requests and a memory limit; CPU limits are optional and usually wrong. Namespace PSS: enforce: baseline, warn/audit: restricted.
  • Images by @sha256: digest from registry.sammasak.dev/lab/. Static sites are amd64+arm64 indexes: after pushing the amd64 image, scripts/add-arm64.sh adds the arm64 variant without a rebuild.

Scale-to-zero traps

  • HTTP apps sit at zero replicas behind the KEDA HTTP interceptor. Zero pods when idle is expected; check the HTTPScaledObject before calling it an incident.
  • A single failing check after idle is usually a cold start; retry once before declaring an outage.
  • If the KEDA interceptor is down, every scaled app 502s at once.

Debug entry points

Run cluster commands on Lenovo (k3s server: flux and the kubeconfig live there). Elsewhere kubectl has no kubeconfig and fails with localhost:8080 ... refused: a missing client config, not a cluster outage.

devenv tasks run gitops:status       # Kustomizations + HelmReleases
devenv tasks run gitops:unhealthy    # non-Running pods, not-Ready Flux objects
devenv tasks run gitops:sync --input name=apps
flux logs --all-namespaces --level=error

For deeper diagnosis dispatch the k8s-debugger agent.

Telemetry privacy (native OTLP)

Boundary: docs/runbooks/native-otlp-ingestion.md. Only allowlisted metrics and sanitized Claude tool_result events may leave the collector; raw output, arguments, session/user IDs and unbounded label values must not reach Loki or Prometheus, and Codex logs stay dropped. On main, log scope/schema/trace/span metadata still reaches Loki; PR #138 (open) scrubs it. A collector change (clusters/homelab/infra/observability.yaml) needs:

  • ALLOY_BIN=<alloy> bash scripts/test-native-otlp-sanitizer.sh (on main): auth, size, redaction and restart at a debug exporter; it never touches Loki.
  • TMPDIR=<scratch> python3 scripts/test-native-otlp-log-loki.py <alloy> <loki> (added by PR #138): real isolated Alloy and Loki from the repo config, asserting the stored log schema. Synthetic local-backend proof only.

Merging to main is the Flux rollout, so it needs explicit approval. Live acceptance additionally needs the applied revision and an authorized synthetic check at the real receiver and Loki; Ready=True or green fixtures are not acceptance (system gate: homelab-gitops#120). A privacy rollback disables native logs; never restore a known-leaking config.

Destructive deletes (kyverno-enforced)

Deleting a PVC, PV, or Namespace — or a Flux Kustomization/HelmRelease whose prune would cascade to them — is admission-denied until the object carries the break-glass annotation. That annotation is the HUMAN's confirmation step, not the agent's: the validate-bash hook blocks an agent from setting sammasak.dev/allow-delete. So do not run the annotate — instead surface the denial and ask the human to perform the two-step themselves:

# Human runs these after re-reading the denial they are overriding:
kubectl -n <ns> annotate pvc <name> sammasak.dev/allow-delete=true
kubectl -n <ns> delete pvc <name>

Removing an app's manifests from Git is the other path: Flux's own service account is exempt, so the prune deletes its namespace and PVCs (reclaim Delete) with no annotation. The Git change is the gate, so get the owner's explicit OK and archive the volumes first (/var/lib/rancher/k3s/storage/pvc-* on the node, as root).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.