AMI com contrato: anatomia do novo bloqueio de tipos no EC2
Um AMI sempre foi um contrato verbal. O artefato sabe exatamente de que hardware ele precisa, driver ENA, NVMe, arquitetura do binário, extensão de CPU que a JVM compilou, e nada disso estava escrito em lugar nenhum que o RunInstances pudesse ler. A partir de 4 de setembro de 2026 dá para escrever: o dono do AMI declara SupportedInstanceTypes, UnsupportedInstanceTypes ou os dois, e o EC2 bloqueia o lançamento fora da lista. Sem custo adicional, em todas as regiões. A leitura fácil é "menos launch quebrado". A leitura que interessa é outra, o ponto de enforcement mudou de lugar, e com ele mudou quem paga a conta da manutenção.
O problema: o contrato existia, mas não era executável
Todo time de plataforma que publica golden AMI já viveu esta cena. A imagem endurecida foi construída para x86_64, com agente de EDR compilado estaticamente e um kernel que só carrega driver de rede em host Nitro. Alguém abre um Auto Scaling group com attribute-based instance type selection pedindo 4 vCPU e 16 GiB, deixa a diversificação de Spot larga, que é a recomendação correta para preencher capacidade, e o EC2 escolhe uma família Graviton porque ela casa os atributos e é a mais barata do momento.
A instância sobe. É esse o detalhe que faz o modo de falha doer. Ela não morre no RunInstances; ela morre depois, sem console, sem rede, sem nada além de um health check que expira. O ASG espera o grace period padrão de 300 segundos, marca a instância como não saudável, termina e tenta de novo, e como a decisão de alocação não mudou, ele escolhe o mesmo pool. Você tem um loop de launch/terminate cobrado no mínimo de 60 segundos por instância criada, um alarme que dispara às duas da manhã e um gráfico que parece falta de capacidade.
As defesas que existiam eram todas do lado do chamador: condição ec2:InstanceType em política IAM, SCP na Organization, ExcludedInstanceTypes dentro do InstanceRequirements da frota. Todas corretas, todas com o mesmo defeito estrutural, elas descrevem o que aquele principal pode lançar, não o que aquela imagem aguenta. Compartilhe o AMI com 40 contas e você precisa replicar a regra 40 vezes, em 40 lugares que divergem.
O contrato viaja com o artefato, e falha no portão, não no health check
Desenho de referência: o pipeline gera a lista a partir de uma consulta de capacidade, grava a especificação no AMI, e o RunInstances vira o ponto único de avaliação para toda conta que recebeu a imagem.
- EC2 Image Builder · pipeline do golden AMI
- DescribeInstanceTypes · filtro por arquitetura + hypervisor
- ReplaceImageInstanceTypeSpecification · lista explícita, gerada
- AMI · InstanceTypeSpecification
- CopyImage / share · especificação preservada
- RunInstances · avalia tipo × contrato
- Auto Scaling group · ABIS + Spot diversificado
- Launch template · versão do AMI fixada
- Instância lançada · tipo dentro do contrato
- InvalidParameterCombination · lançamento bloqueado
- CloudTrail · errorCode em RunInstances
- Scaling activity · falha registrada no ASG
- CloudWatch · metric filter por imageId
Anatomia: duas listas, uma ordem de avaliação, zero estado
O atributo se chama InstanceTypeSpecification e carrega SupportedInstanceTypes e UnsupportedInstanceTypes. A lógica é curta o bastante para caber num ADR: sem especificação, tudo passa (é o default, e é por isso que nada quebrou no dia 4); só a lista de não suportados, passa tudo menos o que casar; com a lista de suportados, o tipo precisa casar a lista de suportados e não casar a de não suportados. A negação sempre vence.
Se escreve pela CLI com aws ec2 replace-image-instance-type-specification --image-id ami-... --instance-type-specification '{"SupportedInstanceTypes": ["t3.","a2."], "UnsupportedInstanceTypes": ["t3.micro"]}'. Omitir o parâmetro remove a restrição. Se lê no describe-images, que devolve o campo InstanceTypeSpecification com os padrões como estão. Um lançamento barrado devolve InvalidParameterCombination no RunInstances, com a mensagem apontando o próprio DescribeImages como lugar de investigar.
Quatro propriedades mudam o desenho, e nenhuma delas está no anúncio:
Replace, não Modify: a operação troca a especificação inteira. Não existe adicionar nem remover item, é read-modify-write, sem versão condicional. Dois pipelines corrigindo a mesma imagem em paralelo produzem last-writer-wins em silêncio, e o perdedor só descobre quando um lançamento legítimo é bloqueado.
Só o dono escreve. Conta que recebeu o AMI compartilhado consome o contrato e não negocia.
CopyImage preserva a especificação. É o que faz o padrão valer a pena em topologia multi-região: você declara uma vez e a réplica em sa-east-1 já nasce com o contrato.
Marketplace é mutuamente exclusivo. Não dá para pôr especificação em AMI listado no Marketplace, nem listar no Marketplace um AMI que já tem uma. Se você é ISV distribuindo appliance, este recurso não existe para você.
Três formas de restringir tipo de instância, e o que cada uma cobre de verdade
Condição IAM / SCP ec2:InstanceType | ExcludedInstanceTypes no ABIS | InstanceTypeSpecification no AMI | |
|---|---|---|---|
| Onde a regra vive | Na política do principal, por conta ou OU | Na configuração da frota ou do ASG | No artefato, uma vez, na conta dona |
| Vale para quem lança de outra conta | Não, a política é de lá, não sua | Não, quem configura a frota é o consumidor | Sim, o EC2 avalia independente do chamador |
| Sobrevive a CopyImage e compartilhamento | Irrelevante, não acompanha a imagem | Irrelevante, não acompanha a imagem | Sim, preservada na cópia |
| Quando a falha aparece | UnauthorizedOperation no lançamento | Nunca, o tipo só não é escolhido | InvalidParameterCombination no lançamento |
| Custo de manutenção | Alto: replicar e auditar N contas | Médio: uma edição por frota que existir | Baixo por lugar, alto por geração de hardware |
Onde o glob te trai: string não é predicado de arquitetura
As duas listas aceitam . A documentação dá os exemplos: t3. casa toda a família t3, g5. casa a g5, .12xlarge casa qualquer 12xlarge de qualquer família, *xlarge casa qualquer tipo xlarge ou maior. Parece expressivo. É onde eu pararia e faria a pergunta que importa: você está descrevendo hardware ou está descrevendo nomes?
O que o AMI realmente exige é uma capacidade: arm64 contra x86_64, hypervisor Nitro contra Xen, presença de GPU, presença de disco local NVMe. O que a especificação avalia é um padrão sobre a string do tipo. As duas coisas coincidem hoje porque a AWS batiza famílias Graviton com g e famílias com disco local com d. Elas coincidem por convenção, e convenção de nomenclatura não é contrato: c6gn, c7gd, m7i-flex, u7in-32tb e mac2-m2pro já mostram que o esquema tem exceções, sufixos e infixos que nenhum glob simples cobre. Um g. escrito para excluir Graviton é uma heurística, e heurística escrita à mão, num arquivo que ninguém revisita, é dívida com juros.
A segunda armadilha é temporal, e é a mais cara. Uma allow-list explícita bloqueia toda geração futura por construção. Famílias novas chegam a novas regiões o tempo todo; no dia em que a próxima geração aparecer no seu pool de Spot, a frota que você deixou larga de propósito, porque diversificação larga é a prática correta para capacidade Spot, vai ignorar exatamente a família mais barata e mais disponível. Você não vai receber erro nenhum: o ASG simplesmente não escolhe, o preço sobe um pouco, a taxa de interrupção sobe um pouco, e ninguém liga isso a um JSON escrito oito meses atrás. É a mesma tensão do OnDemandMaxPricePercentageOverLowestPrice, que já vem com 20% de folga por padrão justamente para não estreitar o pool sem avisar.
Gere a lista, não escreva a lista
O único jeito de manter o contrato honesto é derivá-lo de uma consulta de capacidade no mesmo build que produziu a imagem: aws ec2 describe-instance-types --filters Name=processor-info.supported-architectures,Values=x86_64 Name=hypervisor,Values=nitro --query 'InstanceTypes[].InstanceType' e alimentar o resultado direto no replace-image-instance-type-specification. Para frotas que já pensam em atributos, GetInstanceTypesFromInstanceRequirements faz o mesmo papel e te deixa pré-visualizar o conjunto antes de gravar. A lista fica explícita, verbosa e feia, e é exatamente isso que você quer, porque ela é regenerada a cada build, e o atraso em relação ao catálogo da AWS passa a ser a sua cadência de pipeline, não a memória de quem escreveu o glob.
O hard block em dia de incidente, e o sinal que ninguém instrumenta
A documentação é explícita: o EC2 aplica a especificação como bloqueio duro, e launch templates e Auto Scaling groups que apontem para o AMI podem falhar se o tipo configurado não for permitido. Isso não é um detalhe de implementação, é a troca central do padrão. Você converteu uma falha lenta e cara (instância zumbi, health check, loop) numa falha rápida e barata (InvalidParameterCombination no RunInstances). Rápida e barata é melhor em 95% dos dias. Nos outros 5%, você está no meio de um evento de capacidade zonal, o price-capacity-optimized finalmente encontrou um pool com capacidade, e o seu contrato diz não.
Em sistema financeiro isso tem nome: você trocou risco de correção por risco de disponibilidade, e trocou de propósito. Quando o AMI de fato não boota naquele hardware, a troca é trivialmente boa, a instância não ia servir tráfego mesmo. Quando a lista só está desatualizada, você inventou um modo de indisponibilidade que não existia antes, exatamente na janela em que a elasticidade importa. É por isso que allow-list gerada e deny-list mínima não são preferência estética: uma erra para o lado de deixar passar, a outra erra para o lado de barrar.
O buraco é observabilidade. A especificação não afeta instância que já está rodando, não emite métrica própria e não aparece em dashboard nenhum por default. O sinal auditável é o evento RunInstances no CloudTrail com errorCode, dá para fazer metric filter agrupando por requestParameters.imageId e alarmar em qualquer bloqueio, porque em regime normal a taxa esperada é zero. Bloqueio recorrente não é o controle funcionando; é um launch template apontando para um AMI que mudou de contrato debaixo dele. E adicione o par de leitura no runbook: describe-images com o campo InstanceTypeSpecification é a primeira coisa a olhar quando um lançamento falha por parâmetro, a própria mensagem de erro manda você para lá, e quem estiver de plantão às duas da manhã precisa saber disso antes de abrir um caso no suporte.
Como estragar isso
- Aplicar o contrato em AMI já compartilhado sem inventário de consumidores: você não vê os launch templates das outras 40 contas, e o bloqueio só aparece no próximo scale-out delas, que provavelmente é um pico, não um deploy.
- Escrever a lista à mão: um glob curado por humano é uma foto do catálogo de instâncias no dia em que foi escrito, e o catálogo muda toda semana.
- Dois pipelines escrevendo a especificação do mesmo AMI:
ReplaceImageInstanceTypeSpecificationsubstitui tudo e não tem escrita condicional; o segundo apaga o primeiro sem erro. - Usar isso como controle de custo: bloquear
*.24xlargepara conter gasto é política de FinOps disfarçada de compatibilidade, isso pertence a SCP comec2:InstanceTypeou aOnDemandMaxPricePercentageOverLowestPrice, onde a exceção tem dono e processo. - Confundir isso com validação em pipeline: o contrato falha no lançamento, não no build. Ele não substitui um teste que sobe a imagem em cada família permitida antes de promover o AMI a produção.
Lendo o padrão pelos pilares
Excelência operacional
O contrato vira código gerado no mesmo pipeline do AMI, versionado com ele. Adicione describe-images ao runbook: a mensagem de erro aponta para lá, e é o primeiro passo do diagnóstico.
Segurança
Restringir a lista não é isolamento nem menor privilégio, só o dono escreve, e a regra segue a imagem, mas ela não substitui condição IAM nem SCP para governança de quem lança o quê.
Confiabilidade
Ganho real ao eliminar o loop de launch/terminate por AMI incompatível; risco real de não escalar em evento de capacidade se a lista estiver desatualizada. Alarme com taxa esperada zero é o que separa os dois.
Otimização de custos
O recurso não custa nada e corta o desperdício das instâncias que subiam para morrer no health check, mas uma allow-list estreita empurra a frota para pools mais caros sem emitir sinal nenhum.
Eu ligaria isso primeiro nos AMIs que realmente têm um limite físico, imagem arm64, imagem com driver que só existe em Nitro, imagem de inferência que assume GPU, e deixaria em paz os AMIs de propósito geral, onde o default permissivo já está certo. Em toda imagem que restringir, eu geraria a lista por describe-instance-types dentro do build e falharia o pipeline se ela ficasse vazia, porque uma lista vazia por bug de query bloqueia tudo silenciosamente. E colocaria um alarme de taxa esperada zero em cima do errorCode do RunInstances no CloudTrail no mesmo dia, não na sprint seguinte. A lição que me custou caro, em plataforma financeira: todo controle que barra alguma coisa e não conta quantas vezes barrou vira, com o tempo, uma causa raiz de incidente que ninguém consegue nomear. O bloqueio é a parte fácil; o contador é o que te salva às duas da manhã.
Referências
Veredito
Adote, com escopo estreito e com a lista gerada. Use InstanceTypeSpecification quando três condições valerem juntas: o AMI tem uma incompatibilidade de hardware real e não uma preferência de custo, você não controla todas as contas que lançam a imagem, e o pipeline que constrói o AMI consegue derivar a lista de uma consulta de capacidade a cada build. Faltando qualquer uma delas, a condição ec2:InstanceType em SCP continua sendo a ferramenta certa, ela erra do lado de quem sabe pedir exceção. Não use isso como controle de FinOps, não escreva glob à mão e não ligue em AMI compartilhado sem antes rodar describe-images contra os launch templates que já apontam para ele. O recurso não custa nada e o default permissivo garante que nada quebre até você agir; o preço é integralmente de manutenção, e ele vence toda vez que a AWS lançar uma família nova. Se o seu time não tem onde pendurar essa manutenção, o padrão continua correto e a resposta ainda é não.
Continue lendo
Deep dives de arquitetura, AWS, IA e mercado: direto no seu email. Grátis.
Sem spam · cancele quando quiser
Pergunte ao Fernando sobre isto
Uma resposta focada sobre este artigo, do meu assistente de IA, baseada no meu trabalho.
Participe da conversa
Entre para comentar
Confirme seu e-mail para participar, você também recebe a newsletter. Sem senha.