Terraform AWS Cloud: gerenciamento de infraestrutura saudável
Publicados: 2022-03-11Escrever um aplicativo é apenas parte da história. Para que ele tenha valor, ele precisa ser implantado em algum lugar que possa ser dimensionado, precisa ser executado com alta disponibilidade, precisa de backups e assim por diante.
Mais e mais desenvolvedores são obrigados a pelo menos ter uma noção desse processo de implantação. Isso se manifesta, por exemplo, no DevOps se tornando uma função frequentemente solicitada hoje em dia, à medida que a complexidade do sistema aumenta. Não podemos nos permitir ignorar essas mudanças e precisamos estar cientes de como projetar aplicativos para serem facilmente implantáveis.
Isso também é do interesse de nossos clientes: eles nos contratam como especialistas em nossas áreas e esperam que entreguemos o produto completo, muitas vezes do início ao fim. Eles têm requisitos e geralmente ignoram a pilha em que sua solução de negócios é executada. No final, é o valor comercial do produto que importa.
Apresentando o Terraform
O gerenciamento de implantação e infraestrutura não é um processo simples. Além de muita experiência de domínio em constante mudança, precisamos aprender uma outra ferramenta ou um novo fluxo de trabalho. Se você está adiando isso, este artigo é uma chance para você se familiarizar com uma abordagem para gerenciamento de infraestrutura. Espero que no final você fique mais confiante em usar o Terraform e saiba mais sobre as possíveis abordagens e desafios. Você deve ser capaz de usar essa ferramenta para, pelo menos, começar a gerenciar alguma parte de sua infraestrutura de nuvem.
O Terraform é uma camada de abstração, sim, e as abstrações são vazadas, concordo. Mas, no final, estamos no negócio de resolver problemas e gerenciar abstrações. Este tem como objetivo nos proporcionar mais sanidade em nossas tarefas do dia a dia.
O objetivo
Vou explicar o que é o Terraform, como ele se encaixa em todo o ecossistema e como ele se compara a outras ferramentas semelhantes. Em seguida, mostrarei as etapas necessárias para configurar uma configuração do Terraform para vários ambientes e pronta para produção para uma equipe. Explicarei o básico sobre como escrever a configuração do Terraform — como gerenciar a complexidade e duplicar o código com módulos compartilháveis .
Os exemplos serão todos focados em um provedor de nuvem: Amazon Web Services (AWS). Esta é apenas uma nuvem com a qual tenho mais experiência, mas todas as informações devem se aplicar a outras nuvens também.
Termino com algumas notas que gostaria de ter conhecido quando comecei: algumas pegadinhas de sintaxe, peculiaridades e casos em que o Terraform não seria minha ferramenta de escolha.
Não vou me concentrar em detalhes minuciosos da sintaxe. Você pode se atualizar rapidamente lendo a documentação e os guias fantásticos que a HashiCorp fornece para o Terraform. Gostaria de focar em coisas que podem não ser óbvias desde o início e que gostaria de ter conhecido antes de começar a trabalhar com o Terraform. Esperamos que isso o aponte na direção certa e permita que você considere o Terraform como uma ferramenta a ser usada em projetos futuros.
O gerenciamento de infraestrutura é difícil
Há várias etapas envolvidas na configuração de um ambiente para um aplicativo na nuvem. A menos que você escreva todas elas como listas de verificação detalhadas e as siga de perto, o tempo todo, você cometerá erros; somos humanos, afinal. Esses passos são difíceis de compartilhar. Você precisa documentar muitos procedimentos manuais e os documentos podem ficar desatualizados rapidamente. Agora multiplique tudo isso pelo número total de ambientes para um único aplicativo: dev , test/qa , stage e prod . Você também precisa pensar na segurança de cada um deles.
Toda ferramenta não tem uma interface de usuário que eu possa usar e esquecer a complexidade?
Eles têm interfaces de usuário — o AWS Management Console é um excelente exemplo. Mas essas ferramentas fazem muito sob o capô. Um clique na interface do usuário pode realmente invocar uma cascata de mudanças difíceis de entender. Geralmente, não há como desfazer o que você fez na interface do usuário (os prompts usuais “Tem certeza?” geralmente não são suficientes). Além disso, é sempre uma boa ideia ter um segundo par de olhos para verificar as alterações, mas quando usamos UIs, precisaríamos sentar com essa pessoa juntos ou verificar nossas alterações depois de feitas, o que é mais uma auditoria do que uma Reveja. Cada provedor de nuvem tem sua própria interface do usuário que você precisa dominar. As interfaces de usuário são projetadas para serem fáceis de usar (não necessariamente simples!) e, como tal, são propensas a abordagens ilusórias, como “este é apenas um pequeno ajuste” ou um hotfix de produção rápido que você esquecerá em 48 horas. Esse clique manual também é muito difícil de automatizar.
E as Ferramentas CLI?
Eles seriam melhores do que ferramentas de interface do usuário para nossos casos de uso. No entanto, você ainda está propenso a fazer alterações manualmente ou escrever scripts bash que podem facilmente sair do controle. Além disso, cada provedor tem suas próprias ferramentas CLI. Com essas ferramentas, você não pode ver suas alterações antes de se comprometer com elas. Felizmente este não é um problema novo e existem ferramentas que ajudam com isso.
Orquestração versus gerenciamento de configuração
Vou definir algumas categorias de ferramentas e práticas que são usadas para gerenciar a infraestrutura. Eles são orquestração e gerenciamento de configuração. Você pode pensar neles aproximadamente como modelos de programação declarativos e imperativos (pense em Prolog vs. Python). Cada um tem seus prós e contras, mas é melhor conhecer todos eles e aplicar a melhor ferramenta para um determinado trabalho. Além disso, a orquestração geralmente envolve um nível de abstração mais alto do que o gerenciamento de configuração.
Orquestração
A orquestração se assemelha mais a um paradigma de programação declarativa. Eu gosto de pensar nisso como sendo o maestro de uma orquestra. O trabalho de um maestro é bem resumido pela Wikipedia (link) como “a arte de dirigir a performance simultânea de vários músicos ou cantores pelo uso de gestos”. Os maestros comunicam a batida e o tempo, a dinâmica e a sugestão da música, mas o trabalho real é realizado por músicos individuais que são especialistas em tocar seus instrumentos musicais. Sem essa coordenação, eles não seriam capazes de executar uma peça perfeita.
É aqui que o Terraform se encaixa. Você o usa para conduzir uma parte da infraestrutura de TI: você informa o que implantar e o Terraform vincula tudo isso e executa todas as chamadas de API necessárias. Existem ferramentas semelhantes neste espaço; um dos mais conhecidos é o AWS Cloud Formation. Ele tem melhor suporte para recuperação e rollbacks em caso de erros do que o Terraform, mas também, na minha opinião, uma curva de aprendizado mais acentuada. Também não é independente da nuvem: funciona apenas com a AWS.
Gerenciamento de configurações
O lado complementar dessas práticas é a abordagem de gerenciamento de configuração. Nesse paradigma, você especifica as etapas exatas que uma ferramenta deve fazer para chegar a uma determinada configuração desejada. As etapas em si podem ser pequenas e oferecer suporte a vários sistemas operacionais, mas você precisa pensar ativamente na ordem de execução. Eles não têm consciência do estado atual e do ambiente (a menos que você os programe com isso) e, como tal, executarão cegamente quaisquer etapas que você der a eles. Isso pode levar a um problema conhecido como desvio de configuração , em que seus recursos serão dessincronizados lentamente com o que pretendiam representar inicialmente, especialmente se você fez algumas alterações manuais neles. Eles são ótimos para gerenciar e provisionar serviços em instâncias individuais. Exemplos de ferramentas que se destacam nesse fluxo de trabalho são Chef, Puppet, Ansible e Salt.
A orquestração impõe uma abordagem à sua infraestrutura em que você trata seus recursos como gado, não como animais de estimação. Em vez de “nutrir” manualmente cada VPS, você pode substituí-los por uma cópia exata quando algo der errado. Não quero dizer que você simplesmente não se importe e reinicie a coisa esperando o melhor.
Em vez disso, você deve investigar e corrigir o problema no código e, em seguida, implantá-lo.
O Ansible (e outras ferramentas de CM) pode ser usado para gerenciar a infraestrutura da AWS, mas isso envolveria muito trabalho e é mais propenso a erros, especialmente quando a infraestrutura muda com frequência e cresce em complexidade.
Uma coisa importante a ser lembrada é que as abordagens de orquestração e gerenciamento de configuração não entram em conflito entre si. Eles são compatíveis. Não há problema em ter um grupo de instâncias do EC2 (VPS) em um grupo de AutoScaling gerenciado pelo Terraform, mas executando uma AWS Application Image (AMI), que é um snapshot do disco, que foi preparado com etapas imperativas com, por exemplo, Ansible . O Terraform ainda tem um conceito de “provedores” que permitem que você execute ferramentas externas de provisionamento assim que uma máquina for inicializada.
A documentação do Terraform faz um ótimo trabalho explicando isso e ajudando você a colocar o Terraform em todo o ecossistema.
O que é Terraform?
É uma ferramenta de código aberto, criada pela HashiCorp, que permite codificar sua infraestrutura como arquivos de configuração declarativos que são versionados e compartilhados e podem ser revisados.
O nome HashiCorp deve soar um sino - eles também fazem Nomad, Vault, Packer, Vagrant e Consul. Se você já usou alguma dessas ferramentas, já conhece a qualidade da documentação, a comunidade vibrante e a utilidade que pode esperar de suas soluções.
Infraestrutura como código
O Terraform é independente de plataforma; você pode usá-lo para gerenciar servidores bare metal ou servidores em nuvem como AWS, Google Cloud Platform, OpenStack e Azure. No jargão do Terraform, eles são chamados de provedores . Você pode ter uma noção da escala lendo uma lista completa de provedores suportados. Vários provedores podem ser usados ao mesmo tempo, por exemplo, quando o provedor A configura VMs para você, mas o provedor B configura e delega registros DNS.
Isso significa que se pode trocar de provedor de nuvem com uma alteração em um arquivo de configuração? Não, eu nem acho que você iria querer isso, pelo menos não de forma automatizada. O problema é que diferentes provedores podem ter diferentes capacidades, diferentes ofertas, fluxos, ideias, etc. Isso significa que você terá que usar recursos diferentes para um provedor diferente expressar o mesmo conceito. No entanto, tudo isso ainda pode ser feito em uma sintaxe de configuração única e familiar e fazer parte de um fluxo de trabalho coeso.
Partes essenciais de uma configuração do Terraform
- O próprio binário do Terraform, que você precisa instalar
- Os arquivos de código fonte, ou seja, sua configuração
- O estado (local ou remoto) que representa os recursos que o Terraform gerencia (mais sobre isso posteriormente)
Escrevendo a configuração do Terraform
Você escreve o código de configuração do Terraform em arquivos *.tf
usando a linguagem HCL. Existe uma opção para usar o formato JSON ( *.tf.json
), mas é direcionado a máquinas e geração automática em vez de humanos. Eu recomendo que você fique com HCL. Não vou me aprofundar na sintaxe da linguagem HCL; os documentos oficiais fazem um trabalho fantástico descrevendo como escrever HCL e como usar variáveis e interpolações. Mencionarei apenas o mínimo necessário para entender os exemplos.
Dentro dos arquivos do Terraform, você lida principalmente com recursos e fontes de dados . Os recursos representam componentes de sua infraestrutura, por exemplo, uma instância AWS EC2, uma instância RDS, um registro DNS Route53 ou uma regra em um grupo de segurança. Eles permitem provisioná-los e alterá-los dentro da arquitetura de nuvem.
Supondo que você tenha configurado o Terraform, se você emitir um terraform apply
, o código abaixo criaria uma instância do EC2 totalmente funcional (somente algumas propriedades são mostradas):
resource "aws_instance" "bastion" { ami = "ami-db1688a2" # Amazon Linux 2 LTS Candidate AMI 2017.12.0 (HVM), SSD Volume Type - ami-db1688a2 instance_type = "t2.nano" key_name = "${var.key_name}" subnet_ vpc_security_group_ids = ["${aws_security_group.bastion.id}"] monitoring = "false" associate_public_ip_address = "true" disable_api_termination = "true" tags = { Name = "${var.project_tag}-bastion-${var.env}" Env = "${var.env}" Application ApplicationRole = "Bastion Host" Project = "${var.project_tag}" } }
Por outro lado, existem fontes de dados que permitem ler dados sobre determinados componentes sem alterá-los. Você deseja obter o AWS ID (ARN) de um certificado emitido pelo ACM? Você usa uma fonte de dados. A diferença é que as fontes de dados são prefixadas com data_
ao fazer referência a elas nos arquivos de configuração.
data "aws_acm_certificate" "ssl_cert" { domain = "*.example.com" statuses = ["ISSUED"] }
O acima faz referência a um certificado SSL ACM emitido que pode ser usado junto com AWS ALBs. Antes de fazer tudo isso, porém, você precisa configurar seu ambiente.
Estrutura de pastas
Os ambientes Terraform (e seus estados) são separados por diretórios. O Terraform carrega todos os arquivos *.tf
de um diretório em um namespace, portanto, a ordem não importa. Eu recomendo a seguinte estrutura de diretórios:
/terraform/ |---> default_variables.tf (1) /stage/ (2) |---> terraform.tfvars (3) |---> default_variables.tf (4) |---> terraform.tf (5) |---> env_variables.tf (6) /prod/ /<env_name>/
-
default_variables.tf
– define todas as variáveis de nível superior e, opcionalmente, seus valores padrão. Eles podem ser reutilizados em cada ambiente (diretório aninhado) com links simbólicos. -
/stage/
– um diretório que contém a configuração de um ambiente totalmente separado (aqui chamado destage
, mas pode ser qualquer coisa). Quaisquer alterações feitas dentro desta pasta são totalmente independentes de outros ambientes (env—comoprod
) que é algo que você deseja para evitar bagunçar o env de produção com alterações feitas nostage
! -
terraform.tfvars
– define valores de variáveis. Os arquivos.tfvars
são semelhantes aos arquivos.env
, pois contêm os pareskey=val
para variáveis definidas. Por exemplo, isso especifica oprofile
da AWS , AWSkey_name
e AWSkey_path
que eu uso. Ele pode ser ignorado no Git. -
default_variables.tf
– este é um link simbólico para o arquivo (2), que nos permite compartilhar variáveis independentes de ambiente sem nos repetirmos. -
terraform.tf
– esta é a configuração principal de cada env; ele contém o blocoterraform {}
que configura o back-end. Eu também configuro provedores aqui. -
env_variables.tf
– este arquivo contém variáveis específicas de env. Eu marco todos os recursos comEnv=<env_name>
na AWS, então esse arquivo geralmente define apenas uma variável:env
.
Claro, essa não é a única maneira de estruturar seu ambiente. Isso é apenas algo que funcionou bem para mim, permitindo uma separação clara de preocupações.
Configuração de back-end
Já mencionei o estado do Terraform. Essa é uma parte essencial do fluxo de trabalho do Terraform. Você pode se perguntar se o estado é realmente necessário. O Terraform não poderia simplesmente consultar a API da AWS o tempo todo para obter o estado real da infraestrutura? Bem, se você pensar bem, o Terraform precisa manter um mapeamento entre o que ele gerencia nos arquivos de configuração declarativa e o que esses arquivos realmente correspondem (no ambiente do provedor de nuvem). Observe que, ao gravar arquivos de configuração do Terraform, você não se importa com os IDs de, por exemplo, instâncias individuais do EC2 ou os ARNs que serão criados para grupos de segurança que você publicar. Internamente, no entanto, o Terraform precisa saber que um determinado bloco de recursos representa um recurso concreto com um ID/ARN. Isso é necessário para detectar alterações. Além disso, o estado é usado para rastrear dependências entre recursos (também algo que você não precisa pensar normalmente!). Eles são usados para construir um grafo que pode ser (geralmente) paralelizado e executado. Como sempre, recomendo que você leia a excelente documentação sobre o estado do Terraform e sua finalidade.
Como o estado é a única fonte de verdade para sua arquitetura, você precisa garantir que você e sua equipe estejam sempre trabalhando em sua versão mais atualizada e que você não crie conflitos por acesso não sincronizado ao estado. Você não quer resolver conflitos de mesclagem no arquivo de estado, acredite.
Por padrão, o Terraform armazena o estado em um arquivo em disco, localizado no diretório de trabalho atual (de cada env) como um arquivo terraform.tfstate
. Tudo bem se você sabe que será o único desenvolvedor no trabalho ou está apenas aprendendo e experimentando o Terraform. Tecnicamente, você pode fazê-lo funcionar em equipe porque pode confirmar o estado em um repositório VCS. Mas então, você precisaria garantir que todos estejam sempre trabalhando na versão mais recente do estado e que ninguém faça alterações ao mesmo tempo! Esta é geralmente uma grande dor de cabeça e eu aconselho fortemente contra isso. Além disso, se alguém se juntar à sua operação single-dev, você ainda terá que configurar um local alternativo para o estado.
Felizmente, esse é um problema com boas soluções incorporadas ao Terraform: o chamado Remote State . Para que o estado remoto funcione, você precisa configurar o back-end usando um dos provedores de back-end disponíveis. O exemplo de back-end a seguir será baseado em AWS S3 e AWS DynamoDB (banco de dados AWS NoSQL). Você poderia usar apenas o S3, mas perderia o mecanismo de bloqueio de estado e verificação de consistência (não recomendado). Se você usou anteriormente apenas o estado local, configurar um back-end remoto oferecerá a opção de migrar seu estado na primeira vez, para que você não perca nada. Você pode ler mais sobre a configuração de back-end aqui.
Infelizmente, há um problema de galinha e ovo: o bucket do S3 e a tabela do DynamoDB precisam ser criados manualmente. O Terraform não pode criá-los automaticamente, pois ainda não há estado! Bem, existem algumas soluções como https://github.com/gruntwork-io/terragrunt que automatizam isso usando a AWS CLI, mas não quero me desviar do tópico principal desta postagem do blog.
As coisas importantes a saber sobre a configuração de back-end do S3 e do DynamoDB são:
- Habilite o versionamento no bucket do S3 para evitar erros humanos e a Lei de Murphy.
- A tabela do DynamoDB tem um limite de taxa de leituras e gravações (chamado de capacidade). Se você fizer muitas alterações no estado remoto, certifique-se de habilitar o DynamoDB AutoScaling para essa tabela ou configurar limites de R/W suficientemente altos. Caso contrário, o Terraform receberá erros HTTP 400 da API da AWS ao executar muitas chamadas.
Para resumir tudo, a seguinte configuração de back-end pode ser colocada em terraform.tf
para configurar o estado remoto no S3 e no DynamoDB.
terraform { # Sometimes you may want to require a certain version of Terraform required_version = ">= 0.11.7" # Stores remote state, required for distributed teams # Bucket & dynamoDB table have to be created manually if they do not exist # See: https://github.com/hashicorp/terraform/issues/12780 backend "s3" { bucket = "my-company-terraform-state" key = "app-name/stage" region = "eu-west-1" # 5/5 R/W Capacity might not be enough for heavy, burst work (resulting in 400s). Consider enabling Auto Scaling on the table. # See: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ProvisionedThroughput.html dynamodb_table = "terraform-state-lock-table" } }
Isso é muito para absorver de uma só vez, mas lembre-se, você faz isso uma vez para cada env e depois pode esquecer. Se você precisar de ainda mais controle sobre o bloqueio de estado, há o HashiCorp Terraform Enterprise, mas não o abordarei aqui.
Provedores
Para que esse back-end seja acessível e possa se comunicar com nosso provedor de nuvem, precisamos configurar o chamado provedor . O seguinte bloco pode ser colocado no arquivo terraform.tf
(para cada env):
provider "aws" { profile = "${var.profile}" region = "${var.region}" version = "~> 1.23.0" }
As variáveis de profile
e region
são armazenadas no arquivo terraform.tfvars
, que pode ser ignorado. A variável de profile
refere-se a um perfil nomeado que contém credenciais de segurança para a nuvem AWS usando o arquivo de credenciais padrão. Observe que também estou definindo algumas restrições de versão. Você não quer que o Terraform atualize os plugins do seu provedor sem o seu conhecimento em cada inicialização de back-end. Especialmente porque há uma versão 2.x do provedor da AWS que requer uma atualização cuidadosa.

Inicialização de back-end
Cada configuração de back-end requer uma etapa de inicialização no início e toda vez que houver uma alteração nela. A inicialização também configura os módulos do Terraform (mais sobre eles posteriormente), portanto, ao adicioná-los, você também precisa executar novamente a etapa de inicialização. Esta operação é segura para ser executada várias vezes. Observe que, durante a inicialização do back-end, nem todas as variáveis podem ser lidas pelo Terraform para configurar o estado, nem devem ser por questões de segurança (por exemplo, chaves secretas). Para superar isso e, em nosso caso, usar um perfil AWS diferente do padrão, você pode usar a opção -backend-config
com aceita k=v
pares de variáveis. Isso é chamado de configuração parcial.
terraform init -backend-config=profile=<aws_profile_name>
Os arquivos do Terraform compartilham um escopo restrito a um determinado diretório. Isso significa que uma subpasta não está diretamente conectada ao código do diretório pai. No entanto, está definindo um módulo que permite a reutilização de código, gerenciamento de complexidade e compartilhamento.
O fluxo de trabalho
O fluxo de trabalho geral ao trabalhar com o código do Terraform é o seguinte:
- Gravar configuração para sua infraestrutura.
- Veja quais mudanças reais ele fará (
terraform plan
). - Opcionalmente, execute as alterações exatas que você viu na etapa 2 (
terraform apply
). - IR PARA 1
Plano Terraform
O comando do plan
do Terraform apresentará uma lista de alterações que serão feitas em sua infraestrutura ao emitir o comando apply
. É seguro emitir o plan
várias vezes, pois por si só não muda nada.
Como ler um plano
Os objetos no Terraform (recursos e fontes de dados) são facilmente identificáveis por seus nomes totalmente qualificados.
- No caso de recursos, o ID pode ser semelhante a:
<resource_type>.<resource_name>
por exemplo,aws_ecs_service.this
. - No caso de recursos dentro de módulos, temos um nome de módulo adicional:
module.<module_name>.<resource_type>.<resource_name>
—por exemplo,module.my_service_ecs_service_task.aws_ecs_service.this
. - Fontes de dados (dentro e fora de um módulo):
(module.<module_name>).data.<resource_type>.<resource_name>
por exemplo,module.my_service_ecs_service_task.data.aws_ecs_task_definition.this
.
O tipo de recurso é específico para um determinado provedor e geralmente inclui seu nome ( aws_…
). A lista completa de recursos disponíveis para AWS pode ser encontrada na documentação.
Há cinco ações que um plano mostrará para determinados recursos:
-
[+]
Adicionar – Um novo recurso será criado. -
[-]
Destruir – Um recurso será completamente destruído. -
[~]
Modificar no local – Um recurso será modificado e um ou mais parâmetros serão alterados. Isso geralmente é seguro. O Terraform mostrará quais parâmetros serão modificados e como (se possível). -
[- / +]
– O recurso será removido e depois recriado com novos parâmetros. Isso acontece se uma alteração foi feita em um parâmetro que não pode ser alterado no local. O Terraform mostrará quais alterações forçam a recriação de um recurso com o seguinte comentário em vermelho:(forces new resource)
. Isso é potencialmente perigoso, pois há um período em que o recurso não existirá. Ele também pode quebrar outras dependências conectadas. Eu recomendaria contornar essas mudanças, a menos que você saiba quais serão as consequências ou não se importe com um tempo de inatividade. -
[<=]
– Uma fonte de dados será lida. Esta é uma operação somente leitura.
Acima está um exemplo de plano. As alterações que fiz foram:
- Alterado o
instance_type
da primeira instância do bastião - Adicionada uma segunda instância de bastião
- Alterado o nome de um grupo de segurança
Observe que a última alteração é uma regra de grupo de segurança que detectou automaticamente uma alteração no nome do grupo pai. Na minha opinião, todo o plano é muito legível. Alguns valores de parâmetros aparecem como <computed>
— isso não significa que eles serão alterados, mas sim que eles não podem ser recuperados e apresentados neste estágio (como o nome do SG que ainda não foi criado). Lembre-se de que apenas os recursos alterados serão mostrados durante o comando plan
. Qualquer coisa omitida não será tocada.
Normalmente, os comandos do Terraform aceitam parâmetros adicionais. O parâmetro mais importante para o comando plan é a opção -out
, que salvará seu plano em disco. Este plano salvo pode ser executado (exatamente como salvo) pelo comando apply. Isso é muito importante, pois caso contrário pode haver uma mudança, feita por, por exemplo, colegas entre a emissão de um plan
e a emissão do apply
. Exemplo:
- Emita o
plan
e verifique se está bom. - Alguém muda o estado, sem o seu conhecimento.
- Problema
apply
e - oops, ele fez algo diferente do que foi planejado!
Felizmente, esse fluxo de trabalho foi aprimorado no Terraform v0.11.0. Desde esta versão, o comando apply
apresenta automaticamente um plano que você deve aprovar (digitando explicitamente yes
). A vantagem é que, se você aplicar este plano, ele será executado exatamente como apresentado.
Outra opção útil é -destroy
, que mostrará o plano que resultará na destruição de todos os recursos que o Terraform gerencia. Você também pode direcionar recursos específicos para destruição com a opção -target
.
Aplicação Terraform
Quando aplicamos um determinado plano, o Terraform sai e coloca um bloqueio no nosso estado para garantir a exclusividade. Em seguida, ele altera os recursos e, no final, envia um estado atualizado. Uma coisa a notar é que alguns recursos demoram mais para serem concluídos do que outros. Por exemplo, a criação de uma instância do AWS RDS pode levar mais de 12 minutos, e o Terraform aguardará a conclusão. Obviamente, o Terraform é inteligente o suficiente para não bloquear todas as outras operações com isso. ele cria um gráfico direcionado das alterações solicitadas e, se não houver interdependências, utiliza o paralelismo para acelerar a execução.
Importando recursos
Muitas vezes, o Terraform e outras soluções de “configuração como código” são introduzidas gradualmente em um ambiente já existente. A transição é muito fácil. Basicamente, qualquer coisa não definida dentro do Terraform é deixada sem gerenciamento e inalterada. A Terraform se preocupa apenas com o que gerencia. Claro, é possível que isso apresente problemas — por exemplo, se o Terraform depender de algum endpoint que existe fora de sua configuração e ele for destruído manualmente. O Terraform não sabe disso e, portanto, não pode recriar esse recurso durante as alterações de estado, o que resultará em erros da API durante a execução do plano. Isso não é algo com que eu me preocuparia; os benefícios da introdução do Terraform superam em muito os contras.
Para facilitar um pouco o processo de introdução, o Terraform inclui o comando import
. Ele é usado para introduzir recursos externos já existentes no estado do Terraform e permitir que ele gerencie esses recursos. Infelizmente, o Terraform não é capaz de gerar configurações automaticamente para esses módulos importados, pelo menos no momento da escrita. Existem planos para essa funcionalidade, mas, por enquanto, você precisa primeiro escrever a definição do recurso no Terraform e, em seguida, importar esse recurso para informar ao Terraform para começar a gerenciá-lo. Uma vez importado para o estado (e durante o planejamento da execução), você verá todas as diferenças entre o que escreveu nos arquivos .tf
e o que realmente existe na nuvem. Dessa forma, você pode ajustar ainda mais a configuração. Idealmente, nenhuma alteração deve aparecer, o que significaria que a configuração do Terraform reflete o que já está na nuvem 1:1.
Módulos
Os módulos são uma parte essencial da configuração do Terraform e recomendo que você os adote e os use com frequência. Eles fornecem uma maneira de reutilizar determinados componentes. Um exemplo seria o cluster do AWS ECS, que é usado para executar contêineres do Docker. Para que esse cluster funcione, você precisa que muitos recursos separados sejam configurados: o próprio cluster, a configuração de execução que gerenciaria instâncias EC2 separadas, repositórios de contêiner para imagens, o grupo e a política de escalonamento automático e assim por diante. Geralmente, você precisa ter clusters separados para ambientes e/ou aplicativos separados.
Uma maneira de superar isso seria copiar e colar a configuração, mas esta é obviamente uma solução míope. Você cometeria erros ao fazer as atualizações mais simples.
Os módulos permitem encapsular todos esses recursos separados em um bloco de configuração (chamado de módulo). Os módulos definem entradas e saídas que são as interfaces pelas quais se comunica com “o mundo exterior” (ou o código de chamada). Além disso, os módulos podem ser aninhados dentro de outros módulos, permitindo que você gire rapidamente ambientes separados inteiros.
Módulos locais e remotos
Da mesma forma que o estado, você pode ter módulos locais ou módulos remotos. Os módulos locais são armazenados junto com a configuração do Terraform (em um diretório separado, fora de cada ambiente, mas no mesmo repositório). Tudo bem se você tiver uma arquitetura simples e não estiver compartilhando esses módulos.
Isso, no entanto, tem limitações. É difícil versionar esses módulos e compartilhá-los. O controle de versão é importante porque você pode querer usar a v1.0.0 do módulo ECS na produção, mas gostaria de experimentar a v1.1.0 em um ambiente de teste. Se o módulo foi armazenado junto com seu código, todas as alterações no código do módulo seriam refletidas em cada env (uma vez que o apply
é executado), o que geralmente é indesejável.
Uma abordagem útil para módulos de versão é colocá-los todos em um repositório separado, por exemplo, your-company/terraform-modules. Então, ao referenciar esses módulos dentro de sua configuração do Terraform, você pode usar um link VCS como fonte:
module "my-module" { source = "[email protected]:your-company/terraform-modules.git//modules/my-module?ref=v1.1.0" ... }
Aqui estou referenciando uma v1.1.0 do my-module (caminho específico) que posso testar independentemente de outras versões do mesmo módulo em ambientes diferentes.
Fora isso, há a questão da capacidade de descoberta e compartilhamento dos módulos. Você deve se esforçar para escrever módulos bem documentados e reutilizáveis. Normalmente, você terá diferentes configurações do Terraform para diferentes aplicativos e talvez queira compartilhar o mesmo módulo entre eles. Sem extraí-los para um repositório separado, isso seria muito difícil.
Usando módulos
Os módulos podem ser facilmente referenciados em ambientes Terraform definindo um bloco de módulo especial. Aqui está um exemplo de tal bloco para um módulo ECS hipotético:
module "my_service_ecs_cluster" { source = "../modules/ecs_cluster" cluster_name = "my-ecs-service-${var.env}" repository_names = [ "my-ecs-service-${var.env}/api", "my-ecs-service-${var.env}/nginx", "my-ecs-service-${var.env}/docs", ] service_name = "my-ecs-service-${var.env}" ecs_instance_type = "t2.nano" min_size = "1" max_size = "1" use_autoscaling = false alb_target_group_arn = "${module.my_alb.target_group_arn}" subnets = "${local.my_private_subnets}" security_groups = "${aws_security_group.my_ecs.id}" key_name = "${var.key_name}" env_tag = "${var.env}" project_tag = "${var.project_tag}" application_tag = "${var.api_app_tag}" asg_tag = "${var.api_app_tag}-asg" }
Todas as opções que são passadas (exceto algumas globais como source
) foram definidas dentro da configuração deste módulo como entradas (variáveis).
Registro de Módulos
Recentemente a HashiCorp lançou um registro oficial do módulo Terraform. Esta é uma ótima notícia, pois agora você pode aproveitar o conhecimento da comunidade que já desenvolveu módulos testados em batalha. Além disso, alguns deles têm o selo “Módulo Verificado HashiCorp”, o que significa que eles são verificados e mantidos ativamente e oferecem confiança extra.
Anteriormente, você tinha que escrever seus próprios módulos do zero (e aprender com seus erros) ou usar módulos publicados no GitHub e outros lugares, sem nenhuma garantia quanto ao seu comportamento (além de ler o código!)
Compartilhamento de dados entre ambientes
Idealmente, os ambientes devem ser totalmente separados, mesmo usando contas diferentes da AWS. Na realidade, há casos em que um ambiente Terraform pode usar algumas informações em outro ambiente. This is especially true if you are gradually converting your architecture to use Terraform. One example might be that you have a global
env that provides certain resources to other envs.
Let's say env global
shares data with stage
. For this to work, you can define outputs at the main level of the environment like so:
output "vpc_id" { value = "${module.network.vpc_id}" }
Then, in the stage
environment, you define a datasource that points to the remote state of global
:
data "terraform_remote_state" "global" { backend = "s3" config { bucket = "my-app-terraform-state" key = "terraform/global" region = "${var.region}" dynamodb_table = "terraform-state-lock-table" profile = "${var.profile}" } }
Now, you can use this datasource as any other and access all the values that were defined in global
's outputs:
vpc_
Words of Caution
Terraform has a lot of pros. I use it daily in production environments and consider it stable enough for such work. Having said that, Terraform is still under active development. Thus, you will stumble on bugs and quirks.
Where to Report Issues and Monitor Changes
First of all, remember: Terraform has a separate core repo and repositories for each provider (eg, AWS). If you encounter issues, make sure to check both the core repo and the separate provider repositories for issues and/or opened pull requests with fixes. GitHub is really the best place to search for bugs and fixes as it is very active and welcoming.
This also means that provider plugins are versioned separately, so make sure you follow their changelogs as well as the core one. Most of the bugs I have encountered were resolved by upgrading the AWS provider which already had a fix.
Can't Cheat Your Way out of Cloud Knowledge
You cannot use Terraform to configure and manage infrastructure if you have no knowledge of how a given provider works. I would say this is a misconception and not a downside, since Terraform has been designed to augment and improve the workflow of configuration management and not to be some magic dust that you randomly sprinkle around and—poof! Environments grow! You still need a solid knowledge of a security model of each cloud, how to write, eg, AWS policies, what resources are available, and how they interact.
Prefer Separate Resources That Are Explicitly linked
There are certain resources—for example, the AWS security group or AWS route table—that allow you to configure the security rules and routes respectively, directly inside their own block. This is tempting, as it looks like less work but in fact will cause you trouble. The problems start when you are changing those rules on subsequent passes. The whole resource will be marked as being changed even if only one route/security rule is being introduced. It also gives implicit ordering to those rules and makes it harder to follow the changes. Thankfully, mixing those both approaches is not allowed now (see the note).
Best-practice example, with explicitly linked resources:
resource "aws_security_group" "my_sg" { name = "${var.app_tag}-my-sg" ... } resource "aws_security_group_rule" "rule_one" { security_group_ ... } resource "aws_security_group_rule" "rule_two" { security_group_ ... }
Terraform plan
Doesn't Always Detect Issues and Conflicts
I already mentioned this in the case where you were managing resources with Terraform that were relying on other, unmanaged infrastructure. But there are more trivial examples—for example, you will get an error if your EC2 instance has Termination Protection enabled, even though plan
would show you it's OK to destroy it. You can argue that this is what Termination Protection has been designed for, and I agree, but there are more examples of things you can do in theory/on plan but when executed will deadlock or error out. For example, you cannot remove a network interface if something is using it—you get a deadlock without an option to gracefully recover.
Syntax Quirks
There are also quirks related to how HCLv1 (the syntax language Terraform uses) has been designed. It has a couple of frustrating quirks. There is work underway to provide an improved version of the parser for HCLv2. The best way to read on the current limitations and the plan to overcome them is this fantastic blog series. In the meantime, there are workarounds for most of those issues. They are not pretty and they will fail once v0.12 comes out, but hey, it is what it is.
When State Update Fails
It sometimes happens that Terraform is not able to correctly push an updated state. This is usually due to underlying network issues. The solution is to retry the state update instead of running apply again, which will fork the state .
Another issue might happen when state lock (the synchronization primitive that prevents multiple users to update the same state) fails to be taken down by Terraform. This involves running terraform force-unlock
with the lock ID to take it down manually.
Thankfully, in case of such problems, Terraform provides you with a good description and steps you need to make to fix it.
Not Everything Is Fun to Manage Through Terraform
There are certain cases where Terraform is not my tool of choice. For example, configuring AWS CodePipeline and CodeBuild projects (AWS equivalent of CI/CD pipeline) is cumbersome when done through Terraform. You need to define each step through very verbose configuration blocks and things like “Login via GitHub” are a lot more complicated than using the UI. Of course, it's still possible if you prefer to have it codified. Well, I guess it's a good candidate for a well-written module!
Same thing goes for managing AWS API Gateway endpoints. In this case, using a dedicated serverless framework would be a better option.
When configuring AWS resources with Terraform, you will find yourself writing a lot of policies. Policies that would otherwise often be auto-generated for you (when using the UI). For those, I'd recommend the AWS Visual Editor and then copying the resulting policy JSON into Terraform.
Conclusão
Using Terraform has been fun and I'll continue doing so. Initial steps can be a bumpy ride, but there are more and more resources that help to ease you in.
I'd definitely recommend taking Terraform for a spin and simply playing with it. Remember, though—be safe and test it out on a non-essential account. If you are eligible for AWS Free Tier, use it as a 12-month free trial. Just be aware it has limitations as to what you can provision. Otherwise, just make sure you spin the cheapest resources, like t3.nano instances.
I highly recommend extensions for Terraform support in various code editors. For Visual Studio Code, there is one with syntax highlighting, formatting, validation and linting support.
It's always valuable to learn new things and evaluate new tools. I found that Terraform helped me immensely in managing my infrastructure. I think working with Terraform will only get easier and more fun, especially once v0.12.0 ships with a major upgrade to the HCL syntax and solve most of the quirks. The traction and community around Terraform are active and vibrant. You can find a lot of great resources on things I didn't manage to cover in a single blogs post, eg, a detailed guide on how to write modules.