A Gateway API é a sucessora do Ingress no Kubernetes. Em vez de um único objeto cheio de anotações de cada fornecedor, ela divide a publicação de uma aplicação em papéis. Um define o tipo de load balancer, outro abre as portas e um terceiro cuida das rotas. No EKS, o AWS Load Balancer Controller transforma esses objetos num Application Load Balancer de verdade.
Este artigo continua a série artigos-eks. No artigo do AWS Load Balancer Controller, a aplicação saiu num ALB a partir de um Ingress. Agora o mesmo resultado sai de um Gateway e de um HTTPRoute, num cluster 1.37 criado com Terraform. Tudo o que aparece aqui foi executado num laboratório real, com os prints do console e a saída do kubectl, e destruído no fim.
Segurança: os comandos deste guia usam o perfil do AWS CLI para autenticar. Nunca coloque access keys no código ou em .tfvars versionado; prefira aws sso login ou um perfil com credenciais temporárias.
Sumário
- Pré-requisitos
- Ingress × Gateway API: o que muda
- O ambiente com Terraform
- Instalando os CRDs da Gateway API
- GatewayClass e LoadBalancerConfiguration
- Gateway: o ALB e o listener
- TargetGroupConfiguration: target-type ip
- HTTPRoute: rotas por caminho e reescrita de URL
- O que o controller criou na AWS
- Ingress ou Gateway API?
- Custos e limpeza
- Conclusão
- Referências oficiais
Pré-requisitos
AWS CLI v2 autenticado, Terraform >= 1.5, kubectl e, de preferência, o helm para consultar versões. As versões usadas no laboratório:
- Kubernetes 1.37 no EKS;
- chart
eks/aws-load-balancer-controller3.6.0; - CRDs da Gateway API v1.6.0, canal standard;
- providers
hashicorp/aws ~> 6.0ehashicorp/helm ~> 3.0.
Ingress × Gateway API: o que muda
Com Ingress, tudo fica num objeto só: o esquema do ALB, o tipo de target, os caminhos, o certificado. O que a especificação não cobre vira anotação, e cada controller inventa as suas (alb.ingress.kubernetes.io/..., nginx.ingress.kubernetes.io/...). Trocar de controller significa reescrever as anotações.
A Gateway API divide a mesma informação em recursos com donos diferentes:
| Recurso | Quem cuida | O que vira no ALB |
|---|---|---|
GatewayClass | Time de plataforma | Qual controller atende (aqui, o ALB) |
Gateway | Plataforma ou operação do cluster | O ALB e os listeners (porta e protocolo) |
HTTPRoute | Time da aplicação | Regras do listener e target groups |
LoadBalancerConfiguration, TargetGroupConfiguration, ListenerRuleConfiguration | Quem precisa de ajuste específico da AWS | O que antes era anotação: esquema, tipo de target, atributos |
Na prática, o time de plataforma publica um Gateway e cada time de aplicação pendura nele as suas rotas, sem tocar no ALB. O que é específico da AWS deixa de ser anotação solta e passa a ser um recurso tipado, validado pela API do Kubernetes.
O ambiente com Terraform
A infraestrutura é a mesma do artigo do controller, composta pelos módulos da série: VPC com as tags de subnet, cluster, node group, add-ons e o controller instalado com Helm e permissão via EKS Pod Identity. Só mudam as versões: Kubernetes 1.37 e chart 3.6.0.
module "lb_controller" {
source = "../../terraform-eks-lb-controller-modules"
cluster_name = module.eks.name
region = var.region
vpc_id = module.vpc.vpc_id
chart_version = var.lb_controller_chart_version # "3.6.0"
depends_on = [module.addons]
}
Ao subir o chart, a regra é trocar também o iam_policy.json do módulo pela versão correspondente. Conferi a política da tag v3.6.0 contra a da v3.4.0, que o módulo já tinha: são idênticas, então a role não precisou mudar.
terraform init
terraform plan -var 'public_access_cidrs=["SEU_IP/32"]' # 32 recursos
terraform apply -var 'public_access_cidrs=["SEU_IP/32"]'
aws eks update-kubeconfig --region us-east-1 --name eks-blog-castilho
No laboratório, o apply levou cerca de 13 minutos e meio, quase todo gasto no control plane e no node group.



Os dois nós aparecem no kubectl já na versão do control plane:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-41-27.ec2.internal Ready <none> 20m v1.37.1-eks-117f323
ip-10-0-48-165.ec2.internal Ready <none> 20m v1.37.1-eks-117f323

Instalando os CRDs da Gateway API
Existem dois conjuntos de CRDs, e é aqui que mora a primeira pegadinha. O chart 3.6.0 já instala os CRDs do próprio controller: LoadBalancerConfiguration, TargetGroupConfiguration e ListenerRuleConfiguration. Os CRDs padrão da Gateway API (GatewayClass, Gateway, HTTPRoute e outros) não vêm com o EKS nem com o chart. Eles saem do projeto upstream:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/standard-install.yaml
kubectl get crd | grep gateway
Se o controller subiu antes desses CRDs existirem, como acontece quando o Terraform instala o chart e só depois você aplica os CRDs, ele desliga o suporte à Gateway API na inicialização e registra no log:
Disabling ALBGatewayAPI: missing required CRDs
A verificação só acontece no início. Aplicar os CRDs depois não religa nada: o Gateway fica sem endereço e sem nenhum evento explicando o motivo. A saída é reiniciar o controller depois de aplicar os CRDs:
kubectl rollout restart deployment aws-load-balancer-controller -n kube-system
kubectl logs -n kube-system deploy/aws-load-balancer-controller | grep -i gateway
Em pipeline, a forma de evitar isso é garantir a ordem: CRDs primeiro, chart depois. Ou acrescentar o restart como passo fixo logo após a aplicação dos CRDs.
GatewayClass e LoadBalancerConfiguration
A GatewayClass é um recurso de cluster que diz qual controller atende os Gateway daquela classe. Para ALB, o controllerName é gateway.k8s.aws/alb (para NLB existe o gateway.k8s.aws/nlb):
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: aws-alb
spec:
controllerName: gateway.k8s.aws/alb
O esquema do ALB, que no Ingress era a anotação alb.ingress.kubernetes.io/scheme, agora fica numa LoadBalancerConfiguration. Os valores são os mesmos do Ingress: internal, nas subnets com a tag kubernetes.io/role/internal-elb, ou internet-facing, nas subnets com kubernetes.io/role/elb. Para um ALB acessível pela internet, declare o esquema de forma explícita:
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: loja-alb
namespace: loja
spec:
scheme: internet-facing
A aplicação de exemplo tem dois Deployments no namespace loja: web (nginx) e api (Apache httpd), cada um com dois pods e um Service ClusterIP na porta 80. O manifesto completo está no repositório do GitHub, no fim do artigo.
Gateway: o ALB e o listener
O Gateway aponta para a classe, liga a LoadBalancerConfiguration pelo infrastructure.parametersRef e declara os listeners. Cada listener vira um listener do ALB:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: loja
namespace: loja
spec:
gatewayClassName: aws-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: loja-alb
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
O allowedRoutes.namespaces.from: Same é o controle de acesso que faltava no Ingress: só rotas do namespace loja podem se pendurar nesse listener. Com Ingress e group.name, qualquer namespace que repetisse o nome do grupo acrescentava regras ao ALB. Aqui, para liberar outros namespaces, o dono do Gateway precisa trocar para All ou para um Selector por label.
TargetGroupConfiguration: target-type ip
O tipo de target, que no Ingress era alb.ingress.kubernetes.io/target-type: ip, vai para uma TargetGroupConfiguration ligada a cada Service. Com ip, o ALB registra direto os IPs dos pods, que no EKS são IPs da VPC graças ao VPC CNI, e o tráfego não passa por NodePort:
apiVersion: gateway.k8s.aws/v1
kind: TargetGroupConfiguration
metadata:
name: api-ip
namespace: loja
spec:
targetReference:
name: api
defaultConfiguration:
targetType: ip
O Service web recebe uma configuração igual, com name: web-ip e targetReference.name: web.
HTTPRoute: rotas por caminho e reescrita de URL
O HTTPRoute é a parte do time da aplicação. A ideia do exemplo: /api vai para o httpd e todo o resto vai para o nginx. A primeira versão do lab foi a tradução direta de um Ingress:
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api
port: 80
O resultado foi 404 do httpd em /api. O PathPrefix só escolhe o destino; ele não remove o prefixo. A requisição chegou ao Apache como /api, e o Apache não tem nada nesse caminho. Com Ingress acontece o mesmo, mas lá a correção dependia de recurso de cada controller. Na Gateway API, a reescrita faz parte da especificação, com o filtro URLRewrite:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: loja
namespace: loja
spec:
parentRefs:
- name: loja
sectionName: http
rules:
- matches:
- path:
type: PathPrefix
value: /api
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: api
port: 80
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web
port: 80
O controller traduz o filtro para o recurso nativo de transformação de URL das regras do ALB: uma expressão regular (^/api(/)?) trocada por /. A reescrita acontece no próprio load balancer, sem sidecar nem proxy extra. Depois do ajuste, /api respondeu com a página padrão do httpd.
kubectl apply -f app.yaml
kubectl apply -f gateway.yaml
kubectl get gatewayclass
kubectl get gateway,httproute -n loja
O que o controller criou na AWS
O ALB apareceu no console cerca de 15 segundos depois do apply. Já o PROGRAMMED=True no Gateway e a primeira resposta 200 levaram de 4 a 5 minutos. É o tempo de o ALB terminar o provisionamento, de o DNS propagar e de os pods passarem no health check.
$ kubectl get gatewayclass
NAME CONTROLLER ACCEPTED AGE
aws-alb gateway.k8s.aws/alb True 16m
$ kubectl get gateway -n loja
NAME CLASS ADDRESS PROGRAMMED AGE
loja aws-alb k8s-loja-loja-xxxxxxxxxx-nnnnnnnnnn.us-east-1.elb.amazonaws.com True 16m
$ kubectl get httproute -n loja
NAME HOSTNAMES AGE
loja 16m
O nome segue o padrão k8s-<namespace>-<gateway>-<hash>. O esquema veio da LoadBalancerConfiguration e as subnets públicas foram descobertas pelas tags:

As regras do listener HTTP:80 mostram a tradução do HTTPRoute. A regra 1 casa /api e /api/* e aplica a transformação de URL; a regra 2 manda o resto para o nginx. A ação padrão, quando nenhuma regra casa, é uma resposta fixa 404 gerada pelo próprio ALB:

E cada Service virou um target group do tipo IP, com os pods registrados direto:

Os IPs no target group são os mesmos dos pods:
$ kubectl get pods -n loja -o wide
NAME STATUS IP
api-767f6c7bfc-6tkkd Running 10.0.39.37
api-767f6c7bfc-wz955 Running 10.0.53.135
web-5f7f65895b-ld49z Running 10.0.62.152
web-5f7f65895b-qxwdv Running 10.0.41.152
$ ALB=$(kubectl get gateway loja -n loja -o jsonpath='{.status.addresses[0].value}')
$ curl -s -o /dev/null -w "%{http_code}\n" http://$ALB/
200
$ curl -s http://$ALB/api | grep title
<title>It works! Apache httpd</title>
Uma observação sobre os prints: com uma sessão federada só de leitura, o console do EKS mostra “Unauthorized” nas abas de recursos do Kubernetes, porque ela não tem access entry no cluster. Por isso os pods e os nós aparecem aqui pela saída do kubectl. Quem precisa ver workloads no console deve ganhar uma access entry com política de leitura, como mostrado no artigo de Access Entries.
Ingress ou Gateway API?
O Ingress não foi removido do Kubernetes e o AWS Load Balancer Controller continua atendendo os dois. Os mesmos pods podem ser publicados por um Ingress e por um Gateway ao mesmo tempo, cada um com o seu ALB. Isso permite migrar aplicação por aplicação, trocando o DNS no fim.
- Fique no Ingress se o cluster tem poucas aplicações, um time só, e as anotações atuais resolvem. Nesse cenário, a migração dá trabalho e traz pouco ganho.
- Vá para a Gateway API se vários times dividem o mesmo load balancer, se você quer separar quem cria o ALB de quem cria as rotas, ou se depende de recursos que no Ingress eram anotação específica: reescrita de URL, divisão de tráfego por peso entre
backendRefs, roteamento por header. - Projetos novos já podem começar pela Gateway API: a especificação é estável no canal standard e o suporte do controller é GA desde a v3.0.0.
Custos e limpeza
- Gateway API e controller: sem custo próprio; os pods do controller rodam nos nós.
- ALB: cobrança por hora e por LCU. Cada
Gatewayé um ALB. Compartilhar um Gateway entre vários times custa menos do que criar um por aplicação. - Cluster base: control plane, NAT Gateway e nós, como no primeiro artigo da série.
A ordem da limpeza é a mesma do Ingress: o ALB, os target groups e os security groups foram criados pelo controller, fora do state do Terraform. Apague primeiro os objetos do Kubernetes e espere o ALB sumir; só depois rode o destroy:
kubectl delete -f gateway.yaml
kubectl delete -f app.yaml
aws elbv2 describe-load-balancers --query 'LoadBalancers[?starts_with(LoadBalancerName,`k8s-loja`)].LoadBalancerName'
# espere a lista ficar vazia
terraform destroy -var 'public_access_cidrs=["SEU_IP/32"]'
# o EKS cria este log group sozinho e o Terraform não o remove
aws logs delete-log-group --log-group-name /aws/eks/eks-blog-castilho/cluster
No laboratório, o destroy removeu os 32 recursos. O log group /aws/eks/<cluster>/cluster é criado pelo próprio EKS quando há logs do control plane ativos; como não está no state, ficou para trás e precisou ser apagado à mão. Vale conferir também se não sobrou ALB, target group ou security group com prefixo k8s-.
Conclusão
A Gateway API no EKS entrega o mesmo ALB que o Ingress, mas com papéis separados: a GatewayClass escolhe o controller, o Gateway abre o load balancer e controla quem pode usá-lo, e o HTTPRoute fica com o time da aplicação. As anotações viraram recursos tipados (LoadBalancerConfiguration, TargetGroupConfiguration) e a reescrita de URL virou parte da especificação, traduzida para um recurso nativo do ALB.
Os dois pontos que mais custaram tempo no laboratório valem como checklist: aplicar os CRDs padrão antes do controller (ou reiniciá-lo depois) e lembrar que PathPrefix não tira o prefixo sem um URLRewrite.
Referências oficiais
- Gateway API — documentação do projeto Kubernetes
- HTTP path redirects and rewrites — Gateway API
- Gateway API — AWS Load Balancer Controller
- Release v3.6.0 — AWS Load Balancer Controller
- Release v1.6.0 — Gateway API
- Transforms for listener rules — Application Load Balancers
- Elastic Load Balancing pricing
Interessado em saber mais sobre esse assunto?