diff --git a/content/authors.json b/content/authors.json index 138c39f1b..c1c3106d0 100644 --- a/content/authors.json +++ b/content/authors.json @@ -642,5 +642,16 @@ "github": "https://github.com/ekam-walia" }, "postCount": 1 + }, + "johan-kuylenstierna": { + "handle": "johan-kuylenstierna", + "name": "Johan Kuylenstierna", + "avatar": null, + "bio": "Kubernetes and platform engineer at Agency 42 (agency42.se), Sweden.", + "socials": { + "linkedin": "https://www.linkedin.com/in/johankuylenstierna/", + "github": "https://github.com/Agency-42" + }, + "postCount": 1 } } \ No newline at end of file diff --git a/content/blog/migrate-ingress-nginx-to-gateway-api-hands-on.md b/content/blog/migrate-ingress-nginx-to-gateway-api-hands-on.md new file mode 100644 index 000000000..2bf1070c8 --- /dev/null +++ b/content/blog/migrate-ingress-nginx-to-gateway-api-hands-on.md @@ -0,0 +1,288 @@ +--- +title: "Leaving ingress-nginx: a hands-on migration to Gateway API with ingress2gateway" +seoTitle: "Migrate from ingress-nginx to Gateway API: a hands-on lab with ingress2gateway" +seoDescription: "ingress-nginx is retired. Inventory your annotations, convert with ingress2gateway 1.2, fix what it can't translate, run both side by side and cut over safely." +datePublished: 2026-10-15T10:00:00.000Z +slug: migrate-ingress-nginx-to-gateway-api-hands-on +author: johan-kuylenstierna +draft: false +tags: ["kubernetes", "gateway-api", "ingress-nginx", "networking"] +--- + +ingress-nginx is not "deprecated" or "in maintenance mode" any more. It is retired. Kubernetes SIG Network and the Security Response Committee [announced in November 2025](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/) that best-effort maintenance would end in March 2026, after which there would be "no further releases, no bugfixes, and no updates to resolve any security vulnerabilities". The Steering Committee followed up in January with a [rather less diplomatic statement](https://kubernetes.io/blog/2026/01/29/ingress-nginx-statement/): staying on it after retirement "leaves you and your users vulnerable to attack", and none of the alternatives are drop-in replacements. + +That is what happened. The last controller release, `controller-v1.15.1`, came out on 19 March 2026, and the GitHub repository is now archived (read-only). Your existing controller keeps running, and the Helm chart and images are still downloadable. That is exactly why it is easy to forget about. A proxy that faces the internet and never gets another CVE fix is a slow-motion incident. + +This post is the migration I would want to read first: inventory, automated conversion, the parts the converter can't do, running both stacks in parallel, cutover, and rollback. Everything below was run on a throwaway kind cluster, and every output is pasted from the lab logs. Where something failed, you get the failure too. + +## The lab + +- kind v0.33.0 with node image `kindest/node:v1.37.0` (Kubernetes v1.37.0, containerd 2.3.4) +- ingress-nginx Helm chart 4.15.1 (controller v1.15.1, the final release) +- ingress2gateway 1.2.0 +- Envoy Gateway v1.9.2 via Helm (it installed the Gateway API v1.6.1 CRDs, experimental channel) +- kubectl v1.37.1, Helm v3.19.0 + +Two tiny backends, `app-v1` and `app-v2`, both running `agnhost netexec`. Its `/hostname` endpoint returns the pod name, which makes it easy to see where every request ended up. Both controllers were exposed as NodePorts, and curl ran from a container on the kind Docker network: ingress-nginx on 30080/30443, Envoy Gateway on whatever ports it got. + +The five Ingresses cover the annotations I see most often in real clusters: + +| Ingress | Host | What it does | +|---|---|---| +| `web` | shop.lab.test | plain host + prefix routing to app-v1 | +| `web-canary` | shop.lab.test | `canary: "true"`, `canary-weight: "20"` to app-v2 | +| `api` | api.lab.test | `use-regex` + `rewrite-target: /$2` on `/api(/\|$)(.*)` | +| `secure` | secure.lab.test | TLS secret + `force-ssl-redirect: "true"` | +| `legacy` | old.lab.test | `permanent-redirect: https://shop.lab.test/` | + +Baseline through ingress-nginx, so we know what "correct" looks like: + +```text +## plain: shop.lab.test /hostname +app-v1-56f5bd6b9-t46kf +## canary: 200 requests to shop.lab.test + 159 app-v1 + 41 app-v2 +## rewrite: api.lab.test/api/echo?msg=rewritten +rewritten [200] +## tls redirect: http://secure.lab.test/ +308 https://secure.lab.test/ +## permanent redirect: old.lab.test +301 https://shop.lab.test/ +``` + +41 out of 200 is about 20%. Good. + +## Step 1: inventory before you convert anything + +The converter will tell you what it couldn't translate. But you want the list *before* you run it, because that list is your real migration backlog. One jq one-liner gives you every ingress-nginx annotation per Ingress: + +```bash +$ kubectl get ingress -A -o json | jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)", (.metadata.annotations // {} | to_entries[] | select(.key|startswith("nginx.ingress.kubernetes.io/")) | " \(.key)=\(.value)")' +shop/api + nginx.ingress.kubernetes.io/rewrite-target=/$2 + nginx.ingress.kubernetes.io/use-regex=true +shop/legacy + nginx.ingress.kubernetes.io/permanent-redirect=https://shop.lab.test/ +shop/secure + nginx.ingress.kubernetes.io/force-ssl-redirect=true +shop/web +shop/web-canary + nginx.ingress.kubernetes.io/canary=true + nginx.ingress.kubernetes.io/canary-weight=20 +``` + +And a frequency count, which is the one to paste into the planning ticket: + +```bash +$ kubectl get ingress -A -o json | jq -r '[.items[].metadata.annotations // {} | keys[] | select(startswith("nginx.ingress.kubernetes.io/"))] | group_by(.) | map("\(length) \(.[0])") | .[]' +1 nginx.ingress.kubernetes.io/canary +1 nginx.ingress.kubernetes.io/canary-weight +1 nginx.ingress.kubernetes.io/force-ssl-redirect +1 nginx.ingress.kubernetes.io/permanent-redirect +1 nginx.ingress.kubernetes.io/rewrite-target +1 nginx.ingress.kubernetes.io/use-regex +``` + +In a real cluster, also grep for `configuration-snippet` and `server-snippet`. Raw NGINX config has no Gateway API equivalent at all. Each of those means a conversation with whoever wrote it, ideally before the coffee runs out. + +## Step 2: convert with ingress2gateway + +[ingress2gateway](https://github.com/kubernetes-sigs/ingress2gateway) reads Ingresses from the cluster (or from files with `--input-file`) and prints Gateway API resources. It doesn't touch the cluster, so it's safe to run against production with a read-only kubeconfig. + +```bash +$ ingress2gateway print --providers ingress-nginx -A > /lab/out/gw-standard.yaml; echo; wc -l /lab/out/gw-standard.yaml +``` + +What lands in the file: one `Gateway` named `nginx` with a listener per hostname (HTTP for all five, plus HTTPS for secure.lab.test referencing `secure-tls`), and one HTTPRoute per Ingress/host. The useful part is stderr, trimmed here to the warnings that matter: + +```text +│ Unsupported annotation nginx.ingress.kubernetes.io/force-ssl-redirect +│ object: : shop/secure +│ Using case-insensitive regex path matches. You may want to change this. +│ object: HTTPRoute: shop/api-api-lab-test +│ Failed to apply metadata.annotations."nginx.ingress.kubernetes.io/rewrite-target" from rewrite-target from Ingress shop/api: Path rewrites with capture group references are not supported +│ Gateway API does not support configuring URL normalization (RFC 3986, Section 6). Please check if this matters for your use case and consult implementation-specific details. +``` + +There were also three warnings that ingress-nginx serves HTTPS with its self-signed default certificate for hosts that have no TLS block (shop, api, old), and that this "will not be translated". If someone out there is using `https://api...` and clicking through a certificate warning, that stops working after the move. Probably for the best. + +How each case came out: + +**Canary weights: translated well.** The two Ingresses for `shop.lab.test` were merged into one rule with weighted backends: + +```yaml + rules: + - backendRefs: + - name: app-v1 + port: 80 + weight: 80 + - name: app-v2 + port: 80 + weight: 20 +``` + +That is nicer than the annotation version. The split is part of the route itself, not a second "shadow" Ingress. + +**Permanent redirect: translated.** A `RequestRedirect` filter with `hostname: shop.lab.test`, `scheme: https`, `statusCode: 301` and `replaceFullPath: /`. + +**TLS + HTTP→HTTPS: translated, despite the warning.** It said `force-ssl-redirect` was unsupported, but it still generated a separate HTTPRoute on port 80 with a `RequestRedirect` (`scheme: https`, `statusCode: 308`), because ingress-nginx redirects TLS hosts by default anyway. Read the warnings, then read the YAML. They don't always agree. + +**Regex + rewrite: not translated.** The route kept a match of `RegularExpression` with value `(?i)/api(/|$)(.*).*` and dropped the rewrite entirely. Hold that thought. + +I also tried `--emitter envoy-gateway --allow-experimental-gw-api`. For these five Ingresses, the output was the same apart from adding `name: rule-0` to each rule. The capture-group rewrite failed the same way. Emitters matter more once you have implementation-specific annotations (timeouts, rate limits, auth). + +## Step 3: pick an implementation (without falling in love) + +Gateway API is a spec. You still need something that implements it, and the [official implementations page](https://gateway-api.sigs.k8s.io/implementations/) lists conformance per version. Here's how I'd choose: + +- **Conformance** with the Gateway API version you plan to run, including the extended features you actually use (weights, redirects, URL rewrite). +- **What you already operate.** If you run Istio or Cilium, their Gateway API support is the obvious first candidate, and you avoid adding another data plane. +- **Governance and release cadence.** After this whole episode, "who maintains this and how many of them are there" is a fair question. +- **Escape hatches** for the things the spec doesn't cover yet. They will be vendor-specific CRDs, so keep them few and well documented. + +I used Envoy Gateway for the lab because it's an Envoy sub-project, it installs as a single Helm chart, and it's a plain standalone gateway with no mesh attached. That keeps the experiment small. It is not a recommendation over the others. The Gateway and HTTPRoute objects in this post are portable, which is the whole point. + +```bash +$ helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.9.2 -n envoy-gateway-system --create-namespace --wait --timeout 5m +``` + +ingress2gateway set `gatewayClassName: nginx`, which would be a confusing name for a GatewayClass that runs Envoy. So I created a class called `envoy-gateway` (controllerName `gateway.envoyproxy.io/gatewayclass-controller`) and changed the reference with sed before applying: + +```bash +$ sed 's/gatewayClassName: nginx/gatewayClassName: envoy-gateway/' /lab/out/gw-standard.yaml > /lab/out/gw-envoy-class.yaml && grep -n gatewayClassName /lab/out/gw-envoy-class.yaml && kubectl apply -f /lab/out/gw-envoy-class.yaml +9: gatewayClassName: envoy-gateway +gateway.gateway.networking.k8s.io/nginx created +httproute.gateway.networking.k8s.io/secure-secure-lab-test created +httproute.gateway.networking.k8s.io/web-shop-lab-test created +httproute.gateway.networking.k8s.io/secure-secure-lab-test-http created +httproute.gateway.networking.k8s.io/api-api-lab-test created +httproute.gateway.networking.k8s.io/legacy-old-lab-test created +``` + +## Step 4: test, and distrust the 200s + +Same test script, now pointed at Envoy Gateway's NodePorts: + +```text +## canary: 200 requests to shop.lab.test + 159 app-v1 + 41 app-v2 +## rewrite: api.lab.test/api/echo?msg=rewritten +NOW: 2026-10-13 11:21:03.194588365 +0000 UTC m=+307.558810909 [200] +## tls redirect: http://secure.lab.test/ +308 https://secure.lab.test/ +## permanent redirect: old.lab.test +301 https://shop.lab.test/ +``` + +Redirects match and the canary split matches. And the rewrite case returns **HTTP 200 with the wrong body**. Without the rewrite, the backend received `/api/echo` and answered with its catch-all handler. A health check that only looks at status codes would call this a success. Compare response bodies, not just status codes. + +The fix is the standard `URLRewrite` filter. This is the moment to drop the regex too. `/api(/|$)(.*)` + `/$2` is just a long way of writing "strip the `/api` prefix": + +```yaml + rules: + - matches: + - path: + type: PathPrefix + value: /api + filters: + - type: URLRewrite + urlRewrite: + path: + type: ReplacePrefixMatch + replacePrefixMatch: / + backendRefs: + - name: app-v1 + port: 80 +``` + +```bash +$ kubectl apply -f api-route-fixed.yaml && sleep 5 && curl -s -w ' [%{http_code}]' -H 'Host: api.lab.test' 'http://gwlab-control-plane:30282/api/echo?msg=rewritten'; echo; curl -s -w ' [%{http_code}]' -H 'Host: api.lab.test' http://gwlab-control-plane:30282/api/hostname; echo +httproute.gateway.networking.k8s.io/api-api-lab-test configured +rewritten [200] +app-v1-56f5bd6b9-t46kf [200] +``` + +If your rewrites really need capture groups in the middle of a path, the standard API won't do it, and you're into implementation-specific extensions. Count those during the inventory step, not on cutover night. + +My first TLS test failed against **both** controllers with curl's `[000]`. I briefly blamed the certificate. The actual cause: `getent hosts` returned the node's IPv6 address, and the NodePorts were only listening on IPv4. With `getent ahostsv4`, both worked: + +```text +--- ingress-nginx +app-v1-56f5bd6b9-t46kf [200] +--- envoy gateway +app-v1-56f5bd6b9-t46kf [200] +* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS +* subject: CN=secure.lab.test +``` + +Lesson: when everything fails at once, suspect the test before the system. + +## The gotcha I didn't expect: canary scope + +While checking the rewrite on ingress-nginx, some `api.lab.test` requests answered with an `app-v2` pod name. No Ingress for `api.lab.test` points at app-v2. So I sent 100 requests to each controller: + +![Canary scope: ingress-nginx vs Envoy Gateway](/img/blog/migrate-ingress-nginx-to-gateway-api-hands-on/canary-scope.png) + +On ingress-nginx, 23 of 100 requests to a *different hostname* went to the canary. On Envoy Gateway, all 100 went to app-v1, because the weights live in the `shop.lab.test` route and nowhere else. My reading is that ingress-nginx attaches the canary to the backend service (`app-v1:80`), not to the host. So every Ingress that shares that backend gets the split too. I haven't dug through the controller source to confirm it, so treat that as a hypothesis. The behaviour itself is in the logs. + +Why this matters for the migration: if anyone relied on this, maybe without knowing it, the Gateway API version is *more* correct and *different*. Traffic graphs per host will shift on cutover day. Write that in the change ticket before someone else discovers it in a dashboard. + +## Step 5: run both, cut over, keep the rollback warm + +Both controllers watch different APIs (`Ingress` with class `nginx` versus `Gateway`/`HTTPRoute`), so they can run side by side with no fighting: + +```bash +$ echo '--- ingress-nginx :30080'; curl -s -H 'Host: shop.lab.test' http://gwlab-control-plane:30080/hostname; echo; echo '--- envoy gateway :30282'; curl -s -H 'Host: shop.lab.test' http://gwlab-control-plane:30282/hostname; echo +--- ingress-nginx :30080 +app-v2-85d768d94d-9qb4p +--- envoy gateway :30282 +app-v1-56f5bd6b9-t46kf +``` + +(Different pods, same host: that's just the 80/20 split doing its job.) + +In production, the cutover is a DNS or load-balancer change: point the hostname at the Gateway's address, ideally with a low TTL that you lowered a day earlier. In the lab, I simulated "nginx is gone" by scaling it to zero: + +```bash +$ kubectl -n ingress-nginx scale deploy/ingress-nginx-controller --replicas=0 && kubectl -n ingress-nginx wait pod -l app.kubernetes.io/component=controller --for=delete --timeout=120s; curl -s -m 5 -o /dev/null -w 'nginx: %{http_code}\n' -H 'Host: shop.lab.test' http://gwlab-control-plane:30080/hostname; curl -s -m 5 -o /dev/null -w 'envoy: %{http_code}\n' -H 'Host: shop.lab.test' http://gwlab-control-plane:30282/hostname +deployment.apps/ingress-nginx-controller scaled +pod/ingress-nginx-controller-56b4f49c8b-czmgg condition met +nginx: 000 +envoy: 200 +``` + +And the rollback, which is the reason you don't delete the Ingress objects yet: + +```bash +$ kubectl -n ingress-nginx scale deploy/ingress-nginx-controller --replicas=1 && kubectl -n ingress-nginx rollout status deploy/ingress-nginx-controller --timeout=120s && sleep 5; curl -s -m 5 -w ' [%{http_code}]\n' -H 'Host: shop.lab.test' http://gwlab-control-plane:30080/hostname +deployment.apps/ingress-nginx-controller scaled +Waiting for deployment "ingress-nginx-controller" rollout to finish: 0 of 1 updated replicas are available... +deployment "ingress-nginx-controller" successfully rolled out +app-v1-56f5bd6b9-t46kf [200] +``` + +A rollback plan that fits on a sticky note: + +1. Keep the Ingress objects and the ingress-nginx release in place until the Gateway has handled real traffic for an agreed period. +2. Roll back by pointing DNS/LB back at the old address. Don't edit routes under pressure. +3. Keep the TLS secrets shared (both stacks referenced the same `secure-tls`). Then a rollback never depends on cert-manager timing. +4. Only then uninstall ingress-nginx and delete the Ingresses, in a separate, boring change. + +## Smaller gotchas from the lab + +- **`Programmed=False` doesn't always mean broken.** On kind, the Gateway reported `Programmed=False AddressNotAssigned: No addresses have been assigned to the Gateway`, because the LoadBalancer Service stayed ``, while traffic through the NodePort worked fine. All HTTPRoutes were `Accepted=True ResolvedRefs=True`. In the cloud this condition means something. On bare metal, check how addresses get assigned (MetalLB, cloud LB, etc.). +- **Regex is case-insensitive after conversion** (`(?i)`), because NGINX matched it that way. Decide whether you want to keep that. +- **Generated names** (`nginx`, `api-api-lab-test`, `secure-secure-lab-test-http`) are fine for a first pass. Rename them before they end up in Git forever. +- **Ownership changes.** With Gateway API, the Gateway (listeners, certs) can belong to the platform team while app teams own their HTTPRoutes. That's a feature, but agree who owns what *before* migrating, or you'll just recreate the old free-for-all with more YAML. + +## Wrapping up + +The conversion tool got me most of the way. Weights and redirects came through cleanly. The rewrite came through silently wrong. And the canary behaviour changed in a way that's arguably a fix. That ratio sounds about right for real clusters too: the tooling saves you days, and the inventory and the body-checking tests save you the incident. + +If you'd like a second pair of eyes on a cluster before (or after) the move, that's the kind of [Kubernetes work we do at Agency 42](https://agency42.se/en/kubernetes). + +--- + +*Commands verified on a kind lab cluster (kind v0.33.0, Kubernetes v1.37.0, ingress-nginx controller v1.15.1, ingress2gateway 1.2.0, Envoy Gateway v1.9.2 with Gateway API v1.6.1 CRDs) on 13 Oct 2026. Drafted with AI assistance; reviewed and approved by Johan Kuylenstierna.* diff --git a/public/_redirects b/public/_redirects index fcfe7372b..bf7d0c6a1 100644 --- a/public/_redirects +++ b/public/_redirects @@ -156,6 +156,7 @@ /blog/managing-your-operating-system-with-package-managers /managing-your-operating-system-with-package-managers 301! /blog/mastering-kubernetes-costs-from-monitoring-to-automation /mastering-kubernetes-costs-from-monitoring-to-automation 301! /blog/microservices /microservices 301! +/blog/migrate-ingress-nginx-to-gateway-api-hands-on /migrate-ingress-nginx-to-gateway-api-hands-on 301! /blog/mlxcel-rust-native-inference-engine-tested-on-m1-max /mlxcel-rust-native-inference-engine-tested-on-m1-max 301! /blog/moving-code-between-git-repositories-with-copybara /moving-code-between-git-repositories-with-copybara 301! /blog/multi-stage-docker-build /multi-stage-docker-build 301! @@ -387,6 +388,7 @@ /managing-your-operating-system-with-package-managers /blog/managing-your-operating-system-with-package-managers 200! /mastering-kubernetes-costs-from-monitoring-to-automation /blog/mastering-kubernetes-costs-from-monitoring-to-automation 200! /microservices /blog/microservices 200! +/migrate-ingress-nginx-to-gateway-api-hands-on /blog/migrate-ingress-nginx-to-gateway-api-hands-on 200! /mlxcel-rust-native-inference-engine-tested-on-m1-max /blog/mlxcel-rust-native-inference-engine-tested-on-m1-max 200! /moving-code-between-git-repositories-with-copybara /blog/moving-code-between-git-repositories-with-copybara 200! /multi-stage-docker-build /blog/multi-stage-docker-build 200! diff --git a/public/_worker.js b/public/_worker.js index 485e92b1e..bf47784b0 100644 --- a/public/_worker.js +++ b/public/_worker.js @@ -799,7 +799,7 @@ async function handleNewsletterApi(request, env) { const KUBESIMPLIFY_ROUTES = new Set(['/about', '/workshops', '/partnerships', '/resources', '/products', '/learn', '/privacy']); const KUBESIMPLIFY_PREFIXES = ['/products/', '/learn/']; -const BLOG_SLUGS = new Set(["10-things-you-might-not-know-about-k9s","12-practical-grep-command-examples-in-linux","a-beginners-guide-to-dualbooting-windows-with-ubuntu-part-1","a-beginners-guide-to-dualbooting-windows-with-ubuntu-part-2","a-complete-walk-through-of-devops","a-kubeconfig-for-gke-that-doesnt-need-gcloud","a-simple-way-to-structure-your-terraform-code","a-simplified-guide-to-yaml","about-my-pdf-editor-project","ai-infrastructure-for-platform-engineers","an-overview-of-gitops-and-argocd","announcing-buildsafe","api-response-in-go","arkade","automate-repetitive-tasks-shell-scripting","automated-github-releases-with-github-actions-and-conventional-commits","avoid-overspending-with-kubecost","aws-elastic-cloud-compute","bake-your-container-images-with-bake","become-a-hashicorp-certified-terraform-associate-preparation-guide","best-devops-tools-2025","bonsai-27b-rtx-pro-6000-dgx-spark","breaking-down-docker","building-a-zero-cve-strategy","building-apigateway-with-lambda-using-pulumi","certified-kubernetes-security-specialist-cks-2022-exam-guide","cicd-pipeline-github-actions-with-aws-ecs","ckad-exam-april-2022","claude-code-leak-what-the-source-actually-teaches","clawspark-your-private-openclaw-ai-assistant-that-never-phones-home","cloud-computing","cloud-native-buildpacks-concepts","confidential-containers-running-on-kubernetes","container-and-kubernetes-security","controlling-mcp-tools-with-agentgateway-on-kubernetes","coolify","creating-multi-node-kubernetes-cluster-locally","day-1-the-local-llm-revolution-why-your-desk-just-became-the-new-datacenter","day-1-what-actually-happens-when-you-type-docker-run","day-2-anatomy-of-an-llm-inference-request-from-prompt-to-answer-step-by-step","day-2-your-images-are-a-supply-chain-and-it-s-probably-broken","day-3-stop-writing-dockerfiles-from-scratch","day-3-the-dgx-spark-unpacked-gb10-unified-memory-sm-121-and-the-one-reason-this-hardware-exists","day-4-breaking-isolation-on-purpose-volumes-networks-and-the-real-world","day-4-quantization-demystified-bf16-fp8-nvfp4-mxfp4-int4-gguf-and-why-it-all-matters","day-5-docker-compose-how-docker-actually-gets-used","day-5-local-llm-inference-engines-wrappers-and-what-to-pick","day-6-run-an-llm-on-your-laptop-with-docker","day-7-ship-it-and-what-comes-next","deploy-a-maven-project-on-a-tomcat-server-using-jenkins-and-aws","deploy-a-simple-server-using-aws-terraform","deploying-java-application-using-docker-and-kubernetes-devops-project","devin-outposts-on-kubernetes","ditch-the-overheating-laptop-supercharge-your-docker-workflow-with-docker-offload","diy-how-to-build-a-kubernetes-policy-engine","docker-captain-journey","docker-mcp-catalog","docker-networking-demystified","dynamic-mig-in-kubernetes-with-hami","embed-http-servers-in-wasm-with-rust-and-csharp","enhancing-runtime-security-with-falco-my-hands-on-experience","ephemeral-pull-request-environment-using-vcluster","essential-linux-commands-for-devops","event-driven-architecture-simplified-monolith-to-microservices","everything-you-need-to-know-about-docker-compose","everything-you-need-to-know-about-the-linux-ls-command","exploiting-metasploitable2-using-msfconsole-kali-linux-lab","firewall-a-networks-gatekeeper","four-pillars-of-observability-in-kubernetes","get-good-at-git","getting-started-with-kind-creating-a-multi-node-local-kubernetes-cluster","getting-started-with-ko-a-fast-container-image-builder-for-your-go-applications","getting-started-with-kyverno","git-and-github-a-beginners-guide","github-actions-101-what-are-github-actions-and-how-to-use-them-a-beginners-guide","gitops-demystified","ha-kubernetes","how-a-kubernetes-service-actually-works-and-all-5-types-you-need","how-get-started-with-hashicorp-vault","how-kubernetes-endpointslices-actually-work-and-why-endpoints-had-to-die","how-to-backup-kubernetes-with-kasten-community-edition","how-to-change-directory-in-shell-scripts","how-to-install-a-kubernetes-cluster-with-kubeadm-containerd-and-cilium-a-hands-on-guide","how-to-setup-your-ftp-server-in-linux","implementing-kubernetes-network-policies-a-comprehensive-guide","important-concepts-of-operating-systems","ing-switch-119-annotations-gateway-api-traefik-impact-ratings","ing-switch-migrate-from-ingress-nginx-to-traefik-or-gateway-api-in-minutes-not-days","inside-kueue-how-kubernetes-decides-what-runs-next","installing-prometheus-with-selinux","introducing-kiac-kubernetes-in-apple-containers","introducing-unikraft-lightweight-virtualization-using-unikernels","introduction-of-jenkins-pipeline","introduction-to-cicd-and-cicd-pipeline","introduction-to-cri","introduction-to-developer-platforms-with-gimlet","introduction-to-helm","introduction-to-jenkins","introduction-to-kubernetes","introduction-to-terraform","iptables-demo","istio-service-mesh","k8sgpt-tutorial-when-kubernetes-meets-ai","keptn-getting-started","ksctl-making-kubernetes-easy-across-clouds","kube-proxy-deep-dive","kube-scheduler-deep-dive","kubecon-cloudnativecon-north-america-2024-recap-themes-innovations-and-community-spirit","kubecon-cloudnativecon-rejekts-and-wasm-io-wrap-up-a-leap-into-the-future-with-webassembly-ai-and-sustainable-cloud-practices","kubectl-run-nginx-inside","kubeflow-machine-learning-on-kubernetes-part-1","kubeflow-notebooks-ml-experimentation-made-easier-part-2","kubeflow-pipelines-orchestrating-machine-learning-workflows-part-3","kubernetes-125-dockerd","kubernetes-126","kubernetes-access-control-with-authentication-authorization-admission-control","kubernetes-adoption-key-challenges-in-migrating-to-kubernetes","kubernetes-backup-using-cloudcasa","kubernetes-containerd-setup","kubernetes-crio","kubernetes-management-with-rust-a-dive-into-generic-client-go-controller-abstractions-and-crd-macros-with-kubers","kubernetes-observability-in-2026-with-openobserve","kubernetes-on-apple-macbooks-m-series","kubernetes-scheduling-the-complete-guide","kubernetes-v133-key-features-updates-and-what-you-need-to-know","kubernetes-v135-whats-new-whats-changing-and-what-you-should-know","kubesimplify-a-journey-to-remember","kubesimplify-at-wasmio-and-kubecon-eu-2024","kyverno-and-cosign","kyverno-cli","lets-learn-terraform","lets-simplify-golang-part-1","lets-simplify-golang-part-2","lets-simplify-golang-part-3","lets-talk-about-ansible","linux-boot-process-simplified","linux-system-directories-explained","llm-costs-and-observability-with-agentgateway-on-kubernetes","local-llm-glossary","managing-contexts-in-kubernetes-with-plugins","managing-your-operating-system-with-package-managers","mastering-kubernetes-costs-from-monitoring-to-automation","microservices","mlxcel-rust-native-inference-engine-tested-on-m1-max","moving-code-between-git-repositories-with-copybara","multi-stage-docker-build","multi-tenancy-in-2025-and-beyond","my-first-international-conference-open-source-summit-2022","my-journey-to-kubestronaut-on-kubernetes-10th-birthday","my-kubecon-euvirtual-experience","my-schedule-for-kubecon-cloudnativecon-eu-2022","navigating-through-cncf-landscape","nemotron-3-5-lightning-on-dgx-spark","nemotron3-on-dgx-spark","networking-fundamentals-for-devops","nexus-repository-manager-what-is-it-and-how-to-configure-it-on-a-digital-ocean-droplet","nudgebee-ai-sre-copilot-hands-on","nvcf-is-now-open-source-inside-nvidia-s-gpu-function-platform","operating-systems-101-essential-knowledge-for-devopssre-engineers","optimizing-kubernetes-costs-balancing-spot-and-on-demand-instances-with-topology-spread-constraints","optimizing-scalability-a-deep-dive-into-load-testing-with-locust-on-eks","package-managers-demystified","perform-crud-operations-on-kubernetes-using-golang","platform-engineering-demystified-navigating-the-basics","pods-in-kubernetes","practical-guide-to-kubernetes-api","progressive-rollouts-with-argo-cd-rollouts","prometheus-explained","pure-cilium-a-guide-for-local-load-balancing-and-bgp","quick-bites-of-fluxcd-health-assessment","qwen3-8-27b-on-dgx-spark","rancher-desktop-evolution","ready-for-wasm-day-2023","running-a-big-llm-across-multiple-gpus-with-vllm","running-qwen3-8-flash-next-on-dgx-spark-and-rtx-pro-6000","sharing-gpus-in-kubernetes-with-hami","simplified-introduction-to-bacalhau","slicing-gpus-in-kubernetes-with-nvidia-mig","speeding-up-using-microk8s","ssh-into-your-dgx-spark-from-anywhere-in-the-world-using-tailscale","starting-your-devops-journey-as-a-windows-user","statefulsets","supply-chain-security-using-slsa-part-1-fundamentals","supply-chain-security-using-slsa-part-2-the-framework","terraform-best-practices","testing-docker-ais-gordon-how-smart-is-it","the-complete-guide-to-the-dd-command-in-linux","the-secret-gems-behind-building-container-images-enter-buildkit-and-docker-buildx","the-ultimate-guide-to-audit-logging-in-kubernetes-from-setup-to-analysis","the-webassembly-course","tutorial-build-a-cloud-cost-monitoring-system-with-terraform-ansible-and-komiser","understanding-docker-desktop-all-in-one-platform-for-containers","understanding-etcd-in-kubernetes-a-beginners-guide","understanding-how-containers-work-behind-the-scenes","understanding-the-architecture-of-kubernetes-a-beginners-guide","understanding-the-ins-and-outs-of-git-using-github","wandler-local-openai-compatible-inference-transformersjs-webgpu","what-is-reproducibility-and-why-does-it-matter","what-is-shell-scripting","why-are-network-policies-in-kubernetes-so-hard-to-understand","why-devops-case-study","wtf-is-linux-shell-command-substitution","yours-kindly-drone","zero-trust-istio-sidecar-vs-ambient"]); +const BLOG_SLUGS = new Set(["10-things-you-might-not-know-about-k9s","12-practical-grep-command-examples-in-linux","a-beginners-guide-to-dualbooting-windows-with-ubuntu-part-1","a-beginners-guide-to-dualbooting-windows-with-ubuntu-part-2","a-complete-walk-through-of-devops","a-kubeconfig-for-gke-that-doesnt-need-gcloud","a-simple-way-to-structure-your-terraform-code","a-simplified-guide-to-yaml","about-my-pdf-editor-project","ai-infrastructure-for-platform-engineers","an-overview-of-gitops-and-argocd","announcing-buildsafe","api-response-in-go","arkade","automate-repetitive-tasks-shell-scripting","automated-github-releases-with-github-actions-and-conventional-commits","avoid-overspending-with-kubecost","aws-elastic-cloud-compute","bake-your-container-images-with-bake","become-a-hashicorp-certified-terraform-associate-preparation-guide","best-devops-tools-2025","bonsai-27b-rtx-pro-6000-dgx-spark","breaking-down-docker","building-a-zero-cve-strategy","building-apigateway-with-lambda-using-pulumi","certified-kubernetes-security-specialist-cks-2022-exam-guide","cicd-pipeline-github-actions-with-aws-ecs","ckad-exam-april-2022","claude-code-leak-what-the-source-actually-teaches","clawspark-your-private-openclaw-ai-assistant-that-never-phones-home","cloud-computing","cloud-native-buildpacks-concepts","confidential-containers-running-on-kubernetes","container-and-kubernetes-security","controlling-mcp-tools-with-agentgateway-on-kubernetes","coolify","creating-multi-node-kubernetes-cluster-locally","day-1-the-local-llm-revolution-why-your-desk-just-became-the-new-datacenter","day-1-what-actually-happens-when-you-type-docker-run","day-2-anatomy-of-an-llm-inference-request-from-prompt-to-answer-step-by-step","day-2-your-images-are-a-supply-chain-and-it-s-probably-broken","day-3-stop-writing-dockerfiles-from-scratch","day-3-the-dgx-spark-unpacked-gb10-unified-memory-sm-121-and-the-one-reason-this-hardware-exists","day-4-breaking-isolation-on-purpose-volumes-networks-and-the-real-world","day-4-quantization-demystified-bf16-fp8-nvfp4-mxfp4-int4-gguf-and-why-it-all-matters","day-5-docker-compose-how-docker-actually-gets-used","day-5-local-llm-inference-engines-wrappers-and-what-to-pick","day-6-run-an-llm-on-your-laptop-with-docker","day-7-ship-it-and-what-comes-next","deploy-a-maven-project-on-a-tomcat-server-using-jenkins-and-aws","deploy-a-simple-server-using-aws-terraform","deploying-java-application-using-docker-and-kubernetes-devops-project","devin-outposts-on-kubernetes","ditch-the-overheating-laptop-supercharge-your-docker-workflow-with-docker-offload","diy-how-to-build-a-kubernetes-policy-engine","docker-captain-journey","docker-mcp-catalog","docker-networking-demystified","dynamic-mig-in-kubernetes-with-hami","embed-http-servers-in-wasm-with-rust-and-csharp","enhancing-runtime-security-with-falco-my-hands-on-experience","ephemeral-pull-request-environment-using-vcluster","essential-linux-commands-for-devops","event-driven-architecture-simplified-monolith-to-microservices","everything-you-need-to-know-about-docker-compose","everything-you-need-to-know-about-the-linux-ls-command","exploiting-metasploitable2-using-msfconsole-kali-linux-lab","firewall-a-networks-gatekeeper","four-pillars-of-observability-in-kubernetes","get-good-at-git","getting-started-with-kind-creating-a-multi-node-local-kubernetes-cluster","getting-started-with-ko-a-fast-container-image-builder-for-your-go-applications","getting-started-with-kyverno","git-and-github-a-beginners-guide","github-actions-101-what-are-github-actions-and-how-to-use-them-a-beginners-guide","gitops-demystified","ha-kubernetes","how-a-kubernetes-service-actually-works-and-all-5-types-you-need","how-get-started-with-hashicorp-vault","how-kubernetes-endpointslices-actually-work-and-why-endpoints-had-to-die","how-to-backup-kubernetes-with-kasten-community-edition","how-to-change-directory-in-shell-scripts","how-to-install-a-kubernetes-cluster-with-kubeadm-containerd-and-cilium-a-hands-on-guide","how-to-setup-your-ftp-server-in-linux","implementing-kubernetes-network-policies-a-comprehensive-guide","important-concepts-of-operating-systems","ing-switch-119-annotations-gateway-api-traefik-impact-ratings","ing-switch-migrate-from-ingress-nginx-to-traefik-or-gateway-api-in-minutes-not-days","inside-kueue-how-kubernetes-decides-what-runs-next","installing-prometheus-with-selinux","introducing-kiac-kubernetes-in-apple-containers","introducing-unikraft-lightweight-virtualization-using-unikernels","introduction-of-jenkins-pipeline","introduction-to-cicd-and-cicd-pipeline","introduction-to-cri","introduction-to-developer-platforms-with-gimlet","introduction-to-helm","introduction-to-jenkins","introduction-to-kubernetes","introduction-to-terraform","iptables-demo","istio-service-mesh","k8sgpt-tutorial-when-kubernetes-meets-ai","keptn-getting-started","ksctl-making-kubernetes-easy-across-clouds","kube-proxy-deep-dive","kube-scheduler-deep-dive","kubecon-cloudnativecon-north-america-2024-recap-themes-innovations-and-community-spirit","kubecon-cloudnativecon-rejekts-and-wasm-io-wrap-up-a-leap-into-the-future-with-webassembly-ai-and-sustainable-cloud-practices","kubectl-run-nginx-inside","kubeflow-machine-learning-on-kubernetes-part-1","kubeflow-notebooks-ml-experimentation-made-easier-part-2","kubeflow-pipelines-orchestrating-machine-learning-workflows-part-3","kubernetes-125-dockerd","kubernetes-126","kubernetes-access-control-with-authentication-authorization-admission-control","kubernetes-adoption-key-challenges-in-migrating-to-kubernetes","kubernetes-backup-using-cloudcasa","kubernetes-containerd-setup","kubernetes-crio","kubernetes-management-with-rust-a-dive-into-generic-client-go-controller-abstractions-and-crd-macros-with-kubers","kubernetes-observability-in-2026-with-openobserve","kubernetes-on-apple-macbooks-m-series","kubernetes-scheduling-the-complete-guide","kubernetes-v133-key-features-updates-and-what-you-need-to-know","kubernetes-v135-whats-new-whats-changing-and-what-you-should-know","kubesimplify-a-journey-to-remember","kubesimplify-at-wasmio-and-kubecon-eu-2024","kyverno-and-cosign","kyverno-cli","lets-learn-terraform","lets-simplify-golang-part-1","lets-simplify-golang-part-2","lets-simplify-golang-part-3","lets-talk-about-ansible","linux-boot-process-simplified","linux-system-directories-explained","llm-costs-and-observability-with-agentgateway-on-kubernetes","local-llm-glossary","managing-contexts-in-kubernetes-with-plugins","managing-your-operating-system-with-package-managers","mastering-kubernetes-costs-from-monitoring-to-automation","microservices","migrate-ingress-nginx-to-gateway-api-hands-on","mlxcel-rust-native-inference-engine-tested-on-m1-max","moving-code-between-git-repositories-with-copybara","multi-stage-docker-build","multi-tenancy-in-2025-and-beyond","my-first-international-conference-open-source-summit-2022","my-journey-to-kubestronaut-on-kubernetes-10th-birthday","my-kubecon-euvirtual-experience","my-schedule-for-kubecon-cloudnativecon-eu-2022","navigating-through-cncf-landscape","nemotron-3-5-lightning-on-dgx-spark","nemotron3-on-dgx-spark","networking-fundamentals-for-devops","nexus-repository-manager-what-is-it-and-how-to-configure-it-on-a-digital-ocean-droplet","nudgebee-ai-sre-copilot-hands-on","nvcf-is-now-open-source-inside-nvidia-s-gpu-function-platform","operating-systems-101-essential-knowledge-for-devopssre-engineers","optimizing-kubernetes-costs-balancing-spot-and-on-demand-instances-with-topology-spread-constraints","optimizing-scalability-a-deep-dive-into-load-testing-with-locust-on-eks","package-managers-demystified","perform-crud-operations-on-kubernetes-using-golang","platform-engineering-demystified-navigating-the-basics","pods-in-kubernetes","practical-guide-to-kubernetes-api","progressive-rollouts-with-argo-cd-rollouts","prometheus-explained","pure-cilium-a-guide-for-local-load-balancing-and-bgp","quick-bites-of-fluxcd-health-assessment","qwen3-8-27b-on-dgx-spark","rancher-desktop-evolution","ready-for-wasm-day-2023","running-a-big-llm-across-multiple-gpus-with-vllm","running-qwen3-8-flash-next-on-dgx-spark-and-rtx-pro-6000","sharing-gpus-in-kubernetes-with-hami","simplified-introduction-to-bacalhau","slicing-gpus-in-kubernetes-with-nvidia-mig","speeding-up-using-microk8s","ssh-into-your-dgx-spark-from-anywhere-in-the-world-using-tailscale","starting-your-devops-journey-as-a-windows-user","statefulsets","supply-chain-security-using-slsa-part-1-fundamentals","supply-chain-security-using-slsa-part-2-the-framework","terraform-best-practices","testing-docker-ais-gordon-how-smart-is-it","the-complete-guide-to-the-dd-command-in-linux","the-secret-gems-behind-building-container-images-enter-buildkit-and-docker-buildx","the-ultimate-guide-to-audit-logging-in-kubernetes-from-setup-to-analysis","the-webassembly-course","tutorial-build-a-cloud-cost-monitoring-system-with-terraform-ansible-and-komiser","understanding-docker-desktop-all-in-one-platform-for-containers","understanding-etcd-in-kubernetes-a-beginners-guide","understanding-how-containers-work-behind-the-scenes","understanding-the-architecture-of-kubernetes-a-beginners-guide","understanding-the-ins-and-outs-of-git-using-github","wandler-local-openai-compatible-inference-transformersjs-webgpu","what-is-reproducibility-and-why-does-it-matter","what-is-shell-scripting","why-are-network-policies-in-kubernetes-so-hard-to-understand","why-devops-case-study","wtf-is-linux-shell-command-substitution","yours-kindly-drone","zero-trust-istio-sidecar-vs-ambient"]); export default { async fetch(request, env) { diff --git a/public/img/blog/migrate-ingress-nginx-to-gateway-api-hands-on/canary-scope.png b/public/img/blog/migrate-ingress-nginx-to-gateway-api-hands-on/canary-scope.png new file mode 100644 index 000000000..fa2f41982 Binary files /dev/null and b/public/img/blog/migrate-ingress-nginx-to-gateway-api-hands-on/canary-scope.png differ diff --git a/vercel.json b/vercel.json index 376bb8b85..35d003ae4 100644 --- a/vercel.json +++ b/vercel.json @@ -771,6 +771,11 @@ "destination": "https://blog.kubesimplify.com/microservices", "permanent": true }, + { + "source": "/blog/migrate-ingress-nginx-to-gateway-api-hands-on", + "destination": "https://blog.kubesimplify.com/migrate-ingress-nginx-to-gateway-api-hands-on", + "permanent": true + }, { "source": "/blog/mlxcel-rust-native-inference-engine-tested-on-m1-max", "destination": "https://blog.kubesimplify.com/mlxcel-rust-native-inference-engine-tested-on-m1-max", @@ -2649,6 +2654,17 @@ } ] }, + { + "source": "/migrate-ingress-nginx-to-gateway-api-hands-on", + "destination": "https://blog.kubesimplify.com/migrate-ingress-nginx-to-gateway-api-hands-on", + "permanent": true, + "has": [ + { + "type": "host", + "value": "kubesimplify.com" + } + ] + }, { "source": "/mlxcel-rust-native-inference-engine-tested-on-m1-max", "destination": "https://blog.kubesimplify.com/mlxcel-rust-native-inference-engine-tested-on-m1-max",