Se você seguiu o nosso guia de GKE Autopilot com external-dns e cert-manager, o seu cluster está rodando o ingress-nginx como porta de entrada. Precisamos conversar sobre isso.
Em janeiro de 2026, os comitês de Steering e de Segurança do Kubernetes anunciaram a aposentadoria do ingress-nginx. Em 24 de março o repositório ficou somente leitura. O comunicado é explícito: “sem mais releases para correção de bugs, patches de segurança ou qualquer tipo de atualização”. E isso aconteceu depois de quatro CVEs de severidade alta em fevereiro, além do IngressNightmare (CVSS 9.8) do ano anterior — vulnerabilidades que agora nunca mais serão corrigidas.
Estimativas apontam que cerca de metade dos ambientes cloud-native dependiam do ingress-nginx, e uma pesquisa recente ainda encontrou 44% dos times usando — o que faz de você parte de uma multidão. A boa notícia: a migração para a Gateway API no GKE é mais simples do que parece, dá para fazer sem downtime e, no final, você ganha um Load Balancer melhor. Este guia é o roteiro, passo a passo, para quem tem exatamente o setup do tutorial anterior.
O que muda: Ingress vs. Gateway API
O Ingress foi projetado como um mínimo denominador comum, e cada controlador (nginx, traefik, GCE) estendia o padrão com dezenas de annotations proprietárias. A Gateway API é o sucessor oficial: um padrão do Kubernetes, expressivo e orientado a papéis, que separa infraestrutura de roteamento:
| Recurso | Quem gerencia | O que faz |
|---|---|---|
GatewayClass | Google (no GKE) | Define o tipo de Load Balancer |
Gateway | Time de plataforma | Provisiona o LB, os listeners (80/443), o IP e o TLS |
HTTPRoute | Time da aplicação | Roteia hostnames/paths para os Services |
A mudança arquitetural que importa no GKE: com o ingress-nginx você tinha um Load Balancer L4 regional (passthrough) mandando tráfego para pods do nginx dentro do cluster, que faziam o TLS e o roteamento. Com a Gateway API gerenciada, o GKE provisiona um Application Load Balancer L7 global (anycast) do Google Cloud: o TLS termina na borda da rede do Google, o roteamento é feito pelo próprio LB, e os pods do nginx deixam de existir — nem vCPU/RAM do Autopilot eles consomem mais.
Pré-requisitos
- O cluster GKE Autopilot e o setup do tutorial anterior (ingress-nginx, cert-manager, external-dns no Cloud DNS, GitHub Actions).
gcloud,kubectlehelminstalados.- O domínio já delegado ao Cloud DNS (feito no tutorial anterior).
Os placeholders são os mesmos de antes: meu-projeto, meu-cluster, us-central1, exemplo.com / app.exemplo.com.
💸 Custo: o Application Load Balancer global é cobrado por hora de forwarding rule e por GB processado (tier Premium). Em troca, você elimina os pods do ingress-nginx e o Load Balancer L4 antigo. Para a maioria dos workloads a conta fica parecida — mas confira a tabela de preços de rede para o seu volume.
A estratégia: migrar sem downtime
A recomendação oficial do Google é rodar os dois lado a lado, e é o que vamos fazer:
- Subir o
Gatewaye osHTTPRouteem paralelo ao Ingress atual (nada muda para os usuários). - Testar a nova rota direto pelo IP do Gateway, sem mexer no DNS.
- Fazer o cutover do DNS (o external-dns faz isso por nós).
- Só então remover o Ingress e desinstalar o ingress-nginx.
Em nenhum momento o site fica fora do ar.
Passo 1 — Confirmar a Gateway API no cluster
No Autopilot, a Gateway API vem sempre habilitada — não há nada para ligar. Confirme as classes disponíveis:
kubectl get gatewayclass
Você verá, entre outras, a gke-l7-global-external-managed — é a que o Google recomenda para aplicações públicas HTTPS, e a que usaremos. (Em clusters Standard, se não aparecer, habilite com gcloud container clusters update meu-cluster --location=us-central1 --gateway-api=standard.)
Evite a
gke-l7-gxlb: é a classe legada, não suporta redirect HTTP→HTTPS nem paths por regex. O próprio Google recomenda aglobal-external-managedno lugar dela.
Passo 2 — Reservar um IP estático GLOBAL
Atenção aqui, porque é diferente do tutorial anterior. O ingress-nginx usava um IP regional (Load Balancer L4). O Application Load Balancer global exige um IP global:
gcloud compute addresses create app-gateway-ip --global
# anote o IP — vamos usar para testar antes do DNS
gcloud compute addresses describe app-gateway-ip --global --format="value(address)"
Não apague o IP regional antigo ainda — ele continua servindo o ingress-nginx até o cutover.
Passo 3 — TLS gerenciado com o Certificate Manager
Aqui está a maior mudança conceitual em relação ao cert-manager + Let’s Encrypt. Como o TLS agora termina no Load Balancer do Google, a forma recomendada é deixar o próprio Google emitir e renovar o certificado, via Certificate Manager. Zero componente para operar no cluster.
O fluxo é: autorização DNS → certificado gerenciado → certificate map (que o Gateway referencia).
# 1) autorização DNS para o hostname
gcloud certificate-manager dns-authorizations create app-exemplo-com-auth \
--domain="app.exemplo.com"
# 2) veja o registro CNAME que o Google pede
gcloud certificate-manager dns-authorizations describe app-exemplo-com-auth
# dnsResourceRecord:
# name: _acme-challenge.app.exemplo.com.
# type: CNAME
# data: xxxxxxxx.authorize.certificatemanager.goog.
Crie esse CNAME na sua zona do Cloud DNS (substitua data pelo valor retornado):
gcloud dns record-sets create _acme-challenge.app.exemplo.com. \
--zone=exemplo-com --type=CNAME --ttl=30 \
--rrdatas="xxxxxxxx.authorize.certificatemanager.goog."
Agora o certificado, o map e a entrada que liga o hostname ao certificado:
# 3) certificado gerenciado pelo Google (ele emite e renova sozinho)
gcloud certificate-manager certificates create app-exemplo-com-cert \
--domains="app.exemplo.com" \
--dns-authorizations="app-exemplo-com-auth"
# 4) aguarde ficar ACTIVE (pode levar alguns minutos após o CNAME propagar)
gcloud certificate-manager certificates describe app-exemplo-com-cert \
--format="value(managed.state)"
# 5) o map é o que o Gateway vai referenciar
gcloud certificate-manager maps create app-exemplo-com-map
gcloud certificate-manager maps entries create app-exemplo-com-entry \
--map="app-exemplo-com-map" \
--certificates="app-exemplo-com-cert" \
--hostname="app.exemplo.com"
Wildcard? Use
--domains="exemplo.com,*.exemplo.com"com uma única autorização no domínio raiz.
”E o meu cert-manager?”
Uma dúvida comum — e uma confusão que circula por aí — é que “o cert-manager não funciona com o Gateway do GKE”. Não é verdade. Secrets TLS do Kubernetes são suportados em todas as GatewayClasses do GKE: o controlador copia o Secret para um certificado SSL do Compute Engine. Você pode manter o cert-manager, mas duas coisas mudam:
- Não existe mais
Ingresspara o desafio HTTP-01. O solver precisa ser o de Gateway API, habilitado no cert-manager (≥ 1.15):
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--set crds.enabled=true \
--set config.gatewayAPI.enabled=true
# ClusterIssuer com o solver de Gateway API
solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- name: external-gateway
namespace: demo
kind: Gateway
- No
Gateway, o listener HTTPS referencia o Secret comtls.certificateRefsem vez da annotationcertmap— os dois não podem coexistir no mesmo Gateway.
Cada desafio HTTP-01 espera o LB ser reprogramado (minutos), então é mais lento. Se for manter o cert-manager, o solver DNS-01 com Cloud DNS é o caminho mais tranquilo. Ainda assim, para o GKE, o Certificate Manager é a opção que recomendamos: menos peças, renovação automática pelo Google.
Passo 4 — O Gateway
Crie k8s/core/gateway/gateway.yaml. Ele referencia o IP global pelo nome e o certificado pelo map:
# k8s/core/gateway/gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: external-gateway
namespace: demo
annotations:
networking.gke.io/certmap: app-exemplo-com-map # TLS pelo Certificate Manager
spec:
gatewayClassName: gke-l7-global-external-managed
addresses:
- type: NamedAddress
value: app-gateway-ip # o IP GLOBAL reservado no Passo 2
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: Same
Repare que o listener HTTPS não tem bloco tls: com o certmap, o certificado vem da annotation. O listener HTTP fica só para redirecionar.
Passo 5 — As rotas (HTTPRoute)
Duas rotas: a da aplicação (no listener HTTPS) e o redirect (no listener HTTP). Crie em k8s/apps/whoami/httproute.yaml:
# rota da aplicação, ligada ao listener https
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: whoami
namespace: demo
spec:
parentRefs:
- name: external-gateway
sectionName: https
hostnames:
- "app.exemplo.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: whoami
port: 80
---
# HTTP → HTTPS, ligado ao listener http
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: redirect-http-to-https
namespace: demo
spec:
parentRefs:
- name: external-gateway
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
O sectionName amarra cada rota ao listener certo. O redirect usa o filtro padrão RequestRedirect — suportado na classe global-external-managed (e é justamente o que a classe legada gxlb não faz).
Passo 6 — Aplicar e testar SEM mexer no DNS
Aplique o Gateway e as rotas. O Ingress antigo continua no ar, intocado:
kubectl apply -f k8s/core/gateway/gateway.yaml
kubectl apply -f k8s/apps/whoami/httproute.yaml
O GKE leva alguns minutos para provisionar o Load Balancer. Acompanhe até o Gateway ficar Programmed e receber o IP:
kubectl get gateway external-gateway -n demo \
-o=jsonpath="{.status.addresses[0].value}" --watch
kubectl describe gateway external-gateway -n demo # procure Programmed: True
kubectl get httproute -n demo # as rotas devem estar Accepted
Agora o teste decisivo — bater no IP do Gateway simulando o hostname, sem tocar no DNS:
GW_IP=$(gcloud compute addresses describe app-gateway-ip --global --format="value(address)")
# HTTPS pela nova rota (certificado do Google)
curl -sI --resolve app.exemplo.com:443:$GW_IP https://app.exemplo.com/
# redirect HTTP → HTTPS
curl -sI --resolve app.exemplo.com:80:$GW_IP http://app.exemplo.com/
# HTTP/1.1 301 Moved Permanently
# location: https://app.exemplo.com/
Se o HTTPS responder 200 com certificado válido e o HTTP devolver 301, o novo caminho está pronto. Os usuários ainda estão no ingress-nginx — e vão continuar até você decidir.
Passo 7 — Cutover do DNS (o external-dns faz o trabalho)
No tutorial anterior o external-dns lia sources: [ingress, service]. Para ele passar a apontar app.exemplo.com para o Gateway, troque a fonte para as rotas da Gateway API. Edite k8s/core/external-dns/values.yaml:
sources:
- gateway-httproute # lê hostnames dos HTTPRoute; o IP vem do status do Gateway
- service
O chart do external-dns (≥ 1.20) adiciona sozinho as permissões RBAC de gateways/httproutes quando detecta uma fonte gateway-*. Aplique e remova o Ingress antigo — com policy: sync e o mesmo txtOwnerId, o external-dns reescreve o registro A para o IP do Gateway:
helm upgrade --install external-dns external-dns/external-dns \
--namespace external-dns \
--values k8s/core/external-dns/values.yaml
kubectl delete ingress whoami -n demo
# acompanhe a troca
kubectl logs -n external-dns deploy/external-dns | grep app.exemplo.com
dig +short app.exemplo.com # deve virar o IP global do Gateway
TTL: clientes com o registro antigo em cache continuam batendo no ingress-nginx até o TTL expirar. É exatamente por isso que só desinstalamos o nginx depois — mantenha os dois por um tempo e observe o tráfego do antigo cair a zero.
Passo 8 — Limpeza
Quando o tráfego no ingress-nginx zerar:
# desinstala o controlador aposentado
helm uninstall ingress-nginx -n ingress-nginx
kubectl delete namespace ingress-nginx
# libera o IP regional antigo (o do L4)
gcloud compute addresses delete nginx-ingress-ip --region=us-central1
# se não for manter o cert-manager, remova-o também
helm uninstall cert-manager -n cert-manager
E no repositório: apague o workflow do ingress-nginx e o values.yaml dele. O workflow de deploy das apps continua igual — só troca ingress.yaml por httproute.yaml no kubectl apply. Vale um workflow para o gateway.yaml em k8s/core/gateway/, no mesmo molde dos outros.
Tem muitos Ingress? Use o ingress2gateway
Para quem tem dezenas de Ingress, a ferramenta oficial ingress2gateway (v1.0 saiu em março junto com a aposentadoria; já está na v1.2) converte manifests automaticamente, traduzindo mais de 30 annotations do ingress-nginx:
brew install ingress2gateway
# lê os Ingress do cluster e imprime Gateway + HTTPRoute equivalentes
ingress2gateway print --providers=ingress-nginx -n demo
# ou a partir de um arquivo
ingress2gateway print --providers=ingress-nginx --input-file=ingress.yaml
Ele converte rewrite-target, redirects, canary por peso/header, timeouts, CORS e ssl-redirect. Revise a saída: troque o gatewayClassName para gke-l7-global-external-managed, e leia os avisos — o que não tem equivalente ele sinaliza, em vez de ignorar em silêncio.
O que não migra 1:1
Algumas coisas do nginx não têm tradução direta, e é bom saber antes:
- ModSecurity / WAF — o equivalente no GCP é o Cloud Armor, anexado ao backend com um
GCPBackendPolicy(networking.gke.io/v1). Mais robusto que o ModSecurity dentro do pod. - Rewrites complexos — o GKE Gateway só faz substituição de prefixo; regex de rewrite não existe.
- Autenticação via annotation (
auth-url, basic auth) — o caminho recomendado é o Identity-Aware Proxy. - Annotations
nginx.ingress.kubernetes.io/*em geral — não existem mais. Timeouts, health checks e políticas de SSL viram recursos próprios do GKE (HealthCheckPolicy,GCPGatewayPolicy).
Como fazemos na DevPlus
Nossos clusters seguiam exatamente o setup do tutorial anterior — ingress-nginx com IP fixo, cert-manager e external-dns. Este guia é o roteiro de migração que adotamos, e a lógica por trás dele é simples: um componente aposentado, com CVEs abertas e sem patches, não pode ser a porta de entrada de um sistema em produção. A Gateway API resolve isso e ainda entrega um Load Balancer global, com TLS na borda e sem pods para manter.
Conclusão
A aposentadoria do ingress-nginx não é uma questão de “se”, é de “quando” — e cada dia com ele na frente do cluster é um dia exposto a vulnerabilidades que não serão corrigidas. A migração para a Gateway API no GKE cabe numa tarde: IP global, certificado gerenciado, um Gateway, dois HTTPRoute, cutover pelo external-dns e limpeza. Rodando os dois em paralelo, os usuários nem percebem.
Precisa migrar um cluster em produção com segurança? A DevPlus planeja e executa migrações de infraestrutura Kubernetes sem downtime. Fale com a gente.
Veja também: GKE Autopilot do zero: DNS e HTTPS automáticos, Guia de sobrevivência: .NET no GKE Autopilot em produção e Health Checks no ASP.NET Core: do básico ao Kubernetes.