Lab 11 — API Gateway na frente, ou ALB direto?
O problema, e a empresa que o tem
A Cadência (L01) roda havia meses a API de pedidos atrás de um ALB, para as trinta lojas próprias. Duas redes de varejo parceiras — a Repique Calçados e a Estação Fashion — pediram para consumir a mesma API programaticamente, para sincronizar estoque sem digitação manual. A diretoria topou vender o acesso.
O primeiro protótipo, escrito num sprint, adicionou um cabeçalho X-Partner-Id que a aplicação lê para identificar quem está chamando, e um Dictionary em memória para contar quantas requisições cada parceiro já fez no dia. Funcionou no teste, com uma task só. Em produção, com duas tasks atrás do ALB, o time notou a Estação Fashion passando de 300 requisições combinadas bem antes do fim do dia — sem reclamação de ataque, sem nada de anormal no painel.
Ao mesmo tempo, a equipe de produto quer renomear o campo pedidoId para id na resposta — mudança que a Repique Calçados não está pronta para receber, e a Estação Fashion pediu para adotar assim que possível. Publicar a mudança direto quebra quem não migrou; não publicar trava quem já quer o formato novo.
O que este laboratório NÃO é
Não é autenticação de usuário final nem autorização fina por recurso — a chave de API identifica um PARCEIRO CONTRATANTE, não uma pessoa, e não decide o que ela pode ver dentro dos próprios dados. Login federado para usuário final é o L12. Também não é proteção contra ataque de negação de serviço em escala: throttle de usage plan é administração de contrato comercial; WAF com regra de taxa é outra camada, e as duas são complementares.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando na seção de implantação.
- Explicar por que chave de API identifica e não autentica, e o risco de tratá-la como autenticação.
- Escolher REST API em vez de HTTP API quando o requisito é cota por cliente, e justificar por quê.
- Medir, com número, como cota em memória de aplicação se comporta sob múltiplas tasks.
- Distinguir quota de throttle: qual mecanismo cada uma implementa e o que cada 429 significa.
- Configurar integração privada por VPC Link e explicar por que ela exige um Network Load Balancer.
- Registrar um serviço ECS em dois grupos de destino, sem duplicar a implantação.
- Versionar um contrato por estágio, com uma chave revogável por parceiro e por versão.
- Decidir, para um caso novo, se o tráfego deve ou não atravessar o Gateway.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| REST API vs HTTP API | DVA-C02, SAA-C03 | a escolha obrigatória por causa de chave de API e plano de uso | que HTTP API não tem chave de API nem cota por cliente |
| Chave de API vs autenticação | DVA-C02 | identificação para cota, não prova de identidade | o antipadrão de usar chave de API como controle de acesso |
| Plano de uso: quota e throttle | DVA-C02 | dois mecanismos, dois tipos de 429 | quota conta por período; throttle é token bucket com taxa e rajada |
| Integração privada e VPC Link | DVA-C02, SAA-C03 | REST API exige Network Load Balancer no destino | VPC Link clássico (REST/WebSocket) vs VPC Link v2 (HTTP API) usam alvos diferentes |
| Estágio e versionamento | DVA-C02 | v1 e v2 como integrações distintas de uma mesma API | que o estágio roteia; o formato do payload é responsabilidade do backend |
| Quando ALB basta | SAA-C03, DVA-C02 | o tráfego da loja própria não atravessa o Gateway | reconhecer cenário sem terceiro para saber que o Gateway é custo sem retorno |
| Múltiplos grupos de destino por serviço ECS | DVA-C02, SAA-C03 | a mesma implantação em dois caminhos de entrada | o limite de 5 grupos e a exigência de role vinculada ao serviço |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve alguém tentando emitir chave de API numa HTTP API para economizar, e pede o que vai dar errado. A resposta não é "nada, HTTP API é mais nova e melhor" — é que HTTP API simplesmente não tem o recurso. Confundir "mais barato" com "superconjunto de recursos" é o erro de raciocínio que a questão testa.
Requisitos, e como cada um muda o desenho
Requisito não funcional que não aparece numa linha de configuração é intenção. A coluna da direita é onde cada um deixou marca.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Cota diferenciada por parceiro | 500/dia e 2.000/dia | obriga REST API — só ela tem plano de uso com quota por chave |
| Proteção contra rajada | sem número fixo do parceiro; deve tolerar pico curto | throttle (taxa + rajada) no plano de uso, mecanismo distinto da quota |
| Versão de contrato migrável | v1 e v2 coexistindo por tempo indeterminado | dois estágios, cada chave associada ao par (API, estágio) que o parceiro pode usar |
| Loja própria sem sobrecusto | zero requisição a mais na fatura do Gateway | o tráfego interno continua direto no ALB, sem atravessar o Gateway |
| Sem novo IP público exposto | a rede privada não pode ganhar nova porta de entrada | integração privada por VPC Link, não ALB com IP público adicional |
| Auditoria de quem chamou | identificar o parceiro nos logs, sem confiar no cliente | log de acesso do Gateway com `apiKeyId`, nunca um cabeçalho que o cliente escreve |
Estágio decide a versão; domínio customizado decide a URL que o parceiro vê
Neste laboratório o parceiro chama a URL padrão do Gateway, com o nome do estágio no caminho (`.../v1/pedidos/{id}`). Em produção, é comum colocar um domínio customizado na frente, com mapeamento de base path — `api.cadencia.com.br/v1` apontando para o estágio v1 — para o parceiro nunca ver o identificador interno da API. É um recurso de apresentação, não de versionamento: a decisão de contrato continua sendo o estágio; o domínio só o torna mais legível.
Arquitetura mínima: a cota que existe só no papel
Este é o protótipo que a Cadência já tem no ar, e ele é legítimo como ponto de partida: atende os dois parceiros, sem recurso novo na fatura. O laboratório começa medindo o defeito, porque "a cota não está funcionando direito" é discutível e um número não é.
- → HTTPS + cabeçalho X-Partner-Id (lido, não verificado)
- → HTTPS + o mesmo cabeçalho, mesmo contrato informal
- → round robin — metade do tráfego
- → round robin — a outra metade
- Fora da AWS
- Rede e entrega
- Compute
Este desenho publica e atende — nenhuma linha aqui está tecnicamente errada. O defeito não é uma configuração ruim, é uma responsabilidade que não tem dono: ninguém neste caminho decide "quanto este parceiro já pediu hoje" de um jeito que sobreviva a duas tasks. Percorra os passos e veja o teto virar média.
- O cabeçalho identifica, e ninguém confere. A aplicação lê `X-Partner-Id` do jeito que o cliente mandar. Não há verificação nenhuma: qualquer chamador pode escrever o cabeçalho de outro parceiro e herdar a cota — ou a autorização — associada a ele. Identificação sem verificação não identifica coisa nenhuma, apenas registra o que o chamador afirmou ser.
- O ALB decide só o destino, não o direito de pedir. Round robin (ou o algoritmo de menor número de requisições em curso) distribui entre as tasks saudáveis. É exatamente o trabalho para o qual o ALB foi desenhado, e ele o faz bem — mas nenhuma dessas duas tasks sabe da existência da outra.
- A cota mora na memória de CADA processo. O `Dictionary<string,int>` que conta requisições por parceiro existe uma vez por task, não uma vez por parceiro. Task 1 e Task 2 têm, neste instante, dois números diferentes para o mesmo cliente — e nenhuma delas sabe disso.
- O teto contratado vira uma média entre réplicas. Com 2 tasks e round robin, um parceiro com cota de 300/dia pode, sem nenhum ataque, se aproximar de quase 600 antes de qualquer 429 — porque cada contador só vê metade do tráfego dele. A seção de provas mede esse número, não o estima.
- Escalar ou reiniciar apaga a cota do dia. Um deploy (L03) ou um evento de autoscaling (L06) substitui a task, e o `Dictionary` some com ela. O parceiro que já tinha consumido 290 das 300 volta a ter 0 — não porque o dia virou, mas porque o processo que contava não existe mais.
- Versão nova é reescrever a mesma rota, para todo mundo ao mesmo tempo. Não existe aqui o conceito de "este parceiro ainda está na v1". Mudar o formato da resposta é editar o mesmo controller que os dois parceiros chamam — o deploy afeta ambos no mesmo segundo, quisessem eles migrar ou não.
- Por que alguém constrói assim. Porque funciona no ambiente de desenvolvimento, com uma task só, onde o `Dictionary` nunca discorda de si mesmo. O defeito só aparece com escala — e por isso é fácil de aprovar em revisão de código e caro de descobrir em produção.
O cabeçalho que qualquer chamador pode forjar
Como `X-Partner-Id` é lido sem verificação nenhuma, qualquer cliente pode se anunciar como a Estação Fashion e herdar a cota maior — ou, pior, se o cabeçalho um dia for usado para decidir QUAL PEDIDO retornar, herdar acesso a dados de outro parceiro. Identificação sem verificação de origem não é um detalhe a ajustar depois: é a ausência do controle que o laboratório inteiro existe para instalar.
Arquitetura para produção
Cada peça nova abaixo responde a uma linha da tabela de requisitos. O ALB não desaparece — ele para de ser o único caminho de entrada.
- → HTTPS + chave de API (2.000/dia contratados)
- → HTTPS + chave de API (500/dia, piloto)
- → HTTPS 443, sem chave — não é terceiro
- → VPC Link: ENI na sua VPC, nunca sai à internet
- → encaminha por IP de destino
- → health check próprio; a cota já foi decidida antes daqui
- → encaminha só para alvo saudável
- → segundo caminho até a mesma implantação
- → registra (1º grupo de destino)
- → registra (2º grupo de destino — até 5 por serviço)
- Fora da AWS
- Rede e entrega
- Compute
A mudança estrutural não é "colocar mais uma caixa na frente": é mover identidade, cota, taxa e versão para fora da aplicação, para uma camada que administra contrato com terceiro — e deixar o ALB fazendo exatamente o que ele já fazia bem, para quem não é terceiro. Percorra os passos: cada peça nova responde a um requisito da seção anterior.
- O contrato é resolvido antes de qualquer pacote entrar na VPC. A chave de API chega no cabeçalho, o Gateway a resolve contra o plano de uso e decide identidade, cota e taxa — tudo isso acontece na borda gerenciada pela AWS, antes de qualquer bit alcançar a sua rede. Uma chamada rejeitada aqui nunca chega a consumir CPU da sua task.
- A chave identifica; ela não autentica. A chave de API diz QUEM está pedindo, para fins de medir cota — ela não prova que o chamador tem permissão para o dado pedido, nem substitui um mecanismo de autorização. Tratar chave de API como autenticação é o antipadrão mais grave deste módulo.
- REST, não HTTP — é a troca que faz a cota por cliente existir. HTTP API é mais barata e mais rápida, mas não tem plano de uso nem chave de API: ela não tem onde guardar "quanto este parceiro já pediu hoje". A decisão de usar REST API não é sobre preço — é sobre qual recurso o requisito exige.
- VPC Link entra sem sair para a internet. A integração privada cria uma interface de rede do Gateway dentro da sua VPC. Não existe rota de NAT nem de internet gateway neste caminho — e é exatamente por isso que ele não expõe o ALB nem a task a um endereço público novo.
- A mesma implantação, dois grupos de destino. O serviço ECS aceita até cinco grupos de destino simultâneos. Isso significa que a task de produção não foi duplicada: ela passou a responder por dois caminhos de entrada, cada um com o próprio health check e as próprias métricas.
- O ALB continua existindo — para quem não é terceiro. A loja própria não ganha nada com identidade, cota ou versão contratual: ela é a primeira parte, não um cliente pagante. Fazer esse tráfego atravessar o Gateway seria pagar por uma decisão que ninguém precisa tomar.
- Por que isto não é uma caixa a mais. A topologia mudou de "um caminho de entrada" para "dois caminhos com regras diferentes convergindo na mesma implantação". Não é o desenho mínimo com um componente extra — é uma decisão sobre quem administra o quê.
O que se ganha sem escrever uma linha de contagem
Identidade, cota, taxa e versão passam a ser administradas por um serviço gerenciado, e o teto contratado deixa de depender de quantas tasks o ECS decidiu manter no minuto em que o parceiro chamou. A aplicação perde código, não ganha — o que sobra nela é só a lógica de negócio.
Como funciona, ponta a ponta
A ordem dos passos é a diferença entre "o Gateway decide" e "a aplicação decide": tudo que está antes do passo 6 nunca chega a consumir um ciclo de CPU da sua task.
Os dois 429 não significam a mesma coisa
Um 429 de quota esgotada só se resolve quando o período vira — dia, semana ou mês. Um 429 de throttle pode se resolver em menos de um segundo, assim que o balde de tokens reencher. Um cliente que trata os dois com o mesmo `retry` fixo ou espera o dia inteiro por engano, ou martela de novo um limite que só o relógio resolve.
Corpo devolvido quando a QUOTA do periodo estourou — nao adianta tentar de novo em segundos; o contador so muda no proximo periodo do plano de uso.
{
"message": "Limit Exceeded"
}// v1 (Repique Calcados) — formato antigo, que o parceiro ainda consome
{
"pedidoId": "b3f1...",
"cliente": "Loja Centro",
"valor": 189.90,
"status": "confirmado"
}
// v2 (Estacao Fashion) — mesmo pedido, campo renomeado. O Gateway roteou
// para uma integracao DIFERENTE; o backend, nao o Gateway, devolveu o campo
// certo.
{
"id": "b3f1...",
"cliente": "Loja Centro",
"valor": 189.90,
"status": "confirmado"
}As decisões, e o que se perde em cada uma
📋 A Cadência vai vender acesso programático à API de pedidos para duas redes parceiras, cobrando por cota, sem tirar o ALB que já atende a loja própria e sem contratar mais gente para operar autenticação.
O requisito central — identidade do chamador, cota, taxa e versão — é exatamente o que um usage plan administra de fábrica, sem uma linha de código de contagem. REST API é a escolha obrigatória, não estética: é o único dos dois tipos de API do Gateway que tem chave de API e plano de uso. Manter o ALB para a loja própria evita pagar a fatura por requisição e a latência extra do Gateway onde não existe contrato nenhum para administrar.
Alt: ALB com cota calculada na aplicação — É o desenho deste laboratório antes da mudança: a cota vira média entre réplicas e some a cada deploy ou evento de escala. Resolve em ambiente de desenvolvimento, com uma task só, e quebra silenciosamente em produção.
Alt: HTTP API com autorizador Lambda customizado — Reimplementa, em código próprio, o que o usage plan já faz — chave, cota e throttle — só que agora mantido pelo seu time. Faz sentido quando a regra de autorização é mais rica do que identidade e cota simples (ex.: escopos por recurso), o que não é o caso aqui.
Alt: WAF com regra baseada em taxa, na frente do ALB — Protege contra volume agressivo por IP, mas não sabe quem é um parceiro contratado nem distingue 300/dia de 2.000/dia — é defesa de borda contra abuso, não administração de contrato comercial. As duas coisas são complementares, não substitutas.
Alt: API Gateway com o mesmo estágio para todos os parceiros, sem versão — Funciona até a primeira mudança de contrato incompatível — daí em diante, qualquer alteração de payload quebra todo parceiro no mesmo instante, porque não existe caminho para migrar um de cada vez.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Tipo de API | REST API | HTTP API (mais barata); WebSocket (não se aplica) | é o único tipo com chave de API e plano de uso | preço por requisição mais alto que HTTP API, e menos moderno em alguns recursos novos |
| Identificação do parceiro | chave de API + plano de uso | cabeçalho customizado lido pela aplicação; JWT de parceiro | a plataforma garante a associação chave→cota; a aplicação não confia no que o cliente afirma | chave de API não carrega escopo fino; para autorização por recurso, precisaria de camada extra |
| Integração até o ECS | privada, via VPC Link + NLB | ALB público com cabeçalho secreto compartilhado; NAT + IP público do ALB | nenhum IP novo exposto à internet; o tráfego nunca sai da rede da AWS | um recurso a mais cobrando por hora, e uma camada extra de troubleshooting de rede |
| Versão do contrato | estágio (v1/v2) com chave por (API, estágio) | campo de versão no corpo da requisição; cabeçalho `Accept-Version` | a revogação de acesso a uma versão é remover uma associação, sem tocar em código | o Gateway não transforma o payload sozinho: o backend precisa manter as duas formas |
| Tráfego da loja própria | continua direto no ALB | atravessar o Gateway também, por uniformidade de caminho | zero custo e zero latência extra onde não existe contrato a administrar | dois caminhos de entrada para operar e observar, em vez de um só |
A dívida que este desenho cria, e que o módulo não paga
A chave de API não expira sozinha e não tem escopo por recurso. Se um dia um parceiro precisar acessar só pedidos de uma loja específica, plano de uso não resolve isso — é autorização, não identificação, e fica para um Lambda authorizer ou para o Cognito, dependendo de quem é o chamador final.
Construir: o segundo caminho de entrada, sem duplicar o serviço
O Network Load Balancer não é uma escolha de gosto: é o único destino que o VPC Link clássico de uma REST API aceita. O serviço ECS existente ganha um segundo grupo de destino — a task continua sendo uma só.
# nlb.tf — o destino obrigatorio do VPC Link para REST API
# REST API nao fala diretamente com um Application Load Balancer numa
# integracao privada: o VPC Link classico (usado por REST e WebSocket APIs) se
# apoia num Network Load Balancer. O ALB continua existindo — ele so deixa de
# ser o destino DESTE caminho.
resource "aws_lb" "parceiros" {
name = "${var.projeto}-parceiros-nlb"
internal = true # sem IP publico: so o Gateway alcanca, via VPC Link
load_balancer_type = "network"
subnets = aws_subnet.privada[*].id
}
resource "aws_lb_target_group" "parceiros" {
name = "${var.projeto}-parceiros"
port = 8080
protocol = "TCP" # NLB opera na camada de transporte; o health check e HTTP
vpc_id = aws_vpc.principal.id
target_type = "ip" # awsvpc: o alvo e o IP da task, igual ao grupo do L01
health_check {
protocol = "HTTP"
path = "/health/ready"
port = "traffic-port"
}
}
resource "aws_lb_listener" "parceiros" {
load_balancer_arn = aws_lb.parceiros.arn
port = 80
protocol = "TCP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.parceiros.arn
}
}
# O VPC Link e o objeto do API Gateway que aponta para o NLB acima. Uma vez
# criado, qualquer integracao privada do REST API pode referenciá-lo.
resource "aws_api_gateway_vpc_link" "parceiros" {
name = "${var.projeto}-vpc-link"
target_arns = [aws_lb.parceiros.arn]
}
# O SERVICO ECS (definido no L01) ganha um segundo bloco `load_balancer`. Nao
# duplica a task: registra a MESMA implantacao num segundo grupo de destino.
# ECS aceita ate 5 grupos por servico, e exige a role vinculada ao servico
# (AWSServiceRoleForECS) para gerenciar mais de um.
resource "aws_ecs_service" "api" {
# ... os demais argumentos sao os do L01/L03, inalterados ...
load_balancer {
target_group_arn = aws_lb_target_group.api.arn # o grupo original, atras do ALB
container_name = "api"
container_port = 8080
}
load_balancer {
target_group_arn = aws_lb_target_group.parceiros.arn # o grupo novo, atras do NLB
container_name = "api"
container_port = 8080
}
}
Por que TCP no listener do NLB, e HTTP no health check
O Network Load Balancer opera na camada de transporte: o listener encaminha TCP sem olhar o conteúdo. O health check, porém, pode falar HTTP e checar a rota `/health/ready` — são duas configurações independentes, e é comum esquecer que o protocolo do listener não obriga o protocolo do health check.
Construir: o contrato — identidade, cota, taxa e versão
Cada recurso abaixo mora num lugar diferente da resposta a "quem pode pedir, quanto e de qual versão" — e nenhum deles existe na aplicação.
# apigateway.tf — o contrato: identidade, cota, taxa e versao
resource "aws_api_gateway_rest_api" "parceiros" {
name = "${var.projeto}-parceiros"
endpoint_configuration {
types = ["REGIONAL"] # sem CloudFront na frente; o publico e um numero pequeno de parceiros B2B
}
}
resource "aws_api_gateway_resource" "pedido" {
rest_api_id = aws_api_gateway_rest_api.parceiros.id
parent_id = aws_api_gateway_rest_api.parceiros.root_resource_id
path_part = "pedidos"
}
resource "aws_api_gateway_resource" "pedido_id" {
rest_api_id = aws_api_gateway_rest_api.parceiros.id
parent_id = aws_api_gateway_resource.pedido.id
path_part = "{id}"
}
resource "aws_api_gateway_method" "get_pedido" {
rest_api_id = aws_api_gateway_rest_api.parceiros.id
resource_id = aws_api_gateway_resource.pedido_id.id
http_method = "GET"
authorization = "NONE" # a chave de API IDENTIFICA; autorizacao fina fica fora de escopo aqui
api_key_required = true # sem chave valida, a requisicao nem chega ao passo de integracao
}
resource "aws_api_gateway_integration" "get_pedido" {
rest_api_id = aws_api_gateway_rest_api.parceiros.id
resource_id = aws_api_gateway_resource.pedido_id.id
http_method = aws_api_gateway_method.get_pedido.http_method
type = "HTTP_PROXY"
integration_http_method = "GET"
connection_type = "VPC_LINK"
connection_id = aws_api_gateway_vpc_link.parceiros.id
# aponta para o NLB interno; a porta e a do listener criado em nlb.tf
uri = "http://${aws_lb.parceiros.dns_name}/api/pedidos/{id}"
request_parameters = {
"integration.request.path.id" = "method.request.path.id"
}
}
resource "aws_api_gateway_method_settings" "throttle_padrao" {
rest_api_id = aws_api_gateway_rest_api.parceiros.id
stage_name = aws_api_gateway_stage.v1.stage_name
method_path = "*/*"
settings {
# limite de taxa e rajada QUANDO nao ha usage plan cobrindo a chamada —
# o plano de uso, abaixo, e o que vale para chamadas com chave valida.
throttling_rate_limit = 20
throttling_burst_limit = 10
}
}
resource "aws_api_gateway_deployment" "parceiros" {
rest_api_id = aws_api_gateway_rest_api.parceiros.id
triggers = {
redeploy = sha1(jsonencode([
aws_api_gateway_resource.pedido_id.id,
aws_api_gateway_integration.get_pedido.id,
]))
}
lifecycle { create_before_destroy = true }
}
# Estagios sao a unidade de versao deste desenho. v1 e v2 sao DUAS integracoes
# distintas — o Gateway roteia; quem devolve o payload no formato certo e o
# backend atras de cada uma.
resource "aws_api_gateway_stage" "v1" {
deployment_id = aws_api_gateway_deployment.parceiros.id
rest_api_id = aws_api_gateway_rest_api.parceiros.id
stage_name = "v1"
}
resource "aws_api_gateway_stage" "v2" {
deployment_id = aws_api_gateway_deployment.parceiros.id
rest_api_id = aws_api_gateway_rest_api.parceiros.id
stage_name = "v2"
}
# ── Identidade e cota: uma chave e um plano por parceiro ─────────────────────
resource "aws_api_gateway_api_key" "repique" {
name = "repique-calcados"
}
resource "aws_api_gateway_usage_plan" "repique" {
name = "plano-repique-calcados"
api_stages {
api_id = aws_api_gateway_rest_api.parceiros.id
stage = aws_api_gateway_stage.v1.stage_name # so v1: este parceiro ainda nao migrou
}
# Cota: teto por PERIODO (dia, semana ou mes). E o numero do contrato.
quota_settings {
limit = 500
period = "DAY"
}
# Throttle: taxa de reposicao do balde de tokens e o tamanho do balde. Protege
# contra RAJADA — e um mecanismo diferente da cota, com 429 de motivo diferente.
throttle_settings {
rate_limit = 3 # tokens repostos por segundo
burst_limit = 5 # tamanho do balde
}
}
resource "aws_api_gateway_usage_plan_key" "repique" {
key_id = aws_api_gateway_api_key.repique.id
key_type = "API_KEY"
usage_plan_id = aws_api_gateway_usage_plan.repique.id
}
resource "aws_api_gateway_api_key" "estacao" {
name = "estacao-fashion"
}
resource "aws_api_gateway_usage_plan" "estacao" {
name = "plano-estacao-fashion"
# Duas entradas: este parceiro tem acesso a v1 E v2 durante a janela de
# migracao. A revogacao de v1, quando chegar, e so remover este bloco.
api_stages {
api_id = aws_api_gateway_rest_api.parceiros.id
stage = aws_api_gateway_stage.v1.stage_name
}
api_stages {
api_id = aws_api_gateway_rest_api.parceiros.id
stage = aws_api_gateway_stage.v2.stage_name
}
quota_settings {
limit = 2000
period = "DAY"
}
throttle_settings {
rate_limit = 10
burst_limit = 20
}
}
resource "aws_api_gateway_usage_plan_key" "estacao" {
key_id = aws_api_gateway_api_key.estacao.id
key_type = "API_KEY"
usage_plan_id = aws_api_gateway_usage_plan.estacao.id
}
# O API Gateway precisa de uma role de CloudWatch configurada UMA VEZ por
# conta/regiao para logar. E uma policy gerenciada pela AWS: nao ha "*" para
# justificar aqui, so a permissao de escrever no CloudWatch Logs.
resource "aws_iam_role" "apigw_cloudwatch" {
name = "${var.projeto}-apigw-cloudwatch"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "apigateway.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "apigw_cloudwatch" {
role = aws_iam_role.apigw_cloudwatch.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
}
output "url_v1" {
value = "${aws_api_gateway_stage.v1.invoke_url}/pedidos/{id}"
description = "URL do estagio v1 — payload no formato antigo (pedidoId)"
}
output "url_v2" {
value = "${aws_api_gateway_stage.v2.invoke_url}/pedidos/{id}"
description = "URL do estagio v2 — payload no formato novo (id)"
}
Nunca coloque a chave de API na query string
Se a chave for passada como parâmetro de URL em vez de cabeçalho `x-api-key`, ela fica gravada em log de acesso, em histórico de navegador, em proxy intermediário e em qualquer ferramenta de observabilidade que capture URLs completas. O dano não é hipotético: é o mesmo padrão de vazamento de chave de terceiro que aparece em repositório público todo mês. O SDK e a documentação de exemplo do parceiro sempre devem apontar para o cabeçalho.
| Mecanismo | O que mede | Unidade | O que o 429 significa |
|---|---|---|---|
| Quota | total de requisições no período | por dia, semana ou mês | o parceiro esgotou o contrato; só volta no próximo período |
| Throttle — taxa | tokens repostos por segundo no balde | requisições/segundo | ritmo sustentado acima do contratado; passa a funcionar assim que o balde reencher |
| Throttle — rajada (burst) | tamanho do balde de tokens | requisições simultâneas | pico curto excedeu a capacidade instantânea; não indica abuso sustentado |
Construir: o backend não decide cota — só correlaciona
A mudança mais importante no código é o que SAI dele: toda a lógica de contagem por parceiro. O que entra é opcional e serve só para log.
// Program.cs — o que muda no backend: NADA na decisao de cota, so correlacao
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextPool<AppDb>(o =>
o.UseNpgsql(builder.Configuration.GetConnectionString("Padrao")));
var app = builder.Build();
// v1: formato antigo. Existe porque a Estacao Fashion ainda consome assim, e o
// contrato com ela nao muda so porque o codigo mudou — muda quando o CLIENTE
// aponta para outra URL. E o estagio do Gateway que decide qual destas duas
// rotas responde a cada parceiro, nao um "if" de versao dentro do controller.
app.MapGet("/api/pedidos/{id:guid}", async (Guid id, AppDb db) =>
await db.Pedidos.FindAsync(id) is { } ped
? Results.Ok(new { pedidoId = ped.Id, ped.Cliente, ped.Valor, ped.Status })
: Results.NotFound());
// v2: o campo renomeado que motivou a versao existir. O Gateway roteia o
// estagio v2 para ESTA rota — a transformacao do payload e responsabilidade do
// backend, nao do Gateway, a menos que voce escreva um mapping template VTL.
app.MapGet("/api/v2/pedidos/{id:guid}", async (Guid id, AppDb db) =>
await db.Pedidos.FindAsync(id) is { } ped
? Results.Ok(new { id = ped.Id, ped.Cliente, ped.Valor, ped.Status })
: Results.NotFound());
// O unico dado do Gateway que a aplicacao usa: um cabecalho de CORRELACAO para
// log, mapeado no Terraform a partir de $context.identity.apiKeyId — nunca para
// decidir cota (isso ja foi decidido) nem para autorizar (a chave nao prova
// identidade de usuario final, so identifica o parceiro contratante).
app.Use(async (ctx, next) =>
{
if (ctx.Request.Headers.TryGetValue("X-Partner-Correlation-Id", out var id))
app.Logger.LogInformation("Requisicao do parceiro {ChaveId}", id.ToString());
await next();
});
app.MapGet("/health/ready", async (AppDb db) =>
await db.Database.CanConnectAsync() ? Results.Ok() : Results.StatusCode(503));
app.Run();
A versão é rota, não transformação automática
Criar o estágio `v2` não faz o Gateway reescrever o corpo da resposta sozinho. Sem um mapping template VTL — fora do escopo deste laboratório, por ser mecanismo de transformação e não de contrato — o backend precisa devolver o formato certo para cada integração. É por isso que existem duas rotas no código: `/api/pedidos/{id}` para v1 e `/api/v2/pedidos/{id}` para v2.
Implantar, e provar com número
Cinco provas. A terceira é a que mais importa: ela mede o mesmo teste que, no desenho mínimo, estourava a cota tarde demais — e mostra o número parando no lugar certo.
# provas.sh — cinco medicoes; nenhuma conclusao vem de "parece que funcionou"
PROJETO=ffv-lab
V1=$(terraform output -raw url_v1)
V2=$(terraform output -raw url_v2)
CHAVE_REPIQUE=$(aws apigateway get-api-key --api-key "$(terraform output -raw chave_repique_id)" \
--include-value --query value --output text)
CHAVE_ESTACAO=$(aws apigateway get-api-key --api-key "$(terraform output -raw chave_estacao_id)" \
--include-value --query value --output text)
# ── Prova 1: sem chave, a requisicao nem chega a integracao ──────────────────
curl -s -o /dev/null -w '%{http_code}\n' "$V1/pedidos/11111111-1111-1111-1111-111111111111"
# Esperado: 403. E o Gateway recusando por FALTA de identificacao — a task nunca
# recebeu nada, o que se confirma vendo o log dela vazio no mesmo intervalo.
# ── Prova 2: a chave da Repique nao abre a v2 ─────────────────────────────────
curl -s -o /dev/null -w '%{http_code}\n' -H "x-api-key: $CHAVE_REPIQUE" \
"$V2/pedidos/11111111-1111-1111-1111-111111111111"
# Esperado: 403. A chave esta associada ao par (API, estagio v1) — nao a API
# inteira. E a prova de que versao aqui e decisao de contrato, nao so de rota.
# ── Prova 3: a cota da Repique estoura em 500, nao em 1000 ───────────────────
for i in $(seq 1 505); do
curl -s -o /dev/null -w '%{http_code} ' -H "x-api-key: $CHAVE_REPIQUE" \
"$V1/pedidos/11111111-1111-1111-1111-111111111111"
done; echo
# Esperado: 200 ate a requisicao 500, depois 429 com mensagem de "Limit Exceeded"
# (quota). Compare com o desenho minimo: la, o mesmo teste passava de 500 antes
# de qualquer bloqueio, porque o contador nao era compartilhado entre tasks.
# ── Prova 4: throttle e cota sao mecanismos diferentes ───────────────────────
# Dispare 20 requisicoes simultaneas (nao sequenciais) com a chave da Estacao
# (rate_limit=10, burst_limit=20) e repita 1s depois.
seq 1 20 | xargs -P20 -I{} curl -s -o /dev/null -w '%{http_code} ' -H "x-api-key: $CHAVE_ESTACAO" "$V1/pedidos/{}" ; echo
sleep 1
curl -s -o /dev/null -w '%{http_code}\n' -H "x-api-key: $CHAVE_ESTACAO" "$V1/pedidos/11111111-1111-1111-1111-111111111111"
# Esperado: algumas 429 na rajada (estourou o balde), e a chamada 1s depois volta
# a 200 — porque o balde reencheu. Diferente da prova 3: aqui NAO e o dia que
# precisa virar para voltar a funcionar.
# ── Prova 5: o mesmo servico ECS responde nos dois caminhos ──────────────────
aws ecs describe-services --cluster "$PROJETO" --services "${PROJETO}-api" \
--query 'services[0].loadBalancers[].targetGroupArn' --output table
# Esperado: DOIS ARNs de grupo de destino listados para o MESMO servico —
# confirma que a task nao foi duplicada, so passou a responder por dois caminhos.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · Sem chave, sem acesso | GET sem `x-api-key` | 403 do Gateway, log da task vazio no intervalo | se a task recebeu a chamada, a integração não está exigindo a chave (`api_key_required`) |
| 2 · Chave não abre outro estágio | chave de v1 chamando URL de v2 | 403 | se passar, o usage plan da Repique tem uma entrada de `api_stages` para v2 que não deveria existir |
| 3 · Cota estoura no número certo | 505 chamadas sequenciais com a chave de 500/dia | 200 até a 500ª, 429 (quota) da 501ª em diante | se passar de 500, a chave está associada a outro plano, ou o VPC Link está sendo contornado |
| 4 · Quota e throttle se comportam diferente | rajada de 20 simultâneas, repetir 1 s depois | alguns 429 na rajada; a chamada 1 s depois volta a 200 | se a chamada 1 s depois ainda falhar, o valor de `rate_limit` está baixo demais para o caso |
| 5 · Uma implantação, dois caminhos | `describe-services` → `loadBalancers` | dois ARNs de grupo de destino para o mesmo serviço | se só um aparecer, o segundo bloco `load_balancer` não foi aplicado |
Quebrar de propósito: três falhas e o diagnóstico
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Chave tratada como autenticação | use `X-Partner-Id` (o cabeçalho do desenho antigo) para decidir qual pedido devolver, em vez de usar o `id` da rota | um parceiro consegue ler pedido de outro só trocando um cabeçalho | revisão de código: qualquer decisão de acesso a dado baseada em cabeçalho não assinado | chave de API só decide cota; autorização de dado vem de outro mecanismo |
| Usage plan sem o estágio certo | crie o plano sem incluir `v2` em `api_stages` e chame v2 com a chave | 403 mesmo com chave válida, sem mensagem clara do motivo | `get-usage-plan` → `apiStages`, comparado ao estágio da URL chamada | adicionar o bloco `api_stages` para o estágio que falta |
| NLB sem health check compatível | aponte o health check do grupo de destino do NLB para uma porta que a task não escuta | todas as chamadas via Gateway devolvem 5xx, e o caminho do ALB continua funcionando normalmente | `describe-target-health` do grupo de destino dos parceiros — o do ALB permanece saudável | porta e caminho do health check batendo com o que a aplicação expõe em `/health/ready` |
O sintoma que engana: "a API caiu" quando só um caminho caiu
Como agora existem dois caminhos de entrada para a mesma implantação, um problema no NLB ou no VPC Link derruba só o tráfego dos parceiros — a loja própria, que passa pelo ALB, continua respondendo normalmente. Um painel que só olha "a API está de pé" sem separar por caminho de entrada relata sucesso enquanto um cliente pagante está fora do ar.
Segurança: o que muda quando existe um terceiro no contrato
Vender acesso programático desloca o risco: de "alguém acessa dado que não devia" para "um parceiro contratado consegue mais do que pagou, ou vê o que é de outro parceiro".
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Chave de API tratada como prova de identidade de dado | média | alto | autorização de recurso nunca decidida por cabeçalho do cliente; a chave só mede cota | revisão de código; teste que troca a chave e verifica isolamento de dado | remover a decisão de acesso baseada em cabeçalho; introduzir autorização real |
| Chave vazada em log ou repositório | média | alto | chave só em cabeçalho, nunca em query string; chave fora do controle de versão | CloudTrail em `GetApiKey`; varredura de segredo em repositório | revogar a chave (`aws_api_gateway_api_key` com `enabled = false`) e emitir outra |
| Um parceiro esgota o próprio plano e tenta contornar via versão errada | baixa | baixo | associação de chave por (API, estágio) — não existe "vazamento" de cota entre estágios | métrica de 429 por chave, separada por estágio | nenhuma ação de segurança necessária: é o comportamento desenhado |
| Vazamento de rota interna pelo caminho público do Gateway | baixa | médio | só os recursos explicitamente criados existem na REST API; nada de proxy genérico ("{proxy+}") | revisão da árvore de recursos do API Gateway | remover qualquer recurso curinga que não tenha sido desenhado de propósito |
| NLB interno exposto além do necessário | baixa | médio | `internal = true`, sem IP público; alcançável só pelo VPC Link | CloudTrail e Config Rule sobre `internet_facing` de balanceadores | recriar o NLB como interno; nenhum estado a migrar, ele é stateless |
Revogar chave sem aviso corta um parceiro em produção, na hora
Desabilitar ou apagar a chave de um parceiro tem efeito imediato — não existe janela de graça nem processamento em andamento que seja poupado. Se o parceiro tiver um lote agendado rodando naquele instante (ex.: sincronização noturna de estoque), a revogação no meio dele derruba a integração sem aviso e sem retry possível até alguém reativar. Rotação de chave por suspeita de vazamento é exatamente o caso em que isso é aceitável; revogação por atraso de pagamento ou renegociação de contrato merece uma janela combinada com o parceiro antes de acontecer.
Você precisa emitir uma chave de API por parceiro e definir cota diária diferente para cada um. Trocar a REST API por uma HTTP API do API Gateway, mais barata por requisição, resolve esse requisito?
Observabilidade: as perguntas que o painel tem de responder
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| Algum parceiro está sendo throttled com frequência? | `ThrottleCount` por API key | ou o parceiro cresceu além do plano, ou há um cliente com retry agressivo | > 10/min sustentado |
| A cota diária está sendo consumida antes da hora? | `Count` acumulado por API key vs quota | uso legítimo crescendo, ou uma integração com laço | > 80% da quota antes de 18h |
| O caminho dos parceiros está saudável, separado do ALB? | `4XXError`/`5XXError` por estágio | erro aqui não implica erro no tráfego da loja própria — são caminhos distintos | qualquer 5XX > 1% |
| A latência do salto extra importa? | `Latency` do estágio vs `TargetResponseTime` do ALB | a diferença é o custo fixo do Gateway + VPC Link, não do backend | diferença > 50 ms investigável |
| Alguém está chamando sem chave válida? | `4XXError` com detalhe de `MissingAuthenticationToken` | tentativa de acesso não contratado, ou integração mal configurada de um parceiro | qualquer volume sustentado |
Onde este painel termina, e onde o do L08 continua
Métricas do API Gateway respondem "o contrato está sendo cumprido e respeitado?". Elas não substituem o rastreamento ponta a ponta da requisição dentro da aplicação — isso é o `trace id` do L08, e ele continua funcionando exatamente igual atrás dos dois caminhos de entrada.
Métrica separada por método e estágio é opcional, e tem custo à parte
Por padrão, o CloudWatch agrega as métricas do Gateway por API. Ver `ThrottleCount` ou `4XXError` discriminado por MÉTODO e por chave exige habilitar métricas detalhadas no estágio, o que soma ao custo de monitoramento. Sem isso, um painel que parece mostrar "saúde por parceiro" está na verdade mostrando o agregado de todos eles somados.
Escala: 2 parceiros, 20, e o limite que não é do seu código
| Cenário | O que muda | O que passa a doer | O que fazer |
|---|---|---|---|
| 2 parceiros, como hoje | um usage plan por parceiro, gerenciado à mão | nada; é o volume deste laboratório | nada |
| 20 parceiros | a automação de emissão de chave e plano passa a ser necessária | criar plano manualmente no console não escala como processo | onboarding automatizado via API/Terraform, chave gerada por um formulário interno |
| 200 parceiros, tráfego alto | volume por chave se aproxima de limites de conta | limites de conta do API Gateway (requisições/segundo, número de planos) — consulte o Service Quotas para o valor atual | solicitar aumento de cota de conta antes de o limite ser atingido, não depois |
| Um parceiro decide fazer scraping agressivo | throttle da chave dele estoura constantemente | ruído nas métricas de todos os parceiros se o painel não filtrar por chave | painel por API key desde o início, não uma métrica agregada só |
| Falha de AZ durante pico de parceiro | NLB e ECS distribuídos por AZ absorvem | se o NLB só tiver subnet numa AZ, ele vira ponto único de falha do caminho novo | NLB com subnets nas mesmas AZs do ALB e do serviço ECS |
O throttle do usage plan não é o único teto que existe
Além do throttle por chave dentro do plano de uso, a conta tem um teto de requisições por segundo compartilhado entre TODAS as APIs REST daquela conta e região — um limite de plataforma, separado do que você configura por parceiro. Com poucos parceiros ele nunca aparece; a partir de dezenas de parceiros com tráfego real, vale confirmar o valor atual em Service Quotas antes de prometer capacidade a um novo contrato.
Custo: o que este laboratório acrescenta à fatura
Diferente do L03, este laboratório acrescenta recurso que cobra mesmo parado: o Network Load Balancer tem tarifa por hora, além do tráfego processado.
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| API Gateway (REST) | por milhão de requisições recebidas | cobra mesmo em chamadas rejeitadas por falta de chave — o 403 já é requisição contada |
| Network Load Balancer | hora ligada + unidade de capacidade de rede processada | existe mesmo sem tráfego de parceiro nenhum no dia — é custo fixo do caminho, não variável |
| VPC Link | não tem tarifa própria separada; o custo é o NLB por trás dele | criar um VPC Link "para testar" e esquecer deixa o NLB cobrando indefinidamente |
| Transferência de dados | tráfego entre Gateway e NLB, dentro da mesma região | baixo nesta escala; relevante só em volume alto de payload |
| CloudWatch Logs do Gateway | GB ingerido, se o log de acesso estiver ligado | log de acesso por requisição de 2 parceiros é desprezível; escala com volume, não com número de parceiros |
| Cenário | Volume | O que domina o custo | Otimização |
|---|---|---|---|
| Piloto (hoje) | 2 parceiros, ~2.500 requisições/dia somadas | a hora ligada do NLB, não o volume de requisição | nenhuma; o NLB já é o custo mínimo de ter integração privada |
| Crescimento | 20 parceiros, 100 mil requisições/dia | requisições do API Gateway passam a pesar mais que o NLB | nenhuma alavanca de arquitetura aqui; é custo proporcional ao negócio que ele habilita |
| Parceiro com tráfego alto e latência tolerante | um único parceiro em volume muito maior que os outros | requisições do Gateway para esse parceiro específico | considerar HTTP API + Lambda authorizer SÓ para esse caso, se cota deixar de ser o requisito central |
O custo oculto: NLB criado e esquecido
Ao contrário do excedente do L03, que dura minutos, o Network Load Balancer deste laboratório fica no ar até alguém apagá-lo — inclusive se o piloto com os dois parceiros for cancelado e ninguém lembrar de rodar a limpeza.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | onboarding de parceiro via Terraform, versão por estágio sem redeploy de código | emissão de chave ainda é manual, um recurso por vez | automação de onboarding com pipeline próprio | média |
| Segurança | chave nunca em query string, integração privada sem IP público novo | chave de API sem escopo fino de recurso | Lambda authorizer ou Cognito quando a autorização precisar ser por recurso (L12) | alta |
| Confiabilidade | dois caminhos de entrada independentes; falha num não derruba o outro | NLB numa única AZ seria ponto único de falha do caminho novo | garantir subnets do NLB nas mesmas AZs do serviço ECS | alta |
| Eficiência de performance | cota e throttle resolvidos na borda, sem consumir CPU da aplicação em chamada rejeitada | salto de rede extra (Gateway → VPC Link → NLB) soma latência | medir a diferença real com X-Ray (L08) antes de otimizar o que talvez não doa | baixa |
| Otimização de custos | tráfego interno não passa pelo Gateway, evitando custo onde não há contrato | NLB cobra por hora mesmo sem tráfego de parceiro | nenhuma óbvia nesta escala; o custo fixo é o preço de ter o contrato administrado | baixa |
| Sustentabilidade | nenhum recurso duplicado — a mesma task atende os dois caminhos | nenhum identificado neste laboratório | não se aplica | baixa |
Evolução em níveis: o que muda, e o que passa a doer
A terceira arquitetura não é um desenho: é a resposta a QUANDO trocar de desenho de acesso a terceiro.
ALB direto, cota tentada num Dictionary em memória, cabeçalho não verificado. É onde a Cadência estava.API Gateway REST com plano de uso por parceiro, VPC Link, dois grupos de destino, dois estágios de versão.Lambda authorizer ou Cognito para decidir não só QUEM chama, mas O QUE aquele chamador pode ver (L12).Emissão de chave, consulta de cota consumida e documentação da API expostos sem intervenção manual do time (AWS Amplify ou similar).Catálogo de APIs, contratos versionados formalmente, depreciação anunciada com prazo, painel de uso por linha de negócio.Padrão de consumo por parceiro alimenta previsão de capacidade e detecção de anomalia de uso (volume muito acima do histórico).A ordem não é negociável, e o motivo é concreto
Autorização por recurso (nível 3) depende de já existir identidade confiável por chave (nível 2) — sem isso, não há "quem" para autorizar em relação a "o quê". Quem tenta pular direto para autoatendimento de parceiro (nível 4) sem ter cota confiável (nível 2) está automatizando a venda de um contrato que a infraestrutura ainda não cumpre.
Onde IA entra nesta arquitetura, e onde não entra
Neste módulo, IA não resolve o problema central. "Quem pode pedir, quanto, e de qual versão" tem resposta determinística — plano de uso, chave e estágio — e um modelo não torna nenhuma dessas três decisões melhor.
Há um uso legítimo e modesto: classificar padrão de consumo por parceiro para sugerir ajuste de cota ANTES de o parceiro pedir aumento por e-mail. É previsão sobre um sinal estruturado que o Gateway já produz — métricas por chave — não geração de texto nem decisão de acesso.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | antecipar quando um parceiro vai estourar a cota, antes de o 429 aparecer para o cliente dele |
| Por que uma regra não bastaria? | uma regra simples ("alerta em 80% da cota às 14h") cobre a maior parte; um modelo só se justifica com histórico suficiente para prever sazonalidade, o que 2 parceiros não têm |
| De onde viriam os dados? | métricas de `Count` e `ThrottleCount` por API key, já emitidas pelo CloudWatch |
| Qual o risco? | sugerir aumento de cota para um parceiro cujo crescimento é um pico pontual, não uma tendência — cota é decisão comercial, e o modelo não deveria alterá-la sozinho |
| Por que não agora? | com 2 parceiros, o alerta de limiar simples já resolve o problema real; IA aqui seria peso sem sinal suficiente para aprender |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "decidir" se uma chamada de API deve ser autorizada, a partir do texto da requisição, troca um contrato determinístico e auditável — chave, plano, estágio — por uma decisão probabilística sobre acesso a dado de terceiro. Autorização é exatamente o tipo de decisão em que "provavelmente certo" não é uma categoria aceitável.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta | Quando é aceitável |
|---|---|---|---|---|---|
| Chave de API como autenticação/autorização | já existe, parece identidade, economiza implementar login | a chave não prova quem é o usuário final nem o que ele pode ver — só identifica o contratante | um parceiro acessa dado de outro trocando um parâmetro | chave decide cota; autorização de dado vem de outro mecanismo (L12) | nunca, quando há dado sensível por escopo |
| Cota calculada na aplicação | não exige recurso novo nem aprender API Gateway | o contador não é compartilhado entre réplicas, e o teto vira média, não limite | parceiro passa da cota contratada sem nenhum ataque, só por escalar | usage plan do API Gateway, contado fora da aplicação | protótipo com uma task fixa, nunca sob autoscaling |
| HTTP API "porque é mais barata" | a diferença de preço por requisição é real e chama atenção | HTTP API não tem chave de API nem plano de uso — o requisito de cota simplesmente não existe nela | implementar cota do zero em Lambda authorizer, reinventando o usage plan | escolher o tipo de API pelo requisito, não pelo preço isolado | quando não há cota nem chave por cliente a administrar |
| Gateway na frente de tráfego que não é de terceiro | parece mais "padronizado" ter um único caminho de entrada | paga fatura por requisição e latência extra onde não existe contrato para administrar | custo do Gateway crescendo com tráfego interno que nunca precisou de cota | tráfego sem terceiro continua direto no ALB | quando a organização decide, por política, centralizar observabilidade em um único ponto |
| Versão sem estágio, com `if` de versão no código | parece mais simples que configurar dois estágios | toda mudança de contrato afeta todos os parceiros no mesmo deploy, sem caminho de migração gradual | parceiro reclama de quebra sem aviso após um deploy qualquer | estágio por versão, cada chave associada só ao que ela deveria acessar | quando existe um único consumidor da API, sem terceiro para coordenar |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| 403 mesmo com chave válida | a chave não está associada ao usage plan do estágio chamado | liste as chaves do plano e compare com a chave usada | `get-usage-plan-keys` do plano do parceiro | associar a chave ao plano certo, ou incluir o estágio em `api_stages` |
| 502 só nas chamadas via Gateway; direto no ALB funciona | VPC Link ou NLB mal configurado — não é problema da aplicação | compare o resultado de uma chamada via Gateway com uma direta ao ALB | `describe-target-health` do grupo de destino dos parceiros | checar porta, protocolo e caminho do health check do NLB |
| Cota estourando antes do esperado | mais de um usage plan apontando para a mesma chave, ou cota errada no plano | liste os planos associados à chave | `get-usage-plan-keys` cruzado por chave | garantir que cada chave está em exatamente um plano com a cota correta |
| Parceiro na v1 recebe erro ao tentar v2 | comportamento esperado: a chave não está associada ao estágio v2 | confirme com o parceiro se ele deveria já ter acesso à v2 | tabela de `api_stages` do usage plan | adicionar o estágio v2 ao plano quando a migração for autorizada |
| Loja própria também recebe 429 | sinal de que o tráfego interno foi roteado pelo Gateway por engano | confirme se a chamada da loja está indo para a URL do ALB ou do Gateway | configuração de endpoint no cliente interno | apontar o tráfego interno de volta para o endpoint do ALB, sem chave |
A pergunta que resolve metade destes casos
O erro é do CONTRATO (chave, plano, estágio) ou da REDE (VPC Link, NLB, health check)? Erro de contrato aparece como 403 com corpo específico do Gateway. Erro de rede aparece como 502 ou 504, e o mesmo teste direto ao ALB continua funcionando — porque a aplicação nunca é o problema nesses casos.
Limpeza: o que o destroy não leva
O recurso que mais importa apagar aqui é o que cobra ligado independente de uso: o NLB.
# 1. Derrube o que o Terraform administra.
terraform destroy -auto-approve
# 2. CHAVES DE API: nao sao removidas por destroy se foram desassociadas
# manualmente do estado em algum momento. Confirme que sumiram.
aws apigateway get-api-keys --query "items[].{nome:name,id:id}" --output table
# 3. NETWORK LOAD BALANCER: cobra por hora mesmo sem trafego. Se o destroy
# falhar por dependencia (listener ou target group ainda associado),
# ele fica de pe indefinidamente.
aws elbv2 describe-load-balancers \
--query "LoadBalancers[?contains(LoadBalancerName, 'parceiros')]" --output table
# 4. VPC LINK: nao tem tarifa propria, mas so existe enquanto o NLB existir.
# Confirme que nao ficou orfao apontando para um NLB ja apagado.
aws apigateway get-vpc-links --query "items[].{nome:name,status:status}" --output table
# 5. Prova final: nada com a tag do projeto de pe.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=ffv-lab-parceiros \
--query "ResourceTagMappingList[].ResourceARN" --output table| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| REST API e estágios | sim | não | requisição já feita é o que cobrou; o recurso em si não tem tarifa fixa |
| Chaves de API | sim, se em Terraform | não | apagadas junto do usage plan quando gerenciadas por código |
| Network Load Balancer | sim, se sem dependência pendente | sim, por hora | o destroy falha se um listener ou target group ainda referenciar o NLB fora do estado do Terraform |
| VPC Link | sim | não diretamente | depende do NLB continuar existindo; ordem de destruição importa |
| Grupo de destino dos parceiros | sim | não | sem tarifa própria; o custo estava no NLB, não nele |
| ALB, RDS e o resto do L01 | não faz parte deste destroy | sim, por hora | este laboratório não toca na infraestrutura do L01 — ela continua cobrando separadamente |
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Cota vira média entre réplicas | usage plan do API Gateway | contagem fora da aplicação, compartilhada por todas as tasks |
| Cabeçalho forjável identifica o parceiro | chave de API + integração privada | a plataforma resolve a identidade antes de o pacote alcançar a VPC |
| HTTP API não guarda cota por cliente | REST API | é o único dos dois tipos com chave de API e plano de uso |
| REST API não fala direto com ALB em integração privada | VPC Link + Network Load Balancer | exigência do mecanismo, não preferência de desenho |
| Mudança de contrato quebraria os dois parceiros juntos | dois estágios (v1/v2) | cada chave associada só ao par (API, estágio) que ela deveria acessar |
| Loja própria não deveria pagar o custo do contrato | ALB inalterado, fora do Gateway | tráfego sem terceiro não tem cota nem identidade para administrar |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Teto de cota que vira média | usage plan contado fora da aplicação | nada relacionado a autorização de dado — cota não é permissão |
| Rajada breve de tráfego | throttle (taxa + rajada) | consumo sustentado acima do contratado por muitas horas — aí é a quota que age |
| Chave vazada em log | disciplina de nunca usar query string | chave vazada por outro canal (repositório, e-mail) — isso exige rotação |
| Mudança de contrato quebrando parceiro não migrado | estágio por versão | payload malformado dentro da MESMA versão — isso é validação de schema, fora de escopo aqui |
- O parceiro chama com `x-api-key` no cabeçalho, nunca na URL.
- O Gateway resolve a chave contra o usage plan associado ao (API, estágio) chamado.
- Quota e throttle são checados — dois mecanismos, dois tipos de 429.
- O estágio decide qual integração responde: v1 ou v2, cada uma com seu backend.
- A integração privada entrega via VPC Link a um Network Load Balancer interno.
- O NLB encaminha ao grupo de destino dos parceiros, registrado na mesma implantação ECS.
- A task responde sem saber que passou por um contrato — a decisão já foi tomada antes.
- A loja própria, nesse meio tempo, seguiu direto pelo ALB, sem cota nem chave.
Desafio — sem roteiro
O requisito
Um cliente do plano gratuito está consumindo a mesma cota que um cliente pago. Crie um segundo usage plan com limite mais baixo e associe a chave de API do cliente gratuito a ele.
Critério de aceite — executável, não "verifique se funciona"
Disparando requisições acima do limite do plano gratuito, a chave gratuita recebe HTTP 429 enquanto uma chave do plano pago, no mesmo minuto, continua recebendo 200.
- Dica 1: Usage Plan e API Key são recursos separados — a cota vive no plano, a identidade vive na chave, e a associação entre os dois é um terceiro recurso (`aws_api_gateway_usage_plan_key`).
- Dica 2: O limite de burst (rajada) é diferente do limite de taxa (por segundo) — um teste que dispara tudo de uma vez mede o burst, não o steady-state.
- Dica 3: Se as duas chaves estiverem devolvendo 429 juntas, confira se as duas foram associadas ao MESMO usage plan por engano — é o erro mais comum nesse desafio.
Lembrete de limpeza
Todo recurso que este desafio criar entra no mesmo `terraform destroy` do laboratório — nada fica para trás cobrando sozinho.
Perguntas frequentes
❓ HTTP API do API Gateway suporta chave de API e cota por cliente?
❓ Chave de API do API Gateway autentica o parceiro que está chamando?
❓ Qual a diferença entre quota e throttle num plano de uso do API Gateway?
❓ ALB sozinho consegue aplicar cota por cliente numa API?
❓ Preciso de NAT Gateway para o API Gateway alcançar o ECS por VPC Link?
❓ Um serviço do ECS pode ficar registrado em dois grupos de destino ao mesmo tempo?
❓ Por que a cota de um parceiro passou de 300 para quase 600 no mesmo dia?
❓ Posso versionar minha API só mudando o código, sem mexer no API Gateway?
Fixando
Numa arquitetura com ALB direto e cota calculada num `Dictionary` em memória de cada task ECS, um parceiro contratado para 300 requisições por dia consegue, na prática, ultrapassar esse número sem nenhum ataque, só usando a API normalmente. Por quê?
Um usage plan do API Gateway está associado ao par (API REST, estágio) — não à API inteira. Um parceiro tem chave válida apenas no estágio v1. O que acontece quando esse parceiro chama a URL do estágio v2 usando a mesma chave?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L01 no ar (ECS Fargate, ALB, RDS), conceitos básicos de REST e de balanceamento de carga |
| Conhecimentos adquiridos | diferença funcional entre REST API e HTTP API; chave de API como identificação e não autenticação; quota vs throttle como mecanismos distintos; VPC Link exigindo Network Load Balancer para REST API; múltiplos grupos de destino num único serviço ECS; versão de contrato por estágio |
| Limitação que fica | a chave de API não tem escopo por recurso — autorização fina sobre QUAL dado um parceiro pode ver ainda não existe neste desenho |
| Próximo exemplo recomendado | L26 — API 100% serverless, e onde ela não serve. Reaproveita a decisão de contrato deste módulo, trocando o backend em contêiner por Lambda e DynamoDB, com custo comparado |
| Também habilitado por este módulo | L12 (autenticação e sessão) resolve a lacuna de autorização por recurso apontada aqui; L41 (menor privilégio em IAM) se aplica à role do serviço-vinculado do ECS |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: Choose between REST APIs and HTTP APIs — a base da decisão entre os dois tipos, incluindo a ausência de chave de API e plano de uso na HTTP API; Usage plans and API keys for REST APIs — o comportamento de quota e throttle e a associação por (API, estágio); Set up private integrations with a VPC link for REST APIs — a exigência de Network Load Balancer para integração privada de REST API; e Registering multiple target groups with an Amazon ECS service — o limite de cinco grupos de destino por serviço e a exigência de role vinculada ao serviço. Os valores de preço não aparecem neste módulo por decisão: use o AWS Pricing Calculator, porque preço varia por região e envelhece mais rápido que o conteúdo.
O que não foi verificado, e você deve conferir na sua conta
Os valores de `rate_limit` e `burst_limit` usados nos exemplos (3/5 e 10/20) são ilustrativos, calibrados para o volume descrito no cenário — não são recomendação genérica. Os limites de conta do API Gateway (requisições por segundo no nível da conta, número de planos de uso, número de chaves) mudam por região e por solicitação de aumento; confira o valor atual em Service Quotas antes de dimensionar para volume real.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…