
Infraestrutura como código (IaC) não é fácil. Volta e meia, você precisa provisionar uma infraestrutura (hoje em dia quase sempre com Terraform) e acaba escrevendo um código sob medida para o seu caso de uso.
Em geral, o tempo é curto e você acaba deixando todos os valores padrão hardcoded, ainda por cima em um ou dois arquivões que concentram todo o trabalho. Muita gente pensa: "Agora eu faço funcionar e depois, quando sobrar um tempinho, vejo como melhorar".
Provisionando a infraestrutura com Terraform, do jeito certo desta vez.
O tempo passa e aparece outra demanda. Quando você se dá conta, já esqueceu o que exatamente fez para "funcionar agora". Ou então outra pessoa precisa entrar no seu código para entender o que ele faz, de modo que ele atenda tanto à tarefa antiga quanto à nova.
E ainda tem aquele medo enorme de quebrar o que está funcionando só porque você resolveu "deixar melhor".
Já passei por isso várias vezes e nunca tinha encontrado um jeito de tornar o Terraform mais amigável e fácil de depurar. Até cair neste post do Yevgeniy Birkman: " 5 lessons learned from writing over 300,000 lines of infrastructure code".
Ele me abriu os olhos sobre como deixar o Terraform mais robusto, mais limpo, mais amigável e, principalmente, como ganhar a confiança necessária para fazer as mudanças e melhorias que precisam ser feitas.
Agora quero compartilhar algumas dicas e experiências sobre refatoração de Terraform. O que vem a seguir é um resumo dos slides que você encontra no fim do artigo.
Terraform monolítico
Quando você trabalha em um arquivo TF gigante, qualquer deslize derruba tudo. Além disso, o Terraform fica muito mais difícil de depurar e achar o trecho certo para mexer vira uma garimpagem demorada. Esse padrão acaba gerando código duplicado e ciclos de desenvolvimento mais lentos.
Visão a 10.000 pés
Conseguir achar rapidamente o que você procura no meio do desenvolvimento ou ao depurar um problema crítico é essencial quando se trabalha com Terraform.
O plano do Terraform consolida todos os arquivos em uma única execução, e dá para tirar bom proveito disso criando arquivos menores e com muito mais visibilidade. Em geral, esses arquivos menores acabam se transformando em um módulo mais reutilizável e combinável.
Anatomia de um módulo
As "300.000 linhas de código de infraestrutura" foram um ótimo ponto de partida, e eu fui um passo além: criei um scaffold que uso em todo módulo novo. Esse scaffold dá mais visibilidade e serve de guia no desenvolvimento.
Não existem valores hardcoded; cada valor que seria fixo vira uma variável padrão, e todo atributo do módulo é uma variável (algumas obrigatórias, outras com valor default). Todo módulo deve ter a seguinte estrutura:

A ideia é colocar cada coisa no seu lugar. Se o arquivo main.tf principal passar de 30 linhas, divida-o em arquivos lógicos: ec2.tf, autoscaling.tf, e por aí vai.
Uma boa prática é manter uma pasta 'examples' tanto para apoiar o desenvolvimento do módulo quanto para servir de exemplo de uso no futuro. Quando começo um módulo novo, uso o seguinte snippet para criar esse scaffold:
➜ export module_name="sample"➜ mkdir -p $module_name/examples $module_name/test➜ cd $module_name && touch \ main.tf \ versions.tf \ default-variables.tf \ required-variables.tf \ outputs.tf \ data.tfEstrutura de módulos em 3 camadas
Crie e expanda sua própria biblioteca de blocos primitivos (terraform-resources)

Depois, monte serviços a partir desses blocos primitivos (terraform-services)

Implante ambientes ponta a ponta a partir dos serviços (terraform-live-envs)

O objetivo é isolar cada ambiente (live) — dev, staging, production — e então pegar cada componente desse ambiente e dividi-lo em módulos de serviço genéricos. Quebre cada módulo de serviço genérico em módulos de recurso, porque cada módulo deve fazer apenas uma coisa e ser o mais desacoplado possível.
Vamos passar por um exemplo:
Você tem uma instância mySQL rodando no Google Cloud SQL (terraform-live-envs), construída em cima de um serviço genérico chamado "sql", que contém dois módulos de recurso (instance e user).

Refatorando o código Terraform
Crie um novo bucket para guardar o novo state do Terraform. Em seguida, reescreva seu código nos módulos em 3 camadas (como mostrado acima e detalhado nos slides). Importe cada recurso para o seu código Terraform de live-envs.
O Terraform vai então exibir o plano de execução da operação de importação:
[1] valores que existem na versão implantada, mas não no seu código, recebem um sinal de menos, indicando remoção.
[2] valores que não existem na versão implantada, mas estão no seu código, recebem um sinal de mais, indicando adição.
O objetivo é chegar a um ponto em que o plano não acuse nenhuma mudança.
Slides
Os slides a seguir trazem uma breve introdução ao Terraform e minhas sugestões para refatorar módulos Terraform existentes: 