Guia - TF-PRO HashiCorp Terraform Authoring and Operations Professional
Última revisão: maio de 2026
Uma referência rápida dos padrões arquiteturais que o exame TF-PRO avalia. Leia de cima a baixo ou pule para uma seção.
HCL e Configuração
Um recurso precisa de um número variável de blocos aninhados idênticos (por exemplo, regras de entrada) impulsionados por uma lista/mapa.
Use um bloco `dynamic` cujo `for_each` itera a coleção; referencie cada elemento através do iterador de bloco (nome padrão = rótulo do bloco) dentro de `content {}`.
Por quê: Blocos dinâmicos geram blocos aninhados repetidos sem copiar e colar; o iterador mantém cada bloco gerado vinculado ao seu elemento de origem.
Crie um recurso por entrada em um mapa de objetos, com chave estável para que a reordenação nunca force a substituição.
Defina `for_each = var.objects` (um mapa). Use `each.key` para a chave estável e `each.value.<attr>` para os campos. Evite `count` aqui - mudanças de índice causam instabilidade.
Por quê: As chaves do mapa são identidades estáveis no estado; os índices da lista são posicionais e mudam quando elementos são adicionados/removidos.
Decida entre count e for_each para múltiplas instâncias.
Use `for_each` quando as instâncias têm identidades distintas (um conjunto/mapa); use `count` apenas para N cópias idênticas e insensíveis à ordem. Prefira `for_each` para qualquer coisa que possa crescer/diminuir.
Por quê: `for_each` endereça por chave (recurso["chave"]); `count` endereça por índice (recurso[0]), que se reorganiza em inserções/exclusões.
Uma variável de entrada é um objeto onde alguns atributos são opcionais e precisam de valores padrão.
Tipifique-o como `object({ name = string, size = optional(number, 10) })`. `optional(type, default)` fornece o valor padrão quando o chamador omite o atributo.
Por quê: `optional()` com um valor padrão mantém os chamadores concisos enquanto garante um valor concreto a jusante - sem tratamento de nulo em todos os lugares.
Valide uma suposição sobre um recurso antes de aplicar, ou garanta um resultado depois.
Use `lifecycle { precondition { ... } }` para afirmar entradas antes da criação/atualização, e `postcondition` para afirmar saídas depois. Ambos aceitam `condition` + `error_message`.
Por quê: Condições personalizadas falham rapidamente com uma mensagem clara, em vez de produzir uma aplicação quebrada ou um erro a jusante confuso.
Um recurso deve ser recriado sempre que outro recurso ou atributo muda.
Adicione `lifecycle { replace_triggered_by = [aws_x.y.id] }`. Quando o valor referenciado muda, o Terraform força a substituição deste recurso.
Por quê: Expressa uma dependência de substituição de forma declarativa, evitando `-replace` manual em cada mudança relacionada.
A substituição de um recurso causa tempo de inatividade porque o antigo é destruído antes que o novo exista.
Defina `lifecycle { create_before_destroy = true }` para que o Terraform provisione primeiro a substituição e depois destrua o antigo. Garanta nomes únicos/sem conflitos difíceis.
Por quê: Substituição com tempo de inatividade zero; mas fique atento a colisões de nomes e limites de cota enquanto ambos existem brevemente.
Rejeite valores de entrada inválidos precocemente (por exemplo, um ambiente que não seja dev/stage/prod).
Adicione um bloco `validation { condition = contains(["dev","stage","prod"], var.env), error_message = "..." }` à variável.
Por quê: Detecta entradas inválidas no momento do planejamento com uma mensagem legível, em vez de falhar profundamente em uma chamada de provedor.
Referencie um valor que pode não existir sem travar o plano.
Use `try(local.maybe.value, "default")` para fallback em erros, ou `can(expr)` para obter um booleano se uma expressão for bem-sucedida.
Por quê: Manipulação elegante de dados opcionais/variáveis; evita "Error: Unsupported attribute" em chaves ausentes.
Transforme uma lista em um mapa, ou filtre/formate uma coleção para um argumento de recurso.
Use uma expressão `for`: `{ for u in var.users : u.name => u.role if u.active }` (mapa) ou `[for x in list : upper(x)]` (lista).
Por quê: Expressões `for` são a maneira idiomática de remodelar dados; a cláusula `if` filtra, a forma `k => v` constrói mapas.
Uma variável ou saída contém um segredo que não deve ser impresso na saída de plan/apply.
Marque a variável como `sensitive = true` (e as saídas também). O Terraform a oculta na saída da CLI, embora ainda seja armazenada no estado.
Por quê: Previne a divulgação acidental em logs/saída de CI; o estado em si ainda deve ser protegido (backend criptografado, controle de acesso).
Gerencie recursos em duas regiões/contas dentro de uma única configuração.
Declare provedores apelidados (`provider "aws" { alias = "west" region = "us-west-2" }`) e defina `provider = aws.west` nos recursos ou passe para os módulos.
Por quê: Aliases permitem que uma configuração vise múltiplas instâncias de provedor; os módulos os recebem explicitamente via o argumento `providers`.
Uma dependência oculta (não expressa por meio de referências) causa problemas de ordenação.
Adicione `depends_on = [aws_iam_role_policy.x]` para forçar a ordenação. Use com moderação - prefira dependências implícitas via referências de atributos.
Por quê: `depends_on` explícito lida com dependências que o grafo não pode inferir, mas o uso excessivo cria planos conservadores e mais lentos.
Renderize um arquivo de configuração/user-data a partir de um template com variáveis estruturadas.
Use `templatefile("${path.module}/tpl.tftpl", { items = local.items })`; o template usa interpolação `%{ for }` / `${}`.
Por quê: `templatefile` mantém a renderização pura/no momento do plano (ao contrário do provedor de template obsoleto) e suporta loops/condicionais.
Crie uma lista plana de cada combinação (subnet, rule) para alimentar um único `for_each`.
Use `setproduct(var.subnets, var.rules)` para o produto cartesiano, ou `flatten([for ...])` para colapsar listas aninhadas em uma.
Por quê: Estas funções transformam dados aninhados na coleção plana e com chave única que `for_each` requer.
Módulos
Um módulo de registro muda e altera inesperadamente a infraestrutura no próximo `init`.
Fixe com `version = "~> 4.2"` (apenas módulos de registro). Para fontes Git, fixe uma tag `?ref=v4.2.0`. Execute `terraform init -upgrade` deliberadamente para mover os fixadores.
Por quê: Módulos não fixados flutuam para a versão mais recente; fixar torna as atualizações intencionais e revisáveis.
Um módulo raiz precisa de um valor produzido profundamente dentro de um módulo filho.
Exponha-o como uma `output` no filho, então referencie `module.child.output_name`. Valores não expostos não são acessíveis aos chamadores.
Por quê: Módulos são encapsulados; as saídas são a única forma de os dados cruzarem o limite do módulo para cima.
Instancie o mesmo módulo uma vez por equipe/ambiente a partir de um mapa.
Defina `for_each` no bloco do módulo: `module "env" { for_each = var.envs; source = "./env"; name = each.key }`. Referencie `module.env["prod"]`.
Por quê: `for_each` em módulos dimensiona um padrão sem copiar e colar blocos; as chaves fornecem endereços estáveis.
Um módulo filho deve criar recursos em um provedor não padrão (apelidado).
Passe provedores explicitamente: `module "x" { providers = { aws = aws.west } }`. O filho declara o provedor em `required_providers` com `configuration_aliases`.
Por quê: Módulos não herdam provedores apelidados implicitamente; o mapa de provedores conecta o alias pai ao filho.
Renomear um recurso ou movê-lo para um módulo normalmente o destruiria e recriaria.
Adicione um bloco `moved { from = aws_instance.old; to = module.compute.aws_instance.new }`. O Terraform atualiza os endereços de estado sem destruição.
Por quê: Blocos `moved` tornam refatorações seguras e revisáveis no código, substituindo o `terraform state mv` manual.
Um módulo monolítico se tornou difícil de gerenciar e mistura preocupações de rede, computação e dados.
Decomponha em módulos filhos focados e os componha em um raiz, passando as saídas de um como entradas para o próximo. Mantenha os módulos com propósito único.
Por quê: A composição melhora a reutilização e a testabilidade; módulos com escopo restrito versionam e evoluem independentemente.
Os consumidores passam combinações inválidas de entradas para um módulo compartilhado.
Adicione blocos `validation` e `precondition` dentro do módulo para impor contratos, e documente as entradas com `description`.
Por quê: Um módulo é responsável pelo seu contrato; validar internamente protege todos os chamadores, não apenas uma configuração raiz.
Módulos profundamente aninhados tornam o fluxo de dados e a passagem de provedores difíceis de seguir.
Mantenha o aninhamento superficial (1-2 níveis). Passe provedores e entradas chave explicitamente em cada nível; evite depender de herança implícita profunda.
Por quê: Árvores superficiais são mais fáceis de entender; aninhamento profundo amplifica a complexidade da passagem de provedores e do encadeamento de saídas.
Publique um módulo reutilizável no registro privado e evolua-o sem quebrar os chamadores.
Marque os lançamentos com semver (`v1.2.0`); mudanças de entrada/saída que quebram a compatibilidade aumentam a versão principal. Os chamadores fixam com restrições `~>`.
Por quê: O versionamento semântico permite que os consumidores adotem correções/funcionalidades com segurança e optem por mudanças que quebram a compatibilidade deliberadamente.
Escolha de onde obter um módulo para um determinado nível de maturidade.
Caminhos locais (`./modules/x`) para módulos no repositório, Git (`git::...?ref=tag`) para módulos compartilhados, mas não publicados, registro (`namespace/name/provider`) para módulos versionados/publicados.
Por quê: O tipo de fonte corresponde ao escopo de compartilhamento; apenas fontes de registro suportam o argumento `version` e a resolução de restrições.
Uma saída de módulo contém um segredo consumido pela raiz.
Marque a saída do módulo como `sensitive = true`. Consumi-la em um contexto não sensível causará um erro até que você também a trate como sensível.
Por quê: A sensibilidade se propaga através do limite do módulo, prevenindo vazamentos acidentais na saída raiz.
Um conjunto de recursos existente baseado em `count` precisa se tornar `for_each` sem destruir instâncias.
Adicione blocos `moved` mapeando cada índice `resource[0]` para o novo endereço `resource["key"]`, e então mude para `for_each`.
Por quê: Blocos `moved` remapeiam o estado de endereçamento posicional para endereçamento por identidade, evitando destruir/recriar.
CLI e Gerenciamento de Estado
Você renomeou um recurso na configuração; o plano agora quer destruir o antigo e criar um novo.
Prefira um bloco `moved` na configuração. Para correções ad-hoc/CLI, use `terraform state mv aws_x.old aws_x.new` para redirecionar o objeto existente.
Por quê: Ambos atualizam o endereço de estado para que o Terraform veja o objeto existente como o recurso renomeado - sem destruição.
Traga um recurso existente, criado manualmente, sob gerenciamento do Terraform.
Adicione um bloco `import { to = aws_x.y; id = "i-123" }` e execute `terraform plan -generate-config-out=gen.tf` para gerar a configuração inicial, então refine e aplique.
Por quê: A importação dirigida por configuração é revisável e gera uma configuração inicial, ao contrário do antigo `terraform import` imperativo.
Pare de gerenciar um recurso com Terraform, mas deixe-o funcionando na nuvem.
Execute `terraform state rm aws_x.y`. O Terraform esquece o objeto; ele não é destruído. Remova também sua configuração para evitar um plano de recriação.
Por quê: `state rm` desvincula sem excluir - útil ao passar um recurso para outra ferramenta/equipe.
Mova o estado de um backend local para S3 (ou para HCP Terraform).
Adicione/substitua o bloco `backend`/`cloud`, execute `terraform init` - o Terraform detecta a mudança e solicita a migração do estado existente para o novo backend.
Por quê: `init` orquestra a cópia; responder sim migra o estado com segurança, em vez de começar vazio.
Dois engenheiros executam `apply` contra o mesmo estado remoto simultaneamente.
Use um backend que suporte bloqueio (S3+DynamoDB, HCP Terraform, etc.). O Terraform adquire um bloqueio por operação; a segunda execução espera ou gera um erro.
Por quê: O bloqueio impede gravações simultâneas que corromperiam o estado. Nunca o desative casualmente.
Uma aplicação travada deixou um bloqueio obsoleto e agora toda execução está bloqueada.
Confirme que nenhuma operação está realmente em execução, então `terraform force-unlock <LOCK_ID>`. Use o ID da mensagem de erro.
Por quê: `force-unlock` limpa um bloqueio órfão; fazer isso enquanto uma operação real está em execução arrisca a corrupção do estado.
Detecte a divergência entre a configuração/estado e a infraestrutura real sem propor mudanças.
Execute `terraform plan -refresh-only` (ou `apply -refresh-only` para atualizar o estado). Ele relata diferenças sem planejar mudanças de recurso.
Por quê: Separa a detecção de divergência do planejamento de mudanças - você vê o que mudou na nuvem antes de decidir conciliar.
Um recurso está se comportando mal e você quer recriá-lo sem editar a configuração.
Execute `terraform apply -replace="aws_instance.web"`. Este é o substituto moderno para o `terraform taint` obsoleto.
Por quê: `-replace` força um recurso a ser destruído e recriado na próxima aplicação, declarativamente na CLI.
Você está tentado a usar `-target` rotineiramente para acelerar as aplicações.
Use `-target` apenas para recuperação de erros ou correções cirúrgicas. Evite-o como um fluxo de trabalho normal - ele produz aplicações parciais e pode pular dependências.
Por quê: O direcionamento rotineiro oculta problemas de dependência e resulta em estado incompleto; HashiCorp o documenta como uma ferramenta excepcional.
Um provedor mudou de namespace (por exemplo, hashicorp/aws para um fork) e o estado referencia o endereço antigo.
Execute `terraform state replace-provider registry.terraform.io/hashicorp/aws registry.example.com/org/aws`.
Por quê: Reescreve as referências do provedor no estado para que `init`/`plan` resolvam a nova fonte sem recriar recursos.
Uma configuração precisa de saídas produzidas por outra configuração/workspace.
Use a fonte de dados `terraform_remote_state` (ou saídas de execução do HCP Terraform) para ler as saídas de outro estado em modo somente leitura.
Por quê: Compartilha valores entre limites de estado sem duplicar recursos; apenas saídas exportadas são legíveis.
As configurações de backend (bucket, chave) diferem por ambiente e não devem ser codificadas.
Deixe-as fora do bloco `backend` e passe no `init`: `terraform init -backend-config=prod.hcl` (ou `-backend-config="key=..."`).
Por quê: A configuração parcial mantém uma configuração reutilizável entre ambientes, fornecendo valores de backend específicos do ambiente no `init`.
Você precisa de estados separados para dev/stage/prod a partir de uma única configuração.
Use workspaces da CLI (`terraform workspace new prod`) para isolamento leve, ou configurações raiz/workspaces HCP separados para uma separação mais forte.
Por quê: Cada workspace tem seu próprio estado; referencie `terraform.workspace` para variar nomes/tamanhos. Para isolamento forte, prefira backends/workspaces distintos.
Operações do HCP Terraform
Evite armazenar chaves de nuvem de longa duração em variáveis de workspace do HCP Terraform.
Configure credenciais de provedor dinâmicas: o HCP Terraform usa OIDC/identidade de carga de trabalho para obter credenciais de curta duração de AWS/Azure/GCP/Vault por execução.
Por quê: Elimina segredos estáticos; as credenciais são criadas just-in-time e expiram, diminuindo o raio de impacto.
As mesmas variáveis (configuração de provedor, tags) são necessárias em muitos workspaces.
Defina um conjunto de variáveis e aplique-o a um projeto ou a workspaces selecionados. Variáveis em nível de workspace sobrescrevem valores de conjuntos de variáveis.
Por quê: Conjuntos de variáveis evitam duplicar configurações compartilhadas; a precedência (workspace > conjunto) permite que um workspace sobrescreva quando necessário.
Aplique barreiras de proteção (sem S3 público, tags obrigatórias) em cada execução.
Anexe um conjunto de políticas Sentinel ou OPA. Defina o nível de aplicação: consultivo (aviso), soft-mandatório (substituição com permissão) ou hard-mandatório (bloqueio).
Por quê: Política como código controla as execuções centralmente; os níveis de aplicação equilibram o rigor com a flexibilidade operacional.
Integre uma verificação externa (estimativa de custo, varredura de segurança) no pipeline de execução.
Configure uma tarefa de execução em um estágio (pré-planejamento, pós-planejamento, pré-aplicação). O HCP Terraform chama o serviço externo e controla a execução com base em seu resultado.
Por quê: As tarefas de execução estendem o pipeline com verificações de terceiros sem a necessidade de customizações complexas de CI.
Escolha como as execuções são disparadas para um workspace.
Acionadas por VCS (commit/PR dispara o plano), acionadas por CLI (`terraform plan/apply` contra o remoto), ou acionadas por API (configuração enviada). Escolha por fluxo de trabalho da equipe.
Por quê: Acionadas por VCS são adequadas para GitOps; acionadas por CLI são adequadas para iteração local; acionadas por API são adequadas para pipelines personalizados. Elas são mutuamente exclusivas por workspace.
Conceda a uma equipe acesso de gravação a workspaces de staging, mas somente leitura à produção.
Defina permissões em nível de organização/projeto/workspace: atribua acesso da equipe (leitura/planejamento/escrita/administração) por projeto ou workspace; use o agrupamento de projetos para gerenciar em escala.
Por quê: Permissões granulares e com escopo forçam o menor privilégio; concessões em nível de projeto reduzem o gerenciamento por workspace.
A aplicação de um workspace de rede deve enfileirar automaticamente uma execução em workspaces de aplicativo dependentes.
Configure um gatilho de execução: o workspace a jusante se inscreve no a montante; uma aplicação bem-sucedida enfileira a execução a jusante.
Por quê: Gatilhos de execução encadeiam workspaces dependentes para que as mudanças na infraestrutura compartilhada se propaguem em ordem.
Permita que usuários não-Terraform provisionem infraestrutura padronizada através de um formulário.
Publique um módulo sem código no registro privado; os usuários o instanciam através da UI, fornecendo apenas entradas - sem autoria de HCL.
Por quê: Módulos sem código democratizam o provisionamento self-service, mantendo a configuração subjacente governada e versionada.
Compartilhe módulos e provedores aprovados por toda a organização.
Publique no registro privado do HCP Terraform; os consumidores referenciam `app.terraform.io/org/name/provider` com restrições de versão.
Por quê: Um registro privado centraliza a descoberta, versionamento e governança de módulos internos.
Organize dezenas de workspaces por equipe/aplicação para permissões e conjuntos de variáveis.
Agrupe workspaces em projetos; aplique permissões de equipe e conjuntos de variáveis em nível de projeto.
Por quê: Projetos escalam a governança - você gerencia o acesso e a configuração compartilhada por projeto, em vez de por workspace.
Detecte continuamente quando a produção se desvia do estado configurado.
Habilite avaliações de saúde (detecção de drift / validação contínua) no workspace; o HCP Terraform periodicamente atualiza e relata o drift e as asserções falhas.
Por quê: Avaliações automatizadas revelam desvios e pós-condições quebradas entre as aplicações, antes que causem incidentes.
O HCP Terraform deve alcançar a infraestrutura dentro de uma rede privada sem entrada pública.
Implante agentes do HCP Terraform na rede privada e atribua o workspace a um pool de agentes; as execuções são realizadas via agente.
Por quê: Agentes permitem que o HCP Terraform opere em ambientes privados/isolados sem expô-los publicamente.
Uma aplicação inválida corrompeu o estado e você precisa recuperá-lo.
O HCP Terraform mantém o estado versionado; reverta para uma versão de estado anterior pela UI/API do workspace e replaneje.
Por quê: O versionamento de estado integrado fornece pontos de recuperação sem a necessidade de gerenciar snapshots de backend por conta própria.