Skip to main content
BlogDevOps

Retirement ingress-nginx: Migrasi ke Contour atau Gateway API

Arti retirement ingress-nginx Maret 2026, perbandingan Contour dan Gateway API, lima keanehan yang memicu 404, dan langkah yang bisa dilakukan hari Senin.

Matthews Wong

Software Engineer

Oktober 202612 menit baca
Retirement ingress-nginx: Migrasi ke Contour atau Gateway API

Ringkasan Utama

Komunitas Kubernetes mem-pensiunkan controller ingress-nginx pada Maret 2026: tidak ada lagi release, bugfix, atau perbaikan keamanan, meski deployment yang berjalan tetap bekerja. Ingress API sendiri tetap ada. Anda bisa pindah ke Ingress controller lain seperti Contour dengan menulis ulang annotation nginx, atau ke Gateway API dengan menulis ulang route dan menguji ulang setiap keanehan perilaku.

Pengecekannya hanya satu perintah, dan itulah alasan tulisan ini ada. Jalankan kubectl get pods dengan selector app.kubernetes.io/name=ingress-nginx di semua namespace, dengan izin cluster administrator. Jika ada pod yang muncul, Anda menjalankan software yang berhenti dirawat oleh proyek Kubernetes sejak Maret 2026. Tidak ada yang rusak, dan di situlah jebakannya: Steering Committee memperingatkan bahwa deployment yang ada tetap berjalan, sehingga Anda bisa baru sadar setelah diserang.

# Needs cluster administrator permissions. Any output means you run ingress-nginx.
kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx

Saya membaca ulang sumber primer pada 17 Oktober 2026: pengumuman retirement Kubernetes dan pernyataan Steering Committee, tulisan engineer Microsoft tentang lima perilaku ingress-nginx, deprecation externalIPs pada Service, panduan migrasi CNCF bulan Juli, README ingress-nginx, dan dokumentasi Contour. Saya belum menjalankan migrasi ini di cluster production untuk tulisan ini, jadi perilaku di bawah dikutip, bukan diukur, dan saya tandai di mana sumbernya berhenti.

Apa yang dipensiunkan, dan apa yang tidak

SIG Network dan Security Response Committee mengumumkan retirement ini pada 11 November 2025. Best-effort maintenance berjalan sampai Maret 2026. Setelahnya tidak ada release, bugfix, maupun update untuk celah keamanan yang ditemukan kemudian. Repository GitHub menjadi read-only, Helm chart dan container image tetap tersedia, dan deployment yang ada tidak rusak.

Yang tidak dipensiunkan adalah Ingress API. Panduan CNCF menyebut API ini tetap didukung dan banyak dipakai; hanya controller ingress-nginx buatan komunitas yang mencapai end of life. Pembedaan ini melahirkan dua jalur migrasi: tetap di Ingress dan ganti controller, atau tinggalkan Ingress untuk Gateway API. Jangan tertukar dengan NGINX Ingress dari F5, yang oleh tulisan Microsoft disebut controller terpisah dan tidak berkaitan, hanya sama-sama memakai NGINX sebagai data plane.

Skalanya menjelaskan nada pernyataannya. Pernyataan Steering Committee mengutip riset internal Datadog bahwa sekitar separuh lingkungan cloud native bergantung pada ingress-nginx, menyebut tidak ada alternatif yang menjadi pengganti drop-in langsung, dan menegaskan perubahan ini tidak boleh diabaikan, dianggap remeh, atau ditunda sampai menit terakhir.

Mengapa berakhir, dan artinya bagi threat model Anda

Pengumumannya blak-blakan soal penyebabnya. Opsi yang dulu dianggap membantu, seperti menambahkan directive konfigurasi NGINX sembarang lewat snippet annotation, kini dianggap celah keamanan serius, dan bertahun-tahun proyek ini hanya dikerjakan satu atau dua orang di waktu luang mereka. Penggantinya, InGate, tidak pernah cukup matang dan juga akan dipensiunkan.

README di branch main memuat peringatan kedua yang sudah ada sebelum retirement: jangan dipakai di instalasi production multi-tenant, karena proyek ini mengasumsikan siapa pun yang bisa membuat objek Ingress adalah administrator cluster. Jika platform Anda membiarkan tim aplikasi membuat Ingress, asumsi itu sudah salah sebelum Maret.

Ada sinyal yang lebih sepi di README yang sama. Saat saya membacanya pada 17 Oktober 2026, tabel supported versions menempatkan v1.15.1 di urutan pertama, diuji terhadap Kubernetes 1.31 sampai 1.35, dan tidak ada baris untuk Kubernetes 1.36 atau yang lebih baru. Di cluster yang lebih baru, Anda menjalankan controller yang tidak akan diuji terhadap versi Anda.

Dua jalur berdampingan

Panduan CNCF membingkai pilihan ini sebagai perpindahan lateral ke Ingress controller berbasis Envoy, dengan Contour sebagai contoh, versus pindah ke Gateway API. Saya mempertahankan empat faktornya dan menambah baris kelima tentang apa yang benar-benar berhenti bekerja.

FaktorJalur A: Contour di Ingress APIJalur B: Gateway API
Usaha migrasiRendah sampai sedang: menerjemahkan annotationTinggi: menulis ulang manifest routing
Model operasionalSatu pemilik: Ops mengelola definisi monolitikBerbasis peran: Ops mengelola Gateway, developer mengelola HTTPRoute
Ketahanan ke depanRendah: Ingress API sudah feature-frozenTinggi: pengembangan upstream aktif
KapabilitasSangat bergantung pada annotation proprietaryFitur lanjutan ada di spec inti
Apa yang berhenti bekerjaSetiap annotation nginx.ingress.kubernetes.ioObjek Ingress itu sendiri, ditambah perilaku yang hanya ada sebagai keanehan ingress-nginx

Dua catatan. Penulis panduan itu bekerja di konsultan yang menjual jasa migrasi, dan panduannya tidak memberi estimasi durasi maupun ID CVE, jadi anggap penilaian usahanya sebagai opini. Kedua, panduan itu menyebut jalur Contour sebagai stopgap yang membeli waktu untuk perencanaan. Pembacaan saya: karena Ingress API sudah feature-frozen, memilih A lalu belakangan ingin Gateway API berarti migrasi dua kali, jadi A hanya masuk akal bila waktu perencanaannya nyata. Baris kelima adalah tambahan saya, dari peringatan annotation di panduan itu dan tulisan Microsoft yang dibahas di bawah.

Jalur A: ganti controller, pertahankan objek Ingress

Jalur A mempertahankan resource Ingress dan mengganti siapa yang melayaninya. Contour memfilter Ingress berdasarkan class: dengan flag --ingress-class-name ia hanya melayani Ingress yang class-nya cocok, dan flag itu menerima daftar dipisah koma. Dokumentasinya menyebut ini berguna saat bermigrasi dari controller lain. Dua controller bisa berjalan berdampingan, masing-masing di balik load balancer sendiri, dan Anda memindahkan Ingress satu per satu dengan mengganti class-nya.

# Contour runs beside ingress-nginx and serves only the class it is told to.
# The flag takes a comma-separated list: --ingress-class-name=contour,contour-canary
contour serve --ingress-class-name=contour

# Before: served by ingress-nginx. Both annotations stop working on Contour.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: "/uuid"
spec:
  ingressClassName: nginx
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: "/IP"
        pathType: ImplementationSpecific
        backend:
          service:
            name: shop
            port:
              number: 8000

# After: the same object, moved by changing the class and dropping the
# nginx annotations. Re-express the rewrite with Contour's HTTPProxy.
metadata:
  name: shop
  annotations:
    projectcontour.io/response-timeout: "30s"   # a Contour annotation, not an nginx one
spec:
  ingressClassName: contour

Biayanya ada di annotation. Panduan CNCF menyebut setiap annotation nginx.ingress.kubernetes.io berhenti bekerja dan harus diterjemahkan manual ke padanan Contour atau dibangun ulang dengan CRD Contour. Referensi annotation Contour hanya mencantumkan set pendek untuk objek Ingress, antara lain ingress.kubernetes.io/force-ssl-redirect, kubernetes.io/ingress.allow-http, projectcontour.io/response-timeout, serta pengaturan retry num-retries, per-try-timeout, dan retry-on. Dokumen itu menambahkan bahwa fitur yang dulu diatur lewat annotation menjadi fitur utama di API HTTPProxy, yang disebutnya antarmuka yang lebih robust.

Path matching juga berubah makna. Contour mendokumentasikan bahwa ImplementationSpecific mempertahankan perilaku lamanya: path yang mengandung salah satu karakter ^+*[]% adalah regex dan selain itu adalah string prefix. Exact dan Prefix mengikuti spec, dan Prefix mencocokkan per segmen, sehingga /foo/bar cocok dengan /foo/bar/baz tetapi tidak dengan /foo/barbaz. Bandingkan dengan lima perilaku ingress-nginx di bawah sebelum mengasumsikan path Anda aman.

Set ingressClassName pada setiap Ingress yang Anda migrasikan. Tanpa flag, Contour menerima Ingress yang menyebut class contour atau tidak menyebut class sama sekali, dan Ingress tanpa class annotation dilayani semua controller, sehingga Ingress tanpa class bisa dilayani dua kali.

Jalur B: Gateway API, tempat peran dipisah

Gateway API memisahkan apa yang di Ingress disimpan dalam satu objek. Operator memiliki Gateway beserta listener dan alamatnya. Developer memiliki HTTPRoute yang menempel ke Gateway lewat parentRefs. Panduan CNCF memuji model ini karena membakukan traffic splitting, header matching tingkat lanjut, dan routing lintas namespace yang aman, hal-hal yang di ingress-nginx dikerjakan tim lewat annotation.

# Owned by the platform team. The listener is mandatory in a real Gateway;
# Kubernetes' externalIPs example omits it to stay short.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: infra
spec:
  gatewayClassName: <your-gateway-class>   # set by the implementation you install
  addresses:
  - type: IPAddress
    value: "192.0.2.4"                       # documentation address; use yours
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All                            # who may attach HTTPRoutes
---
# Owned by the application team, in their own namespace.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop
  namespace: shop
spec:
  hostnames:
  - shop.example.com
  parentRefs:
  - name: shared-gateway
    namespace: infra
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: shop
      port: 8000

Gateway API 1.5, dirilis 27 Februari 2026 menurut blog Kubernetes, menaikkan ListenerSet, yang memungkinkan developer aplikasi mengelola sertifikat TLS, dan filter CORS pada HTTPRoute ke status graduated. Periksa mana yang didukung implementasi pilihan Anda sebelum merencanakan di atasnya; tulisan yang saya baca tidak membandingkan implementasi fitur demi fitur.

Karena Gateway dirancang untuk administrator cluster, RBAC bisa membatasi siapa yang boleh menempelkan alamat, itulah sebabnya tulisan Kubernetes tentang externalIPs menawarkan Gateway sebagai salah satu pengganti field tersebut. Untuk menerjemahkan manifest yang ada, tulisan Microsoft menyebut SIG Network menambahkan annotation ingress-nginx yang paling umum, dan sebagian perilaku tak terduganya, ke Ingress2Gateway, dan panduan CNCF merekomendasikannya untuk mengotomatiskan terjemahan. Anggap hasilnya draf untuk ditinjau, bukan route final.

Di mana migrasi gagal: lima perilaku yang tidak pernah ditulis

Sumber paling berguna adalah tulisan Steven Jin di blog Kubernetes. Pola risikonya berulang: terjemahan yang tampak benar tetap bisa menyebabkan outage karena mengabaikan keanehan ingress-nginx. Setiap contoh berakhir dengan 404 di tempat ingress-nginx menjawab 200 atau 301. Saya belum mereproduksinya; tabel ini merangkum temuan beliau.

Perilakuingress-nginxImplementasi Gateway APIPerbaikan
Path regexPrefix dan case-insensitiveIstio, Envoy Gateway, dan Kgateway melakukan full match yang case-sensitivePerlebar pola atau pakai flag (?i)
Cakupan use-regexBerlaku untuk semua path pada host di semua Ingress-nya, termasuk yang ExactExact dan Prefix tidak pernah diam-diam diubah menjadi regexPerbaiki typo, atau pakai RegularExpression dengan (?i)
rewrite-targetDiam-diam menyiratkan use-regex untuk seluruh hostFilter URLRewrite tidak mengubah tipe matchMatch regex seperti (?i)/IP.* atau path Exact yang sudah dikoreksi
Trailing slashRedirect 301 dari /my-path ke /my-path/Implementasi yang conformant tidak membuat redirect secara diam-diamFilter RequestRedirect yang eksplisit
Normalisasi URLMenormalisasi sebelum matching, sehingga //uuid sampai ke /uuidIstio, Envoy Gateway, dan Kgateway menormalisasi dot segment secara defaultBaca dokumentasi normalisasi tiap implementasi

Baris kedua dan ketiga paling berbahaya karena keanehannya tidak ada di Ingress yang sedang Anda baca. Satu Ingress dengan use-regex atau rewrite-target mengubah setiap path pada host itu menjadi regex prefix yang case-insensitive, termasuk path yang dideklarasikan di Ingress lain. Typo seperti /Header alih-alih /headers berfungsi bertahun-tahun dan mengembalikan 404 setelah terjemahan yang setia.

# Wrong: a faithful copy of the Ingress. ingress-nginx matched /ip here because
# rewrite-target silently made "/IP" a case-insensitive prefix regex.
# An Envoy-based gateway matches Exact literally, so GET /ip is a 404.
- matches:
  - path:
      type: Exact
      value: "/IP"
  filters:
  - type: URLRewrite
    urlRewrite:
      path:
        type: ReplaceFullPath
        replaceFullPath: /uuid

# Right (to keep the old behaviour): say the regex out loud.
- matches:
  - path:
      type: RegularExpression
      value: "(?i)/IP.*"
  filters:
  - type: URLRewrite
    urlRewrite:
      path:
        type: ReplaceFullPath
        replaceFullPath: /uuid

# Trailing slash: ingress-nginx answered GET /my-path with a 301 to /my-path/.
# Gateway API will not do that unless you ask. The Kubernetes post's snippet
# leaves out "type: RequestRedirect"; the API needs it alongside the block.
- matches:
  - path:
      type: Exact
      value: "/my-path"
  filters:
  - type: RequestRedirect
    requestRedirect:
      statusCode: 301
      path:
        type: ReplaceFullPath
        replaceFullPath: /my-path/

Tiga kebiasaan menyusul, dan ini dari saya, bukan dari tulisan itu: replay path dari access log nyata ke gateway baru sebelum cutover, bandingkan status code per path dan bukan per host, dan perlakukan host mana pun yang memiliki use-regex atau rewrite-target di salah satu Ingress-nya sebagai host paling berisiko di cluster.

Implementasi Gateway API yang conformant tidak menambahkan redirect atau perilaku regex yang tidak Anda tulis. Migrasi yang benar di atas kertas mengembalikan 404 untuk traffic yang dulu menghasilkan 200 atau 301, jadi uji status code, bukan hanya apakah route sudah ter-apply.

Jam kedua: externalIPs pada Service

Jika Anda menjalankan bare metal dan mengekspos ingress-nginx lewat externalIPs pada Service, berlaku tenggat kedua. Kubernetes secara resmi men-deprecate .spec.externalIPs di v1.36. API ini mengasumsikan semua user di cluster sepenuhnya tepercaya, yang membuka eksploit seperti dijelaskan di CVE-2020-8554, dan proyek merekomendasikan menonaktifkan field ini sejak 1.21 memakai admission controller DenyServiceExternalIPs.

Perkiraan timeline di tulisan itu: Kubernetes 1.36 men-deprecate field tersebut dan mengeluarkan warning. Paling cepat di v1.40, sekitar setahun kemudian, dukungan di kube-proxy dinonaktifkan tetapi masih ada cara untuk opt back in. Paling cepat di v1.43, sekitar setahun setelahnya, dukungan dinonaktifkan sepenuhnya tanpa opsi opt-in. Hanya field pada Service yang terdampak; tipe alamat ExternalIP pada Node dan kolom EXTERNAL-IP di output kubectl tidak.

# Which Services still set spec.externalIPs? Run in every cluster.
kubectl get services --all-namespaces -o json \
  | jq -r '.items[] | select(.spec.externalIPs != null)
      | [.metadata.namespace, .metadata.name, (.spec.externalIPs | join(","))] | @tsv'

# To block new uses, enable the DenyServiceExternalIPs admission plugin on
# kube-apiserver (existing Services are not rewritten):
#   --enable-admission-plugins=...,DenyServiceExternalIPs

Tulisan itu mendaftar tiga pengganti. Yang termudah dan, menurut kata-katanya, terburuk adalah Service type LoadBalancer yang IP-nya Anda patch manual ke status. Lebih baik adalah load balancer controller seperti MetalLB, di mana administrator mendefinisikan address pool dan controller mencegah dua Service mengklaim satu IP. Yang ketiga adalah Gateway dengan alamat di spec.addresses, yang bisa dibatasi RBAC hanya untuk user berhak. Jika migrasi Anda toh berakhir di Gateway API, opsi ketiga ini menyelesaikan deprecation tersebut sekaligus.

Yang dilakukan hari Senin

Tidak satu pun dari ini memerlukan keputusan Contour versus Gateway API lebih dulu. Tiga langkah pertama memakan satu sore dan memberi tahu seberapa besar pekerjaannya.

  1. Jalankan pengecekan pod selector di setiap cluster, termasuk staging dan cluster yang tidak ada pemiliknya.
  2. Hitung annotation nginx.ingress.kubernetes.io per key, dan daftarkan setiap host yang memakai use-regex atau rewrite-target.
  3. Periksa Service yang memakai externalIPs, karena jam itu terpisah.
  4. Pilih jalurnya per cluster. Jika Anda tidak bisa menyediakan tenaga untuk menulis ulang route kuartal ini, ambil Jalur A sebagai stopgap bertanggal dengan rencana Gateway API yang menyertainya; jika Anda memang sedang modernisasi, langsung ke B.
  5. Naikkan controller baru di samping yang lama dengan alamat sendiri, dan pindahkan workload non-kritis lebih dulu, seperti saran panduan CNCF.
  6. Sebelum mengubah DNS, replay path nyata ke alamat baru dan bandingkan status code. Hapus ingress-nginx paling akhir.
# Step 2: which nginx annotations do you actually use, and on which Ingress?
kubectl get ingress --all-namespaces -o json \
  | jq -r '.items[] | . as $i | ($i.metadata.annotations // {}) | keys[]
      | select(startswith("nginx.ingress.kubernetes.io/"))
      | [$i.metadata.namespace, $i.metadata.name, .] | @tsv' \
  | sort > nginx-annotations.tsv

cut -f3 nginx-annotations.tsv | sort | uniq -c | sort -rn      # by key
grep -E 'use-regex|rewrite-target' nginx-annotations.tsv        # highest-risk hosts

# Step 6: send the same request to old and new address, compare status codes.
# --resolve pins the host to an IP without touching DNS.
for p in /uuid /UUID /my-path //uuid /ip; do
  old=$(curl -s -o /dev/null -w '%{http_code}' --resolve shop.example.com:80:OLD_IP "http://shop.example.com$p")
  new=$(curl -s -o /dev/null -w '%{http_code}' --resolve shop.example.com:80:NEW_IP "http://shop.example.com$p")
  echo "$p old=$old new=$new"
done

Satu hal yang sengaja saya tinggalkan: beberapa ringkasan retirement menyebut tanggal spesifik di bulan Maret. Halaman yang saya baca, yaitu pengumuman Kubernetes, pernyataan Steering Committee, README, dan panduan CNCF, hanya menyebut Maret 2026, jadi saya tidak menuliskan tanggal.

Perlakukan retirement ini sebagai tenggat keamanan yang sudah lewat, bukan proyek yang masih longgar. Temukan setiap ingress-nginx, inventarisasi annotation-nya, dan pilih jalur berdasarkan banyaknya perilaku yang harus dipertahankan. Contour membeli waktu, Gateway API adalah tujuannya, dan apa pun pilihannya, outage akan datang dari keanehan yang tidak terdokumentasi, jadi uji status code per path sebelum memindahkan DNS.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apakah ingress-nginx masih aman dijalankan setelah Maret 2026?

Deployment yang ada tetap berjalan dan Helm chart serta image tetap tersedia, tetapi tidak ada lagi release, bugfix, atau update keamanan. Steering Committee Kubernetes menyatakan bertahan di ingress-nginx membuat Anda dan pengguna rentan terhadap serangan. Rencanakan migrasi, jangan menunggu celah keamanan memaksanya.

Apakah Kubernetes Ingress API ikut dipensiunkan bersama ingress-nginx?

Tidak. Hanya controller ingress-nginx buatan komunitas yang dipensiunkan. Panduan CNCF menyatakan Ingress API tetap didukung dan banyak dipakai, meski juga menyebutnya feature-frozen. Karena itu Anda bisa tetap memakai Ingress dan mengganti controller, atau pindah ke Gateway API.

Sebaiknya migrasi dari ingress-nginx ke Contour atau ke Gateway API?

Contour adalah jalur dengan usaha lebih kecil karena objek Ingress tetap dipakai, tetapi setiap annotation nginx.ingress.kubernetes.io harus diterjemahkan, dan panduan CNCF menyebutnya stopgap. Gateway API butuh kerja lebih banyak sekarang dan di sanalah pengembangan upstream berlangsung. Jika belum bisa menyediakan tenaga untuk menulis ulang route, pakai Contour dengan rencana Gateway API bertanggal.

Bagaimana cara mengecek apakah cluster saya memakai ingress-nginx?

Dengan izin cluster administrator, jalankan kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx. Pengumuman Kubernetes menyebut cara ini berhasil di sebagian besar kasus. Cek setiap cluster, termasuk staging dan cluster yang tidak ada pemiliknya, karena controllernya terus berjalan tanpa suara.

Mengapa migrasi ke Gateway API mengembalikan 404 padahal ingress-nginx mengembalikan 200?

ingress-nginx memperlakukan path regex sebagai prefix case-insensitive, menerapkan use-regex ke setiap path pada satu host, membiarkan rewrite-target menyiratkan regex, dan me-redirect trailing slash yang hilang. Implementasi Gateway API yang conformant tidak melakukan semua itu secara diam-diam, sehingga terjemahan harfiah bisa 404. Uji status code per path sebelum memindahkan DNS.