Skip to main content

Terraform

Infraestrutura como Código

Infraestrutura como código (IaC) é o nome dado a prática de utilizar arquivos de configuração em repositórios de código para descrever a infraestrutura de recursos computacionais (servidores, redes, armazenamento, bancos de dados, acessos, DNS, etc).

É comum em empresas que consomem serviços de nuvem o uso da ferramenta Terraform para organizar configurações de infraestrutura e para automatizar os provisionamentos dela.

OpenTofu + S3 + Magalu Cloud

Abaixo segue um guia de como utilizar o OpenTofu, uma alternativa livre ao Terraform, para a criação e gerenciamento de recursos de Object Storage.

Pér-requisitos

  • um sistema Linux (exemplo: Ubuntu)
  • a ferramenta OpenTofu numa versão igual ou superior a v1.7.1

Projeto e Módulo Raíz

Vamos começar criando uma pasta para nosso projeto e um módulo de configuração raíz:

mkdir tutorial-tofu
cd tutorial-tofu
echo "terraform {}" > main.tf
tofu validate

Apesar de válida, esta configuração raíz está vazia. Módulos de Terraform utilizam plugins chamados de Providers que são extensões instaláveis que permitem o uso de diferentes produtos de diferentes nuvens.

Para este tutorial, vamos utilizar o provider hashicorp/aws (licença MPL v2.0), que habilita o uso de fornecedores de Object Storage compatíveis com o padrão S3. Para isto vamos aumentar nosso main.tf para ficar assim:

main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.50.0"
}
}
}

provider "aws" {
# Configuration options
}

E utilizar o comando abaixo para instalar este plugin:

tofu init

Voltaremos na linha comentada acima após criarmos um "profile aws" com credenciais de acesso no passo a seguir.

Nota

Existe um provider de Terraform oficial da Magalu Cloud, não o utilizei aqui por dois motivos:

  1. para demonstrar que a API é compatível com o padrão S3
  2. porque na data de publicação, o provider da Magalu estava ainda na versão 0.18.10, que suportava apenas o recurso buckets e os exemplos abaixo usam tanto buckets quanto objects.

Configuração das credenciais

Por ser compatível com S3, o Object Storage da nuvem da Magalu funciona também com autenticação por API Key, no console você pode criar uma chave destas indo em Início > Object Storage > API Key, botão "Criar API Key"

Esta chave possui duas informações importantes para serem configuradas na sua máquina: ID e Secret. Para este tutorial, vamos declarar estas variáveis num arquivo de configuração de perfis da aws, que deve estar localizado no caminho ~/.aws/credentials, vamos dar o nome deste perfil de tutorial-tofu, substitua os textos em caixa alta COLE_AQUI_O... no exemplo abaixo pelo conteúdo do ID e do SECRET da chave recém criada:

mkdir -p ~/.aws
cat << EOF >> ~/.aws/credentials
[tutorial-tofu]
aws_access_key_id = COLE_AQUI_O_ID
aws_secret_access_key = COLE_AQUI_O_SECRET

EOF

O padrão destes perfis de aws é utilizar a nuvem da Amazon. Para funcionar com a nuvem da Magalu, é preciso mudar os campos endpoint_url e region, neste exemplo vou utilizar a região "Brasil - Nordeste 1", a br-ne1:

cat << EOF >> ~/.aws/config
[profile tutorial-tofu]
endpoint_url = https://br-ne1.magaluobjects.com/
region = br-ne1

EOF

Se tudo deu certo, podemos voltar ao módulo raíz do nosso projeto e incluir este nome de perfil como o campo profile do bloco que configura o provider aws:

main.tf
provider "aws" {
profile = "tutorial-tofu"
skip_region_validation = true
skip_requesting_account_id = true
skip_credentials_validation = true
}

Estes outros três atributos (skip isto, skip aquilo, skip aquilo outro) serão necessários também para o nosso caso, detalhes sobre cada um deles podem ser consultados na documentação do plugin.

Primeiro Bucket

Finalmente, podemos criar o primeiro recurso de nossa infraestrutura: um bucket para armazenarmos objetos dentro, vamos criar um novo módulo de configuração, um arquivo: resources.tf

resources.tf
resource "aws_s3_bucket" "first_bucket" {
bucket_prefix = "tutorial-bucket"
}
Nota

Utilizei o campo bucket_prefix ao invés do campo bucket para nomear nosso primeiro contêiner pois, assim como na AWS, os nomes de buckets na Magalu Cloud também são globais e devem ser únicos, portanto ao invés de inventar um nome único, forneci só um prefixo e deixei para o provider a tarefa de preencher o resto do nome com um numero aleatório único.

Neste ponto, já podemos utilizar o comando tofu plan para planejar o provisionamento:

tofu plan

Este comando lista as mudanças nos recursos que serão necessárias para refletir o estado final descrito nas configurações declarativas do projeto. No nosso caso, ele listará a criação de um bucket: "# aws_s3_bucket.first_bucket will be created"

E posteriormente aplicar este plano:

tofu apply -compact-warnings

Ele vai exibir o plano novamente e pedir para você digitar yes.

Se tudo der certo este processo deve terminar com uma mensagem parecida com esta:

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

O estado deste novo recurso pode ser consultado com os comandos tofu state list, tofu state show e tofu show

A criação deste bucket pode ser verificada também pela listagem do console via navegador:

Lembre-se de mudar a região para "Brasil -Nordeste 1" caso seu console esteja em outra região:

Mais recursos: objetos

Agora que temos um resource do tipo aws_s3_bucket criado, vamos utilizar um resource do tipo aws_s3_object para adicionar dois arquivos dentro deste bucket:

resources.tf
resource "aws_s3_bucket" "first_bucket" {
bucket_prefix = "tutorial-bucket"
}

resource "aws_s3_object" "first_bucket_objects" {
for_each = tomap({
file1 = "./main.tf"
file2 = "./resources.tf"
})
bucket = aws_s3_bucket.first_bucket.id
key = each.value
source = each.value
}

Um tofu plan vai nos dizer:

Plan: 2 to add, 0 to change, 0 to destroy.

E o posterior tofu apply:

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Pronto, os dois arquivos estão na nuvem. Um tofu state list deve mostrar agora 3 resources:

$ tofu state list
aws_s3_bucket.first_bucket
aws_s3_object.first_bucket_objects["file1"]
aws_s3_object.first_bucket_objects["file2"]

State do segundo object:

$ tofu state show 'aws_s3_object.first_bucket_objects["file2"]'
# aws_s3_object.first_bucket_objects["file2"]:
resource "aws_s3_object" "first_bucket_objects" {
arn = "arn::s3:::tutorial-bucket20240523203657781600000001/resources.tf"
bucket = "tutorial-bucket20240523203657781600000001"
bucket_key_enabled = false
content_type = "application/octet-stream"
etag = "01d689431df57be26625e8fb41ac04d2"
force_destroy = false
id = "./resources.tf"
key = "./resources.tf"
source = "./resources.tf"
storage_class = "STANDARD"
tags_all = {}
}

Desprovisionando Tudo

Tão importante como saber criar coisas na nuvem, é saber como limpar tudo para no fim do mês não receber uma surpresa na conta do cartão de crédito. Este tutorial não estaria completo se não ensinasse como apagar estes recursos todos, por mais baratos que sejam:

tofu destroy
Destroy complete! Resources: 3 destroyed.

Configuração e Estado

Como visto acima, o comando tofu state tem funções para mostrar o que o sistema sabe sobre a instalação real da infraestrutura descrita no código dos módulos de configuração. Ter a declaração em arquivos de como as máquinas deveriam estar e saber como elas de fato estão são duas coisas diferentes, esta segunda parte é o "state", o estado da instalação.

Na minha config eu declarei que a instalação tinha que ter um bucket com um certo prefixo no nome e dois objetos dentro, com o caminho dos arquivos locais para cada objeto.

O estado vai ter bem mais informação que isto, como por exemplo: o nome completo que o bucket recebeu, as etags de cada objeto e vários outros detalhes.

Um ls no diretório do projeto vai mostrar a existência de dois arquivos gerados que descrevem o estado atual e o estado anterior da minha instalação:

terraform.tfstate  terraform.tfstate.backup

Um less no arquivo atual, depois do destroy, vai mostrar que o estado atual não possui nenhum recurso por exemplo:

$ less -F terraform.tfstate
{
"version": 4,
"terraform_version": "1.7.1",
"serial": 7,
"lineage": "51390cb1-af8e-d82b-99d6-1bb5da876657",
"outputs": {},
"resources": [],
"check_results": null
}

Compartilhando o estado com o time

Num cenário onde várias pessoas vão trabalhar na mesma infra, ter as configs e este estado apenas local na máquina de quem executou o plano por último não é o ideal. Ao mesmo tempo, por conter informações sensíveis, não é recomendado colocar os arquivos tfstate.* num repositório git, especialmente se este repositório for público.

Uma possível saída para coordenar este trabalho em grupo e compartilhar de maneira privada o estado da instalação, é usar a funcionalidade de estado remoto, que suporta várias opções de onde armazenar este estado, implementados por diferentes backends.

Vamos então finalizar este tutorial com um exemplo de como configurar o OpenTofu para armazenar o estado remotamente num bucket de S3 da Magalu Clloud, utilizando o backend "s3".

Primeiro, vamos criar um bucket novo só para isto, num arquivo novo state-bucket.ft:

state-bucket.tf
resource "aws_s3_bucket" "state_bucket" {
bucket_prefix = "tutorial-state-bucket"
}

tofu apply

Agora vamos exibir na tela o nome do bucket gerado para copiar e usar na config do backend depois:

tofu state show aws_s3_bucket.state_bucket

E finalmente, de volta no main.tf adicionamos este bloco backend "s3" ao bloco terraform

main.tf

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.50.0"
}
}

backend "s3" {
bucket = "NOME_DO_SEU_BUCKET_DE_STATE_AQUI"
key = "the_state"
region = "br-ne1"
profile = "tutorial-tofu"
skip_region_validation = true
skip_requesting_account_id = true
skip_credentials_validation = true
skip_s3_checksum = true
}
}

provider "aws" {
profile = "tutorial-tofu"
skip_region_validation = true
skip_requesting_account_id = true
skip_credentials_validation = true
}

E para instalar o backend:

tofu init
Nota

num cenário mais real talvez o bucket de state não viveria no mesmo projeto, ou teria alguma prevenção tipo lifecycle { prevent_destroy = true }, ou seria criado manualmente. Eu criei junto aqui apenas para facilitar o tutorial.

(adaptado do post original em Egoísmo Duplicado)