gateway-api
Skill71 starsChanged 8 days agoApache-2.0View on GitHub
Migrate from the retired ingress-nginx controller to Kubernetes Gateway API, and design, debug, and review Gateway API ingress. Covers annotation inventory and risk triage, ingress2gateway conversion, implementation choice (Envoy Gateway default, Cilium, Istio, NGINX Gateway Fabric, AWS Load Balancer Controller), CRD channel and lifecycle, shared-Gateway ownership, cert-manager listener TLS, Flux layering, parallel-run DNS cutover, and rollback. Use when asked to "migrate off ingress-nginx", "convert my Ingresses to HTTPRoute", "set up Gateway API", "why is my HTTPRoute not attached", or "plan the ingress cutover". Flux reconciliation stuck → /platform-skills:gitops. East-west HTTPRoute in a mesh → /platform-skills:linkerd.
We split this skill into the rules it gives an agent
11 rules. About 0 are common, 5 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 18
Ask one at a time:
- Distinctive: line 77
Run, one namespace at a time:
- Very specific: line 82
Use --emitter=standard for a non-Envoy implementation, and --ingress-nginx-ingress-class=<class> when the class is not nginx.
- Distinctive: line 83
Treat every warning as a finding.
- Very specific: line 85
Delete generated Gateway objects
- Very specific: line 89
Use the Service port number in backendRefs
- Very specific: line 112
Run bash examples/gateway-api/gateway-api-validate.sh against the adapted copy, or the kubeconform command above
- Distinctive: line 132
Decommission gate: no requests on the old load balancer for the whole soak.
- Very specific: line 149
Read the condition reason and match it to the troubleshooting table in references/gateway-api.md (NotAllowedByListeners, …
- Very specific: line 163
Ask which stage was reached, then apply the rollback table in references/gateway-api.md:
- Distinctive: line 174
Every mode ends with:
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 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.
Files
Move ingress traffic off the retired ingress-nginx controller onto Gateway API without a user-visible change, and keep a fast rollback until the old path is provably unused.
Read references/gateway-api.md before responding. It holds the implementation matrix, the ownership model, CRD channel rules, the ingress2gateway v1.2.0 annotation coverage table, cert-manager requirements, Flux layering, the seven-phase cutover, rollback by stage, and the troubleshooting table. The working layout is examples/fluxcd/basic-monorepo; a before/after pair for one Ingress is in examples/gateway-api.
This command is interactive by default. With no arguments, ask the three questions below, then run the matching mode. Never apply converted manifests or change DNS yourself: produce the change, the validation, and the rollback, and let the user run it.
Questions (no arguments)
Ask one at a time:
- Where are you?
assess(have ingress-nginx, not started) /convert(ready to translate) /scaffold(new cluster, no Ingress yet) /cutover(Gateway works, traffic not moved) /debug(a route or Gateway is not working) /review(check existing Gateway API config) /rollback - Which implementation? Envoy Gateway (default) / Cilium / Istio / NGINX Gateway Fabric / AWS Load Balancer Controller / undecided. If undecided, apply the "Choosing an implementation" table: pick by what the team already operates
- How is the cluster delivered? Flux / Argo CD / Helm or kubectl directly
Mode dispatch
| Mode | What it does | Mutates anything? |
assess | Inventories Ingresses, classes and annotations; classifies each annotation as auto, decision, or blocked | No |
convert | Runs ingress2gateway, then rewrites output for the shared-Gateway model | Writes files only |
scaffold | Generates CRDs, controller, GatewayClass, Gateway, cert-manager and Flux layers | Writes files only |
cutover | Produces the per-hostname shadow test, DNS plan, soak criteria and decommission gate | No |
debug | Diagnoses a route, listener, certificate or Gateway address problem | No |
review | Reviews Gateway API manifests for correctness, security and operational safety | No |
rollback | Picks the rollback for the stage reached | No |
Mode: assess
- Collect:
kubectl get ingressclass # One row per Ingress annotation, keeping the resource identity and hosts, # so every annotation can be traced to the Ingress that carries it. # Columns: namespace, name, class, hosts, annotation, value. The value is # kept because several blockers are value-dependent (`rewrite-target: /$1` # uses a capture group; a snippet's body is what has to be redesigned). # @tsv escapes tabs and newlines, so a multi-line snippet stays on one row. # An Ingress with no nginx annotation still gets one row with "-". kubectl get ingress -A -o json | jq -r ' .items[] | (.metadata.annotations // {}) as $a | [.metadata.namespace, .metadata.name, (.spec.ingressClassName // $a["kubernetes.io/ingress.class"] // "-"), ([.spec.rules[]?.host // "*"] | unique | join(","))] as $id | ([$a | to_entries[] | select(.key | startswith("nginx.ingress.kubernetes.io/")) | [.key, .value]] | if length == 0 then [["-", "-"]] else . end)[] | $id + . | @tsv' > ingress-inventory.tsv column -t -s $'\t' ingress-inventory.tsv # Cluster-wide count per annotation, to size the work kubectl get ingress -A -o json \ | jq -r '.items[] | .metadata.annotations // {} | keys[]' \ | grep '^nginx.ingress.kubernetes.io/' | sort | uniq -c | sort -rn # Controller-wide settings also change behaviour, not just annotations kubectl get configmap -A -l app.kubernetes.io/name=ingress-nginx -o yaml - Classify every annotation against the coverage table in
references/gateway-api.md:- auto: ingress2gateway converts it to core Gateway API
- decision: converted to an implementation-specific policy, or converted with different semantics (timeouts, IP ranges, regex)
- blocked: warned-and-dropped or not recognised (
configuration-snippet,server-snippet, auth, rate limiting). Each needs an owner and a design before conversion
- Flag controller ConfigMap keys that set global behaviour (custom headers, timeouts,
use-forwarded-headers,ssl-redirect). They do not appear on any Ingress and are the easiest to lose - Output a table: namespace, Ingress, host, class, annotations by category, owner, blocking yes/no. End with the count of blocked items. Do not proceed to
convertwhile any item is blocked without an agreed decision
Mode: convert
- Confirm
assesshas no open blocked items - Run, one namespace at a time:
Useingress2gateway print --providers=ingress-nginx --emitter=envoy-gateway \ -n <namespace> > converted-<namespace>.yaml--emitter=standardfor a non-Envoy implementation, and--ingress-nginx-ingress-class=<class>when the class is notnginx. Never pass--allow-experimental-gw-apion a standard-channel cluster - Treat every warning as a finding. List each one with the Ingress it came from
- Rewrite for the shared-Gateway model:
- Delete generated
Gatewayobjects - Set each route's
parentRefstoplatform-gatewayinenvoy-gateway-system, with the rightsectionName - Move TLS to a Gateway HTTPS listener per hostname (cert-manager requirement)
- Bind each HTTPS listener's
allowedRoutesto the namespace that owns its hostname (kubernetes.io/metadata.name) - Use the Service port number in
backendRefs
- Delete generated
- Re-express each
decisionitem (filter or policy) and show it next to the original annotation - Validate:
kubeconform -strict -summary \ -schema-location default \ -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \ converted-<namespace>.yaml - State what is not covered by the converted output, plainly
Mode: scaffold
Generate the layout from examples/fluxcd/basic-monorepo, adapted to the answers:
- CRDs: standard channel, applied as plain manifests, never through a Helm release (they exceed the 1 MiB release Secret). For Flux: one
Kustomizationper source withprune: falseanddeletionPolicy: Orphan: Gateway APIconfig/crd/standardat the pinned tag, plus Envoy Gateway'scharts/crds/crds/generated/from the pinned chart artifact. Setcrds.enabled: falseon the controller chart - Controller: pinned exact version; at least two control-plane replicas in production
- Data plane: at least two proxy replicas plus a PodDisruptionBudget in production. Never a PDB of
minAvailable: 1on one replica - GatewayClass and Gateway: one shared Gateway owned by the platform team. Port 80
from: Same(redirect and ACME only), HTTPS listenersfrom: Selectorbound to the namespace that owns the hostname viakubernetes.io/metadata.name. Neverfrom: All, and never one shared opt-in label across listeners: either lets a team claim another team's hostname. App namespaces are created in the platform layer, never by the app layer - Redirect: a platform-owned
RequestRedirectroute on port 80 withstatusCode: 308 - TLS: cert-manager v1.15+ with
config.gatewayAPI.enabled: true, thecert-manager.io/cluster-issuerannotation on the Gateway, and thegatewayHTTPRouteHTTP-01 solver. cert-manager must start after the CRDs exist (its layerdependsOnthe CRD Kustomizations) - Delivery: for Flux, three layers (
infrastructure→infrastructure-configs→apps) with ahealthCheckExprsentry on GatewayProgrammed. For Argo CD, sync waves in the same order - NetworkPolicy: allow app ingress only from the proxy pods (namespace and pod selector)
- Run
bash examples/gateway-api/gateway-api-validate.shagainst the adapted copy, or the kubeconform command above
Mode: cutover
Produce a per-hostname plan following the migration path in references/gateway-api.md:
- Pre-flight: Gateway
Programmed=True, every routeAccepted=TrueandResolvedRefs=True, every certificateReady - TTL: lower the DNS TTL to 60s at least one old-TTL period before the switch
- Shadow test each hostname against the new address without touching DNS:
Compare status codes, key response headers, redirects and p99 latency with the old path# The address can be an IP or a hostname (cloud LBs often publish a DNS name). # --connect-to accepts either and keeps the URL host for Host and TLS SNI; # --resolve only accepts an IP and fails to parse a hostname. NEW_ADDR=$(kubectl get gateway platform-gateway -n envoy-gateway-system -o jsonpath='{.status.addresses[0].value}') curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' \ --connect-to <host>:443:"$NEW_ADDR":443 https://<host>/<health-path> - Order: lowest-risk hostname first; one hostname per change
- Soak criteria: error rate and p99 within SLO for at least one full business cycle
- Decommission gate: no requests on the old load balancer for the whole soak. Decommissioning is its own change, never the cutover change
Blast radius: one hostname per DNS change. Nothing else moves.
Mode: debug
- Collect:
kubectl get gatewayclass kubectl get gateway -A kubectl describe gateway <name> -n <namespace> kubectl get httproute <name> -n <namespace> -o yaml # read .status.parents kubectl get certificate,certificaterequest -A kubectl get svc -n envoy-gateway-system kubectl logs -n envoy-gateway-system deploy/envoy-gateway --tail=100 - Classify the layer: CRD/chart rendering (install fails, admission policy errors), reconciliation (Flux or Argo stuck,
no matches for kind), runtime (route or Gateway conditions) - Read the condition
reasonand match it to the troubleshooting table inreferences/gateway-api.md(NotAllowedByListeners,NoMatchingListenerHostname,BackendNotFound,RefNotPermitted,AddressNotAssigned) - State the most likely root cause with the exact field to change, the corrected YAML, the verification command, and the rollback
Mode: review
Review in this order and separate critical findings from improvements:
- Correctness:
backendRefsuse port numbers; listener hostnames cover route hostnames; every HTTPS listener hashostname,tls.mode: TerminateandcertificateRefs; configs depend on CRDs - Security: no
from: Allon shared listeners; each HTTPS listener binds to the namespace owning its hostname, not a shared label;ReferenceGrants are as narrow as possible; NetworkPolicy admits only proxy pods; no experimental channel unless deliberate - Operational safety: CRDs are plain manifests that are never pruned or deleted with their owner; the controller chart does not install CRDs; proxy replicas ≥ 2 with a PDB in production; exact version pins; a
Programmedhealth check - Portability: implementation-specific policies are listed and justified
Mode: rollback
Ask which stage was reached, then apply the rollback table in references/gateway-api.md:
| Stage | Rollback |
| Before DNS cutover | Delete the new routes; nothing user-facing changed |
| After cutover, ingress-nginx still running | Point DNS back at the ingress-nginx address |
| After decommission | Revert the decommission commit; Flux or Argo re-creates ingress-nginx and the Ingress objects; then switch DNS |
| Bad CRD upgrade | Revert the CRD source tag in Git. Never delete a CRD or its Kustomization |
Output format
Every mode ends with:
- Findings or changes, with exact files and fields
- Blast radius, in one line
- Validation, as commands to run
- Rollback, as the concrete step for the stage reached
In this repository59 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- .cursor/rules/keda.mdcCursor rule
- .cursor/rules/kubernetes.mdcCursor rule
- .cursor/rules/platform-skills.mdcCursor rule
- .cursor/rules/terraform.mdcCursor rule
- code-reviewSkill
- platform-skillsSkill
- ai-governanceSkill
- awesome-docsSkill
- aws-profileSkill
- awsSkill
- azureSkill
- backup-drSkill
- chaosSkill
- checkovSkill
- cluster-upgradeSkill
- commitSkill
- complianceSkill
- composite-actionsSkill
- datadogSkill
- debugSkill
- documentSkill
- doraSkill
- dynatraceSkill
- finopsSkill
- fluxcdSkill
- github-actionsSkill
- gitopsSkill
- helmchartSkill
- karpenterSkill
- kedaSkill
- kingfisherSkill
- kubernetesSkill
- kubescape-operatorSkill
- kubescapeSkill
- kyvernoSkill
- linkerdSkill
- linuxSkill
- mcpSkill
- observabilitySkill
- opaSkill
- openshiftSkill
- platform-skillsSkill
- preflightSkill
- productSkill
- progressive-deliverySkill
- pr-reviewSkill
- renovateSkill
- runtime-securitySkill
- secretsSkill
- self-improveSkill
- setup-agentsSkill
- supply-chainSkill
- terraformSkill
- token-optimizerSkill
- triageSkill
- trivySkill
- zizmorSkill
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
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.

