← Todos os artigos

Módulos de Terraform que escalam com a sua equipa, não contra ela

Um módulo de Terraform é a forma de uma equipa deixar de copiar e colar infra-estrutura. Mas um módulo que não é versionado, revisto e testado não escala a equipa — escala o raio do estrago. Uma alteração descuidada a um módulo 'vpc' partilhado e cada ambiente que aponta para ele herda o bug no próximo apply. A solução é deixar de tratar os módulos como pastas partilhadas e passar a tratá-los como produtos com versões.

Esta é a versão prática: o que torna um módulo fiável, como o publicar através de um pipeline a sério, e como as equipas o consomem — localmente e em CI — directamente do GitHub, fixado a uma tag ou, quando a segurança o exige, a um commit hash imutável. Sem precisar de um registry privado.

O que torna um módulo fiável

A fiabilidade começa antes de qualquer pipeline. Um módulo em que outras equipas confiam é pequeno e faz uma só coisa — uma rede, uma base de dados, um serviço — não uma 'plataforma' que faz tudo. Expõe um conjunto claro e documentado de inputs e outputs, traz defaults sensatos, e fixa as suas próprias versões de provider para se comportar igual hoje e daqui a seis meses.

Nada disto sobrevive a uma equipa em crescimento sem que duas coisas sejam inegociáveis: cada alteração é revista e cada alteração é testada contra infra-estrutura real e descartável. Um módulo é o único pedaço de código cujos bugs se multiplicam por cada consumidor — merece a fasquia mais alta que tiveres.

A versão é o contrato

À medida que a organização cresce e novos ambientes surgem — um sandbox aqui, uma segunda região ali — a versão deixa de ser um luxo e torna-se o contrato entre o módulo e toda a gente que o usa. Cada consumidor fixa uma versão explícita; ninguém segue a main. O versionamento semântico diz ao consumidor o que significa um salto: um patch é seguro, um minor acrescenta, um major obriga-o a mudar código.

Fixar é também como se avança e recua com confiança. Promover para produção é um salto de versão; uma má release é um salto de volta para a anterior. Sem versões, 'fazer rollback do módulo' quer mesmo dizer 'ir descobrir em que commit estava na terça-feira passada'.

Consome-o a partir do GitHub — sem registry

Não precisas de um registry privado nem de Artifactory para partilhar módulos como deve ser. Referencia o módulo directamente pela sua source de Git e por um ref. A mesma source funciona num portátil e em CI; a autenticação é só um token do GitHub — um PAT localmente, o token do runner em CI — ligado uma vez para que os clones por HTTPS levem a credencial.

# main.tf — fixar a uma tag publicada
module "vpc" {{
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"

  cidr_block = "10.20.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b"]
}}

A credencial define-se uma vez com o insteadOf do git, por isso nem o teu código nem o teu state contêm um segredo — o Terraform limita-se a clonar por HTTPS em teu nome:

# local + CI: deixar o git usar um token para o github.com em HTTPS
git config --global url."https://oauth2:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"

Imutabilidade: fixa um commit hash quando tem de ficar trancado

Uma tag é cómoda, mas é móvel: qualquer pessoa com permissões de escrita pode re-apontar ou forçar a v1.4.0 para outro código, e o teu próximo apply puxa-o. Para a maioria dos módulos esse compromisso serve. Para os que não podem mudar debaixo dos teus pés — tudo o que é sensível em segurança ou conformidade — fixa antes o commit hash completo. Um commit não pode ser trocado: ref=<sha> resolve sempre exactamente para os bytes que revistes.

# trancado a um commit exacto e impossivel de trocar
module "kms" {
  source = "git::https://github.com/acme/tf-modules.git//modules/kms?ref=3f9a1c4e8b7d6c5a4f3e2d1c0b9a8f7e6d5c4b3a"
}
Fixar um módulo: tag (móvel) vs commit hash (imutável) source = "git::https://github.com/acme/tf-modules.git//vpc?ref=v1.4.0" tag — cómoda, mas móvel: pode ser re-apontada ou forçada source = "git::https://github.com/acme/tf-modules.git//vpc?ref=3f9a1c4e8b7d" commit hash — imutável: ninguém troca o código debaixo de ti A mesma source funciona localmente e em CI — autentica com um token do GitHub. Sem registry nem Artifactory.
A mesma source de Git funciona localmente e em CI por trás de um token. Uma tag é cómoda mas pode ser movida; um commit hash é imutável — usa-o quando o módulo não pode mudar debaixo dos teus pés.

Publica o módulo como um produto

Se os consumidores fixam versões, algo tem de produzir essas versões de forma fiável — um pipeline no repositório do módulo. E vale a pena ser preciso: um módulo não 'se faz deploy'. É validado, testado e publicado. O deploy é trabalho do consumidor.

  1. Em cada pull request, corre as verificações estáticas: terraform fmt -check, terraform validate e tflint.
  2. Depois testa a sério — sobe o exemplo do módulo numa conta sandbox descartável, valida-o (terraform test, ou Terratest para verificações mais ricas) e destrói-o. Um módulo que nunca foi aplicado é um módulo por testar.
  3. No merge para a main, deixa o GitVersion calcular a próxima versão semântica a partir do histórico de commits, cria a tag de Git correspondente, e publica — anuncia a release no canal de Slack ou Teams da equipa (e espelha para um registry, se por acaso tiveres um).
Constrói o módulo como um produto: do PR a uma versão fixável NO PULL REQUEST validarfmt · validate · tflint testaraplica num sandbox · verifica · destrói merge para main NA MAIN GitVersioncalcula a próxima semver tagv1.4.0 publicaranuncia no Slack / Teams · (espelha para um registry)
Uma versão só existe depois de a alteração ter sido revista, validada e aplicada a sério. No merge, o GitVersion deriva a próxima semver, cria a tag e anuncia-a.
# .github/workflows/release.yml (esboco)
on: { pull_request: {}, push: { branches: [main] } }

jobs:
  check:
    steps:
      - run: terraform fmt -check -recursive
      - run: terraform validate
      - run: tflint
      - run: terraform test          # aplica os exemplos num sandbox e destroi

  release:
    if: github.ref == 'refs/heads/main'
    needs: check
    steps:
      - uses: gittools/actions/gitversion/execute@v3   # -> proxima semver
      - run: gh release create "v${VERSION}"
      - run: ./notify.sh "tf-modules ${VERSION} publicado"  # Slack / Teams

A ferramenta exacta não interessa — GitVersion, release-please e semantic-release servem todos. O que interessa é que uma versão só passa a existir depois de a alteração ter sido revista, validada e mesmo aplicada.

Agora usa-o: do Terraform ao Terragrunt

Com um módulo testado e com tag, um projecto de Terraform simples limita-se a fixá-lo — o bloco module acima é a história toda. Chega para um ou dois ambientes. Mas assim que corres a mesma stack em dev, staging e produção, numa ou mais regiões, copiar e colar essa ligação por ambiente é exactamente a duplicação que os módulos vieram matar.

O Terragrunt mantém os ambientes DRY

O Terragrunt envolve o Terraform para tirar essa repetição: gera a configuração de backend e de provider, mantém a versão do módulo num só sítio por stack, e deixa cada ambiente ser um ficheiro fino que diz 'este módulo, esta versão, estes inputs'. Promover na cadeia passa a ser um salto de versão de uma linha.

# live/prod/eu-west-1/vpc/terragrunt.hcl
terraform {
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"
}
include "root" { path = find_in_parent_folders() }

inputs = {
  cidr_block = "10.30.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

Uma estrutura que escala: projecto / ambiente / região

Organiza o repositório live para que o caminho seja o endereço da infra-estrutura. Uma forma propositadamente aborrecida é ambiente → região → componente — adapta os níveis à forma como a tua organização realmente pensa (há quem separe primeiro por projecto, conta ou equipa):

live/
├── root.hcl                    # state remoto, provider, tags comuns
├── dev/
│   └── eu-west-1/
│       ├── vpc/terragrunt.hcl
│       └── eks/terragrunt.hcl
├── staging/
│   └── eu-west-1/ ...
└── prod/
    ├── eu-west-1/
    │   ├── vpc/terragrunt.hcl
    │   └── eks/terragrunt.hcl
    └── eu-central-1/ ...

Cada folha fixa uma versão do módulo, e a pasta diz-te exactamente o que corre onde. Actualizar a produção passa a ser um PR revisto que muda um ref= de v1.4.0 para v1.5.0 — pequeno, óbvio e trivial de reverter.

Módulos fiáveis e de propósito único; revistos e testados a cada alteração; versionados e publicados por um pipeline; consumidos do GitHub por tag — ou por commit hash imutável quando importa — e montados com uma estrutura de Terragrunt que reflecte o teu parque. É essa a diferença entre módulos que escalam com a tua equipa e módulos que, em silêncio, escalam contra ela.

Todos os artigos

Tem um sistema como este?

Entre em contacto

Encontre-me nas redes sociais