Você está na página 1de 75

COMPANY NAME

Address | Phone | Link | Email

Melhores práticas com Terraform

Description

Terraform, da HashiCorp, é uma solução de Infrastructure as Code (IaC) que permite especificar
configurações de infraestrutura, seja em cloud ou on-premise, em arquivos de configuração facilmente
a r
lidos por humanos, que podem ser reutilizados e compartilhados.k
a t e rm
Muitos profissionais de TI atualmente utilizam Terraform para gerenciar sua infraestrutura. Mas, você
lt w
sabia que há algumas melhore práticas que você deve seguir quando está escrevendo seus arquivos
u
f a
Terraform e definindo sua infraestrutura como código e seu workspace Terraform?
d e
Este é um artigo mão na massa. Enquanto apresento as mais de 20 melhores práticas para
Terraform, você tem a oportunidade de aplicá-las durante a leitura. Então, sem mais delongas, vamos
começar.

O código gerado pelos exercícios existentes neste artigo pode ser encontrado aqui.

Pré-requisitos
Se você quer seguir a parte prática desse artigo, alguns pré-requisitos são necessários. Você pode, se
não quiser sujar suas mãos, ignorá-los.

Uma conta na AWS:

Se você não tem uma conta na AWS, pode criá-la gratuitamente aqui.

IAM User:

Crie um usuário IAM na sua conta na AWS com permissões administrativas e com chaves de acesso
geradas.

Bucket S3:

Crie o bucket S3 onde vamos armazenar nossos arquivos de estado (tfstate) do Terraform. Um bom

Page 1
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

nome para ele seria tf-best-practicies-ACCOUNT-ID-us-east-1. Substitua o ACCOUNT-ID pelo número


da sua conta AWS.

Tabela DynamoDB:

Para gerenciar o locking do arquivo de estado do Terraform, crie uma tabela no DynamoDB com a
hash_key = “LockID” e um atributo { name = “LockID”, type = “S” }. Veja aqui maiores detalhes sobre
essa operação.

Para a criação dos componentes do backend remoto em AWS, podemos utilizar os passos
especificados neste artigo.

Uma conta no Github, com Personal Access Token:

Vamos armazenar nosso código em um repositório no Github e utilizaremos SSH para enviar nossos
arquivos para o repositório remoto.

Terraform:

Você pode instalá-lo para o seu sistema operacional predileto por aqui.

a rk
Terraform Docs:

a t e rm
t w
Você pode instalá-lo para o seu sistema operacional predileto por aqui.
u l
ef a
As seguintes ferramentas adicionais:
d
git
zip e unzip
aws-cli
pip3

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.z


unzip awscliv2.zip
sudo ./aws/install

Ambiente configurado com as chaves AWS

export AWS_ACCESS_KEY_ID=<YOUR_AWS_ACCESS_KEY_ID>
export AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_ACCESS_KEY>
export AWS_DEFAULT_REGION=<YOUR_AWS_DEFAULT_REGION>

Prática 1 – Armazene seu código Terraform em um repositório


Git
A IaC se beneficia do GitHub como ferramenta de colaboração e source of truth. O GitHub é uma
plataforma de DevOps e colaboração que é conhecida por seus recursos de controle de versão.

Page 2
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Sistemas de controle de versão (VCS) são comumente usados para manter uma coleção de arquivos
de software, permitindo que os usuários documentem, rastreiem, desfaçam e mesclem alterações
feitas por vários usuários em tempo real.

O GitHub também serve como uma plataforma de colaboração para milhões de desenvolvedores por
meio de conversas em pull requests e problemas. Ao usar o GitHub para controle de versão e
colaboração, os operadores podem cooperar melhor com os desenvolvedores de aplicativos durante
todo o ciclo de vida do software. Como usuário do Terraform, você deve salvar seus arquivos de
configuração em um VCS.

Dica de sucesso 1:

Sempre mantenha seu código versionado e armazenado em um repositório remoto.

Criar um repositório Git para armazenar seu código é a primeira melhor prática que eu recomendo
quando você está iniciando um projeto Terraform. Vamos criar nosso repositório Git antes de começar
a codificar nossa infraestrutura.

Vamos inicializar um repositório Git local e criar um repositório remoto no Github.

a rk
mkdir tf-best-practicies
cd tf-best-practicies
a t e rm
git init -b main
u lt w
git config user.name "<Name to use in Git commits>"
ef a
git config user.emaikl "<Email to use in Git commits>"
ls -laF d
git status

Page 3
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a
Fig. 1 – Inicialização do repositório Git local

Crie um repositório no Github chamado tf-best-practicies.

Prática 2 – Use .gitignore


Usamos o arquivo .gitignore para dizer ao Git que desejamos excluir de nossos commits arquivos de
estado do Terraform, arquivos de backup, planos de execução, entre outros.

O comando terraform init cria um diretório de trabalho que contém os arquivos de configuração do
Terraform. Este comando prepara o diretório de trabalho para uso do Terraform. Também descobrirá,
baixará e instalará automaticamente os plug-ins de provedor apropriados publicados no Terraform
Registry público ou em um registro de provedor de terceiros.

Todos os arquivos baixados localmente não precisam ser enviados para o repositório Git com outros
arquivos de configuração do Terraform. Além disso, outros arquivos como chaves ssh, arquivos de
estado e arquivos de log ou planos de execução também não precisam ser enviados.

Você pode informar ao Git quais arquivos e diretórios devem ser ignorados ao fazer o commit,
colocando um arquivo .gitignore no diretório raiz do seu projeto. Configure o arquivo .gitignore em seu
repositório para compartilhar as regras de ignorar com outros usuários que possam querer cloná-lo.
Um arquivo .gitignore local normalmente deve ser mantido no diretório raiz do projeto.

Page 4
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Abaixo você encontrará o .gitignore que eu uso em todos os meus projetos mas você pode modificá-lo
de acordo com as suas necessidades. Todos os arquivos configurados no .gitignore a seguir serão
ignorados pelo Git e, portanto, não serão versionados pelo Git e nem enviados para o repositório
remoto.

Dica de sucesso 2:

Sempre tenha um arquivo .gitignore em seu repositório com as regras para evitar que arquivos
desnecessários sejam gerenciados pelo versionador.

Vamos criar um arquivo .gitignore e enviá-lo ao repositório remoto. Execute em seu computador, no
diretório tf-best-practicies, os comandos abaixo:

cat <<__EOF__>.gitignore
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*
a rk
# Crash log files
a t e rm
crash.log
u lt w
d ef a
# Exclude all .tfvars files, which are likely to contain sentitive data, such
# password, private keys, and other secrets. These should not be part of versi
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
#
terraform/**/*.tfvars

# Ignore override files as they are usually used to override resources locally
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Include tfplan files to ignore the plan output of command: terraform plan -o
*tfplan*

# Ignore CLI configuration files


.terraformrc
terraform.rc
.terraform.*

# Ignore autoenv configuration


.env

# Ignore SSH keys


*id_rsa*

Page 5
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

# Ignore zip files


*.zip
__EOF__

git status
git add .gitignore
git status
git commit -m "Adding .gitignore"
git log
git remote add origin URL_DO_SEU_REPOSITORIO
git push -u origin main

a rk
a t e rm
u lt w
d ef a

Page 6
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 2 – Enviando .gitignore para o Github

Page 7
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
Fig. 3 – Enviando .gitignore para o Github

d ef a

Fig. 4 – Nosso repositório com o .gitignore

Prática 3 – Use uma estrutura de arquivos consistente


Não há uma estrutura rígida de arquivos e diretórios exigida pelo Terraform. Para um projeto pequeno,
pode ser suficiente apenas um arquivo contendo as variáveis, os outputs e os resources, mas
recomendo que os projetos sejam formatados da mesma maneira, independente de seu tamanho.

Posso dar algumas sugestões para projetos simples:

1. Use o diretório modules na estrutura do seu projeto quando há módulos. Módulos são diretórios
que contém arquivos de configuração do Terraform que foram criados de maneira a permitir
reutilização;

Page 8
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

2. Um arquivo README.md deve ser incluído pelo menos na raíz do projeto, mas podemos manter
um para cada módulo, com a documentação de uso do módulo específico;
3. Crie main.tf para chamar os módulos, locals.tf para armazenar configurações locais e data.tf
para os recursos data;
4. Use um arquivo provider.tf com os detalhes do provider;
5. Use um arquivo backend.tf com os detalhes de backend do projeto;
6. Use um arquivo variables.tf com as configurações das variáveis utilizadas;
7. Use um arquivo outputs.tf com os outputs do projeto;
8. Use um arquivo terraform.tfvars para carregar automaticamente as variáveis.

Dica de sucesso 3:

Use sempre estruturas consistentes de diretórios e arquivos, não importa o tamanho de seu projeto.

Vamos criar uma estrutura de arquivos consistente, a título de exemplo. Você pode adicionar mais
arquivos se quiser, mas lembre-se: a estrutura de arquivos devem ser consistente entre seus projetos.
Para isso, execute os comandos abaixo:

ls -la
touch README.md
a rk
touch main.tf
touch variables.tf
a t e rm
touch data.tf
u lt w
touch outputs.tf
touch provider.tf
touch backend.tf d ef a
touch locals.tf
touch terraform.tfvars

Fig. 5 – Estrutura de artigos consistente

Vamos mandar nossas modificações para o repositório remoto.

Page 9
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

git status
git add .
git commit -m 'Keep consistent file structure'
git push

a rk
a t e rm
u lt w
d ef a

Fig. 6 – Enviando modificações para o repositório remoto

Prática 4 – Auto-formatação de arquivos Terraform


Legibilidade conta. Devemos sempre pensar que construímos código que será lido por outras pessoas
em algum momento. Terraform, seja em JSON ou em HCL, segue os mesmos guias de estilo de

Page 10
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

outras linguagens de programação. Uma chave não fechada ou uma identação mal feita pode tornar
seu código difícil de ler e difícil de manter.

Pensando nessa questão, a Hashicorp inseriu no Terraform um comando fmt, que corrige as
discrepâncias do código. Os arquivos do Terraform são reescritos em uma estrutura e estilo
consistentes utilizando o comando terraform fmt.

Dica de sucesso 4:

Sempre use terraform fmt -diff para verificar e formatar seus arquivos Terraform antes de enviá-los
ao repositório remoto.

Vamos criar um arquivo provider.tf, sem nenhuma formatação.

cat <<__EOF__>provider.tf
# Provider Requirements
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~>3.0"
a rk
}
}
a t e rm
}
u lt w
d ef a
# AWS Provider (aws) with region set to 'us-east-1'
provider "aws" {
region = "us-east-1"
}
__EOF__

Com o arquivo preparado, mas sem formatação, vamos rodar os seguintes comandos:

terraform fmt -check


terraform fmt -diff

Page 11
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 7 – terraform fmt corrigindo formatação do arquivo provider.tf

Não nos esqueçamos nunca de enviar as modificações ao repositório Git remoto. Já vamos também
inicializar nosso projeto.

git status
git add provider.tf
git commmit -m 'Add provider.tf, format Terraform files'
git push
terraform init

Page 12
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 8 – terraform init

Prática 5 – Evite valores Hard Coded


Algumas vezes provavelmente acabamos codificando valores padrão para as configurações. Quem
nunca pensou algo como : “Vou fazer isso funcionar por enquanto e descobrir como melhorá-lo mais
tarde, quando tiver algum tempo livre”. Mas você recebeu uma nova tarefa e esqueceu o que fez para
“fazer funcionar agora”. Você também se preocupa em arruinar algo que funciona tentando melhorá-lo.

Isso fere nossas boas práticas e nos impede de criar um bom código, reaproveitável e modular. Então,
é uma prática recomendada evitar recursos de hard coding nos arquivos de configuração do
Terraform. Em vez disso, os valores devem ser colocados como variáveis.

Dica de sucesso 5:

Page 13
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Sempre defina variáveis, atribua valores a elas e as use onde necessitar.

No exemplo abaixo temos a definição de uma instância EC2 com AMI, tipo e nome hard coded. Esse
código não é reaproveitável.

resource "aws_instance" "example" {


ami = "ami-005de95e8ff495156"
instance_type = "t2.micro"
tags = {
Name = "instance-1"
}
}

Ao invés disso, declare variáveis e use-as na definição do resource. Execute os comandos abaixo
para criar um arquivo variables.tf, contendo as definições das variáveis, e use-as no main.tf para criar
nossa instância EC2.

cat <<__EOF__>variables.tf
variable "instance_ami" {
a rk
rm
description = "Value of the AMI ID for the EC2 instance"
type = string
a t e
}
default = "ami-005de95e8ff495156"

u lt w
variable "instance_type" {
d ef a
description = "Value of the Instance Type for the EC2 instance"
type = string
default = "t2.micro"
}
variable "instance_name" {
description = "Value of the Name Tag for the EC2 instance"
type = string
default = "instance-1"
}
__EOF__

cat <<__EOF__>main.tf
resource "aws_instance" "example" {
ami = var.instance_ami
instance_type = var.instance_type
tags = {
Name = var.instance_name
}
}
__EOF__

Vamos criar nosso primeiro recurso com Terraform.

terraform init
terraform plan

Page 14
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

terraform apply

Fig. 9 – Final da execução do terraform apply a rk


a t e rm
u lt w
d ef a

Fig. 10 – Instância EC2 criada pelo Terraform

Não se esqueça de enviar as modificações no código para o Github.

Page 15
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Prática 6 – Siga sempre uma convenção de nomenclatura


Terraform é bastante subjetivo quando se trata do nome de um recurso. A única regra que, se
quebrada, gera erros durante a execução do código é a de que não pode haver dois recursos
diferentes com o mesmo nome.

Não há rigidez na criação de um nome de recurso, mas podemos definir algumas padronizações de
forma a ter legibilidade e evitar confusão. Isso pode ser acordado com seu time ou definido em
documentos de padronização da sua companhia.

1. Ao invés de – (hífen), use _ (underscore) em todos os lugares (nomes de recursos, variáveis,


outputs, locals, etc);
2. Use somente letras minúsculas e números;
3. Use nomes no singular;
4. Use – (hífen) nos argumentos e valores de variáveis, principalmente os que são visíveis pelos
usuários;
5. Use nomes descritivos para cada recuros. Um Security Group chamado 133_zebra é menos

a rk
descritivo que um Security Group chamado secgroup_alb_wordpress.

Dica de sucesso 6:
a t e rm
lt w
Defina normas e padrões de nomenclatura com seu time e siga-os o tempo todo.
u
d ef a
No exemplo seguinte, vamos ver os nomes de nossos recursos e variáveis definidos em letras
minúsculas, com números e _ (underscores), enquanto os valores das variáveis são definidas com –
(hífen). Definimos também o arquivo outputs.tf, seguindo as mesmas normas de nomenclatura.

cat <<__EOF__>variables.tf
variable "instance_1_ami" {
description = "Value of the AMI ID for the EC2 instance"
type = string
default = "ami-005de95e8ff495156"
}

variable "instance_1_type" {
description = "Value of the Instance Type for the EC2 instance"
type = string
default = "t2.micro"
}

variable "instance_1_name" {
description = "Value of the Name Tag for the EC2 instance"
type = string
default = "instance-1"
}
__EOF__

cat <<__EOF__>main.tf

Page 16
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

resource "aws_instance" "instance_1" {


ami = var.instance_1_ami
instance_type = var.instance_1_type
tags = {
Name = var.instance_1_name
}
}
__EOF__

cat <<__EOF__>outputs.tf
output "instance_1_id" {
description = "The ID of the instance-1"
value = try(aws_instance.instance_1.id)
}
__EOF__

Executamos nosso novo código:

terraform plan
terraform apply

a rk
a t e rm
Uma nova instância EC2 foi criada, substituindo a instância que foi criada na prática 5.

u lt w
d ef a

Fig. 11 – Nova instância EC2 criada

Antes da próxima prática, vamos remover os recursos que criamos na AWS até agora.

Page 17
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

terraform destroy

a rk
a t e rm
u lt w
d ef a

Fig. 12 – Recursos removidos

Não se esqueça de enviar suas modificações para o Github.

Prática 7 – Use a variável self


As variáveis gerais são úteis de várias maneiras, mas falta um elemento importante: a capacidade de
prever o futuro. Uma variável self é um tipo de valor exclusivo para seus recursos e preenchido no
momento da criação. Esse tipo de variável é utilizado quando o valor de uma variável é desconhecido

Page 18
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

antes da implantação da infraestrutura. É importante observar que apenas os blocos connection e


provisioner do Terraform habilitam essas variáveis.

Por exemplo, self.private_ip pode ser usado para obter o endereço IP privado de uma máquina após a
implantação inicial, mesmo que o endereço IP não seja conhecido até que seja atribuído.

Dica de sucesso 7:

Use a variável self quando você precisa conhecer o valor de uma variável antes do deploy da
infraestrutura.

cat <<__EOF__>main.tf
resource "aws_instance" "instance_1" {
ami = var.instance_1_ami
instance_type = var.instance_1_type
tags = {
Name = var.instance_1_name
}
}

resource "aws_instance" "instance_2" {


a rk
ami = var.instance_2_ami
instance_type = var.instance_2_type
a t e rm
tags = {
Name = var.instance_2_name
u lt w
}
provisioner "local-exec" {d ef a
command = "echo The IP address of the Server is \${self.private_ip}"
on_failure = continue
}
}
__EOF__

cat <<__EOF__>variables.tf
variable "instance_1_ami" {
description = "Value of the AMI ID for the EC2 instance"
type = string
default = "ami-005de95e8ff495156"
}

variable "instance_1_type" {
description = "Value of the Instance Type for the EC2 instance"
type = string
default = "t2.micro"
}

variable "instance_1_name" {
description = "Value of the Name Tag for the EC2 instance"
type = string
default = "instance-1"
}

Page 19
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

variable "instance_2_ami" {
description = "Value of the AMI ID for the EC2 instance"
type = string
default = "ami-005de95e8ff495156"
}

variable "instance_2_type" {
description = "Value of the Instance Type for the EC2 instance"
type = string
default = "t2.micro"
}

variable "instance_2_name" {
description = "Value of the Name Tag for the EC2 instance"
type = string
default = "instance-2"
}
__EOF__

cat <<__EOF__>outputs.tf
output "instance_1_id" {
a rk
description = "The ID of the instance-1"
value
t e
= try(aws_instance.instance_1.id)
a rm
}

u lt w
output "instance_2_id" {
d ef a
description = "The ID of the instance-2"
value = try(aws_instance.instance_2.id)
}
__EOF__

Vamos executar nosso código.

terraform plan
terraform apply

Page 20
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 13 – Variável self mostrando seu valor em tempo de deploy

Não se esqueça de mandar seu código para o Github e destruir os recursos lançados (usando
terraform destroy)

Prática 8 – Use módulos


Terraform nos permite projetar configurações cada vez mais sofisticadas para gerenciar nossa
infraestrutura. Entretanto, nosso arquivo ou diretório de configuração não tem limites e pode ser
prejudicial para a legibilidade, manutenção e replicação do código. Isso pode ser mitigado usando
módulos.

Um módulo é um contêiner para uma coleção de recursos relacionados. Os módulos podem ser
usados para construir abstrações leves, permitindo que sua infraestrutura seja descrita em termos de
arquitetura e não em termos de objetos físicos. Você pode colocar seu código em um módulo
Terraform e reutilizá-lo várias vezes ao longo da vida útil do seu projeto Terraform.

Page 21
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Por exemplo, você poderá reutilizar o código do mesmo módulo nos ambientes Dev e QA, em vez de
copiar e colar o mesmo código.

Todo profissional do Terraform deve empregar módulos de acordo com as seguintes diretrizes:

1. Comece a escrever sua configuração;


2. Organize e encapsule seu código usando módulos locais;
3. Encontre módulos relevantes pesquisando no Terraform Registry;
4. Compartilhe módulos com sua equipe após a publicação.

Dica de sucesso 8:

Sempre use módulos. Você vai economizar muito tempo de codificação. Não há necessidade de
reinventar a roda.

Módulos nos auxiliam a reutilizar código, diminuindo o tempo de desenvolvimento do nosso projeto.
Vamos seguir o passo-a-passo para programar e usar um módulo que irá criar dois buckets S3.

mkdir -p modules/aws-s3-static-website-bucket/www
a rk
rm
tree

a t e
u lt w
d ef a

Fig. 14 – Estrutura de projeto Terraform com módulos

Execute os comandos abaixo para criar os arquivos necessários para o funcionamento do módulo.

cat <<__EOF__>modules/aws-s3-static-website-bucket/README.md
AWS S3 Static Website Bucket Module

Page 22
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

===================================

Modulo para criar buckets S3 para Websites


------------------------------------------
__EOF__

cat <<__EOF__>modules/aws-s3-static-website-bucket/main.tf
resource "aws_s3_bucket" "s3_bucket" {
bucket_prefix = var.bucket_prefix

tags = var.tags
}

resource "aws_s3_bucket_website_configuration" "s3_bucket" {


bucket = aws_s3_bucket.s3_bucket.id

index_document {
suffix = "index.html"
}

error_document {
key = "error.html"
a rk
}
}

a t e rm
u lt w
resource "aws_s3_bucket_acl" "s3_bucket" {

ef a
bucket = aws_s3_bucket.s3_bucket.id
d
acl = "public-read"
}

resource "aws_s3_bucket_policy" "s3_bucket" {


bucket = aws_s3_bucket.s3_bucket.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = [
aws_s3_bucket.s3_bucket.arn,
"\${aws_s3_bucket.s3_bucket.arn}/*",
]
},
]
})
}
__EOF__

cat <<__EOF__>modules/aws-s3-static-website-bucket/variables.tf

Page 23
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

variable "bucket_prefix" {
description = "Prefix of the s3 bucket. We need to guarantee that bucket nam
type = string
}

variable "tags" {
description = "Tags to set on the bucket."
type = map(string)
default = {}
}
__EOF__

cat <<__EOF__>modules/aws-s3-static-website-bucket/outputs.tf
output "arn" {
description = "ARN of the bucket"
value = aws_s3_bucket.s3_bucket.arn
}

output "name" {
description = "Name (id) of the bucket"
value = aws_s3_bucket.s3_bucket.id
}
a rk
output "domain" {
a t e rm
value
u l w
description = "Domain name of the bucket"
t
= aws_s3_bucket_website_configuration.s3_bucket.website_domain
}
__EOF__ d ef a
O comando abaixo vai criar os arquivos main.tf e outputs.tf na raiz do nosso projeto Terraform.

cat <<__EOF__>main.tf
resource "aws_instance" "instance_1" {
ami = var.instance_1_ami
instance_type = var.instance_1_type
tags = {
Name = var.instance_1_name
}
}

resource "aws_instance" "instance_2" {


ami = var.instance_2_ami
instance_type = var.instance_2_type
tags = {
Name = var.instance_2_name
}
provisioner "local-exec" {
command = "echo The IP address of the Server is \${self.private_ip}"
on_failure = continue
}
}

module "website_s3_bucket" {

Page 24
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

source = "./modules/aws-s3-static-website-bucket"

bucket_prefix = "terraform-best-practices-"

tags = {
Terraform = "true"
Environment = "test"
}
}
__EOF__

cat <<__EOF__>outputs.tf
output "instance_1_id" {
description = "The ID of the instance-1"
value = try(aws_instance.instance_1.id)
}

output "instance_2_id" {
description = "The ID of the instance-2"
value = try(aws_instance.instance_2.id)
}

a rk
output "website_bucket_arn" {
description = "ARN of the bucket"
a t e rm
}
value

u lt w
= module.website_s3_bucket.arn

d e
output "website_bucket_name" {f a
description = "Name (id) of the bucket"
value = module.website_s3_bucket.name
}

output "website_bucket_domain" {
description = "Domain name of the bucket"
value = module.website_s3_bucket.domain
}
__EOF__

Adicionalmente, criaremos dois arquivos HTML para compor nosso site estático armazenado no S3.

cat <<__EOF__>modules/aws-s3-static-website-bucket/www/error.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8?>
<title>Error</title>
</head>
<body>
<p>Something is wrong here</p>
</body>
</html>
__EOF__

Page 25
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

cat <<__EOF__>modules/aws-s3-static-website-bucket/www/index.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8?>
<title>Static Website</title>
</head>
<body>
<p>This is a sample static website hosted in AWS S3 bucket</p>
</body>
</html>
__EOF__

Executamos o nosso projeto, como sempre.

terraform init
terraform plan
terraform apply

Sempre que criarmos um novo módulo devemos rodar o comando terraform init, para que o módulo
seja reconhecido dentro do diretório .terraform.
a rk
a t e rm
u lt w
d ef a

Page 26
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 15 – Criando bucket S3

Vamos copiar os arquivos HTML que criamos para o bucket

aws s3 cp modules/aws-s3-static-website-bucket/www/ s3://$(terraform output -r

Page 27
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Fig. 16 – Arquivos copiados para o bucket

Agora vamos criar um novo bucket S3, reaproveitando o módulo já desenvolvido. Execute os
comandos abaixo:

cat <<__EOF__>>outputs.tf

output "website_bucket_2_arn" {
description = "ARN of the bucket"
value = module.website_s3_bucket_2.arn
}

output "website_bucket_2_name" {
description = "Name (id) of the bucket"
value = module.website_s3_bucket_2.name
}

output "website_bucket_2_domain" {
description = "Domain name of the bucket"
value = module.website_s3_bucket_2.domain
}
__EOF__
a rk
a t e rm
cat <<__EOF__>>main.tf
u lt w
e
module "website_s3_bucket_2" {
d f a
source = "./modules/aws-s3-static-website-bucket"

bucket_prefix = "terraform-best-practices-bucket-2-"

tags = {
Terraform = "true"
Environment = "test"
}
}
__EOF__

Vamos executar nosso novo código.

terraform init
terraform plan
terraform apply

Page 28
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
Fig. 16 – Criando o novo bucket
u lt w
d ef a
Não vamos nos esquecer de enviar o código modificado para nosso Github. Em seguida, antes de
passarmos para a próxima prática, vamos eliminar os recursos criados.

aws s3 rm s3://$(terraform output -raw website_bucket_name)/ --recursive


aws s3 rm s3://$(terraform output -raw website_bucket_2_name)/ --recursive
terraform destroy

Prática 9 – Execute Terraform com -var-file


O parâmetro -var-file é usado para para informar ao Terraform um arquivo de parâmetros que devem
ser utilizados como valores das variáveis esperadas pelo código.

Isso permite que você salve os valores das variáveis de entrada em um arquivo com o sufixo .tfvars,
que pode ser armazenado no versionador para qualquer ambiente de variável que você precise
implantar.

Se o diretório atual contiver um arquivo terraform.tfvars, o Terraform o usará automaticamente para


preencher as variáveis. Se o arquivo tiver um nome diferente, você poderá fornecê-lo explicitamente
usando o sinalizador -var-file.

Page 29
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Uma vez que você tenha um ou mais arquivos .tfvars, você pode usar o sinalizador -var-file para
direcionar o Terraform sobre qual arquivo ele deve usar para fornecer variáveis de entrada para o
comando Terraform.

Esta é mais uma ferramenta que nos possibilita reaproveitar o código Terraform desenvolvido para
criar ambientes diferentes como, por exemplo, ambientes de desenvolvimento, testes e produção.

Dica de sucesso 9:

Matenha múltiplos arquivos .tfvars com definição de variáveis, que podem ser informados aos
comandos terraform plan ou terraform apply através do argumento -var-file.

Vamos para os nossos testes. Execute os comandos abaixo:

cat <<__EOF__>test.tfvars
instance_1_ami = "ami-005de95e8ff495156"
instance_1_type = "t2.micro"
instance_1_name = "instance-1"
instance_2_ami
instance_2_type
= "ami-005de95e8ff495156"
= "t2.micro"
a rk
instance_2_name = "instance-2"
a t e
website_s3_bucket_1_prefix= "terraform-best-practices-1-"rm
t w
website_s3_bucket_2_prefix = "terraform-best-practices-2-"
u l
terraform = "true"
environment = "test"
__EOF__ d ef a
cat <<__EOF__>main.tf
resource "aws_instance" "instance_1" {
ami = var.instance_1_ami
instance_type = var.instance_1_type
tags = {
Name = var.instance_1_name
}
}

resource "aws_instance" "instance_2" {


ami = var.instance_2_ami
instance_type = var.instance_2_type
tags = {
Name = var.instance_2_name
}
provisioner "local-exec" {
command = "echo The IP address of the Server is \${self.private_ip}"
on_failure = continue
}
}

module "website_s3_bucket_1" {
source = "./modules/aws-s3-static-website-bucket"

Page 30
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

bucket_prefix = var.website_s3_bucket_1_prefix

tags = {
Terraform = var.terraform
Environment = var.environment
}
}

module "website_s3_bucket_2" {
source = "./modules/aws-s3-static-website-bucket"

bucket_prefix = var.website_s3_bucket_2_prefix

tags = {
Terraform = var.terraform
Environment = var.environment
}
}
__EOF__

cat <<__EOF__>variables.tf
a rk
variable "instance_1_ami" {
t e rm
description = "Value of the AMI ID for the EC2 instance"
a
}
type = string

u lt w
d ef
variable "instance_1_type" {
a
description = "Value of the Instance Type for the EC2 instance"
type = string
}

variable "instance_1_name" {
description = "Value of the Name Tag for the EC2 instance"
type = string
}

variable "instance_2_ami" {
description = "Value of the AMI ID for the EC2 instance"
type = string
}

variable "instance_2_type" {
description = "Value of the Instance Type for the EC2 instance"
type = string
}

variable "instance_2_name" {
description = "Value of the Name Tag for the EC2 instance"
type = string
}

variable "website_s3_bucket_1_prefix"{

Page 31
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

description = "Value of the Name Tag for the S3 bucket"


type = string
}

variable "website_s3_bucket_2_prefix"{
description = "Value of the Name Tag for the S3 bucket"
type = string
}

variable "terraform"{
description = "Value of the Terraform Tag for the S3 bucket"
type = string
}

variable "environment"{
description = "Value of the Environment Tag for the S3 bucket"
type = string
}
__EOF__

cat <<__EOF__>outputs.tf
output "instance_1_id" {
a rk
description = "The ID of the instance-1"
value
t e
= try(aws_instance.instance_1.id)
a rm
}

u lt w
output "instance_2_id" {
d ef a
description = "The ID of the instance-2"
value = try(aws_instance.instance_2.id)
}

output "website_bucket_1_arn" {
description = "ARN of the bucket"
value = module.website_s3_bucket_1.arn
}

output "website_bucket_1_name" {
description = "Name (id) of the bucket"
value = module.website_s3_bucket_1.name
}

output "website_bucket_1_domain" {
description = "Domain name of the bucket"
value = module.website_s3_bucket_1.domain
}

output "website_bucket_2_arn" {
description = "ARN of the bucket"
value = module.website_s3_bucket_2.arn
}

output "website_bucket_2_name" {
description = "Name (id) of the bucket"
value = module.website_s3_bucket_2.name

Page 32
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

output "website_bucket_2_domain" {
description = "Domain name of the bucket"
value = module.website_s3_bucket_2.domain
}
__EOF__

Vamos executar nosso código Terraform informando o arquivo .tfvars que criamos:

terraform init
terraform plan -var-file=test.tfvars
terraform apply -var-file=test.tfvars

a rk
a t e rm
u lt w
d ef a

Fig. 17 – Criação de recursos utilizando -var-file

Não se esqueça de enviar o código para o Github e excluir os recursos criados. Também para o
comando terraform destroy teremos que utilizar o argumento -var-file.

terraform destroy -var-file=test.tfvars

Prática 10 – Armazene o arquivo de estado do Terraform em

Page 33
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

um storage remoto
O arquivo de estado (tfstate) do Terraform é um dos componentes mais importantes do projeto. É ele
que mantém os dados do que foi aplicado, do que deve ser modificado para manter a integridade da
infraestrutura. Perder esse arquivo pode simplesmente invalidar todo o seu código Terraform.

Por padrão, o Terraform salva o estado de uma infraestrutura em um arquivo tfstate armazenado
localmente. Embora isso possa ser suficiente enquanto estamos desenvolvendo nosso código ou
testando algo, quando pensamos nos ambientes definitivos ou em um trabalho compartilhado ou em
um pipeline de IaC, precisamos armazenar o tfstate em um lugar disponível e tolerante a falhas de
diversos tipos. Além disso, quando trabalhamos em um time, precisamos garantir que todos estejam
acessando a versão mais atual do arquivo de estado e que somente uma pessoa por vez possa
modificá-lo. Para isso usamos o conceito de remote state.

Terraform com acesso compartilhado ao arquivo de estado armazenado em um ambiente remoto é o


melhor caminho a se seguir para projetos em grupo. Os problemas apresentados anteriormente são
endereçados pelo remote state. Basicamente, usar um remote state significa armazenar o arquivo
tfstate em um servidor remoto ao invés de em nossa máquina local e garantir que esse local remoto

tfstate mais atual. a rk


tenha algum controle de locking desse arquivo. Assim, os times podem ter a certeza de usar sempre o

a t e rm
Dica de sucesso 10:

u lt w
d ef a
Quando trabalhamos em um projeto junto a várias outras pessoas, devemos sempre usar backends
Terraform que salvam o state file em um armazenamento remoto compartilhado.

Para armazenar o arquivo de estado em um backend remoto em um bucket S3, vamos seguir os
passos abaixo, criando nosso bucket e configurando um arquivo backend.tf indicando esse bucket.
Substitua ACCOUNTID pelo número da sua conta AWS.

aws s3 mb s3://tf-best-practices-ACCOUNTID-us-east-1 --region=us-east-1

cat <<__EOF__>backend.tf
terraform {
backend "s3" {
bucket = "tf-best-practices-ACCOUNTID-us-east-1"
key = "terraform.tfstate"
region = "us-east-1"
}
}
__EOF__

Vamos inicializar o Terraform. Certifique-se de ter executado terraform destroy ao final da Prática 9.

rm -rf .terraform
terraform init

Page 34
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a
Fig. 18 – Inicializando o Terraform com remote backend

Não se esqueça de enviar seu código para o Github.

Prática 11 – Bloqueie o arquivo de estado remoto


O estado remoto do Terraform é dividido em duas partes: o arquivo de estado armazenado em um
local remoto (discutido na Prática 10) e o state locking.

Quando duas ou mais pessoas estão operando a mesma infraestrutura ao mesmo tempo, podem
ocorrer problemas com a criação de recursos se os processos do Terraform tentarem lançar o mesmo
recurso.

Nessa situação, se o backend suportar, o Terraform bloqueará o acesso ao arquivo de estato para
qualquer operação que possa escrever nele. O bloqueio estado do Terraform é exigido para prevenir
outros usuário de simultaneamente destruir ou modificar a infraestrutura.

Há diversos backends remotos possíveis que podemos utilizar no Terraform. Cada um trata o bloqueio
do arquivo de estado de uma maneira diferente. Na AWS, esse bloqueio é gerenciado por uma tabela
do DynamoDB, cuja criação foi solicitada na sessão de pré-requisitos deste artigo. Se você não a

Page 35
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

criou, agora é o momento.

Dica de sucesso 11:

Sempre use bloqueio de estado quando seu arquivo de estado estiver armazenado em um backend
remoto.

Para entender como o bloqueio de estado funciona, siga os passos abaixo para modificar o arquivo
backend.tf, adicionando o parâmetro dynamodb_table. Depois, executaremos dois terraform apply
simultâneamente. Não esqueça de enviar suas modificações para o Github.

Caso você não tenha criado a tabela do DynamoDB, execute o comando abaixo. Se a criou, pule para
o próximo comando.

aws dynamodb create-table \


--table-name terraform-backend-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
--region=us-east-1
a rk
cat <<__EOF__>backend.tf
a t e rm
terraform {
u lt w
backend "s3" {
bucket
key ef a
= "tf-best-practices-ACCOUNTID-us-east-1"
d
= "terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-backend-lock"
}
}
__EOF__

terraform init -reconfigure


terraform destroy -var-file=test.tfvars
terraform apply -var-file=test.tfvars

Execute o comando terraform apply em duas sessões diferentes, uma após a outra, e veja o
comportamento.

Page 36
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig.19 – State locking em funcionamento

Prática 12 – Faça cópias de segurança do arquivo de estado


Quando trabalhamos com o Terraform sem informar um backend remoto, notamos dois arquivos
criados em nosso diretório de trabalho: terraform.tfstate e terraform.tfstate.backup. Esses arquivos
contém o estado da infraestrutura gerenciada pelos arquivos do Terraform que estamos usando.
Quando executamos terraform apply, o comando cria um novo terraform.tfstate e move o arquivo
corrente para o backup. Em caso de desastre com o arquivo de estado, basta substituí-lo pelo backup.

O tema de backends do Terraform é tratado aqui.

Se estamos usando um backend remoto como o AWS S3, é extremamente recomendado habilitar o
versionamento do bucket utilizado. Dessa maneira, se o arquivo de estado é removido ou corrompido,
ou mesmo está em um estado incorreto, seremos capazes de recuperá-lo restaurando uma versão

Page 37
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

prévia do arquivo.

Dica de sucesso 12:

Sempre habilite versionamento ou backup do estado remoto do seu Terraform, para que você possa
recuperá-lo em caso de acidente.

Execute os passos abaixo para habilitar o versionamento do bucket S3 utilizado como backend
remoto, criar os recursos na AWS e verificar as versões do arquivo de estado.

a rk
a t e rm
u lt w
d ef a

Fig. 20 – Versionamento de bucket S3 habilitado

terraform apply -var-file=test.tfvars

Page 38
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a
Fig. 21 – Arquivo de estado versionado

Prática 13 – Manipule o arquivo de estado somente pelo


comando terraform
Graças aos dados de estado, o Terraform lembra qual objeto do mundo real corresponde a cada
recurso na configuração, permitindo modificar um objeto existente quando sua declaração de recurso
for alterada. O Terraform atualiza automaticamente o estado durante as operações terraform plan,
terraform apply e terraform destroy. Dito isso, fazer alterações deliberadas nos dados de estado do
Terraform continua sendo necessário em certos casos.

A modificação de dados de estado fora de uma operação normal de terraform pode fazer com que o
Terraform perca o controle dos recursos controlados. Recomendamos o uso do comando terraform,
uma opção mais segura, que fornece comandos para inspecionar o estado, forçar a recriação, mover
recursos e recuperação de desastres.

Para saber mais sobre isso, você pode consultar a documentação oficial aqui.

Dica de sucesso 13:

Page 39
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Sempre manipule o arquivo de estado do Terraform através do comando terraform e evite efetuar
mudanças manuais no arquivo.

terraform apply -refresh-only -var-file=test.tfvars

a rk
a t e rm
u lt w
d ef a

Fig. 22 – terraform refresh arquivo de estado

Page 40
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a
Fig. 23 – Versões do arquivo de estado após refresh

Prática 14 – Gere um README para cada módulo desenvolvido


O README é, tipicamente, o primeiro arquivo que pessoas que estão começando a trabalhar com
algum projeto existente acessa. Do seu advento para cá, as pessoas levaram a sério o pedido do
arquivo: LEIA-ME.

Um bom README deve conter informações as mais relevantes e amigáveis informações de um


projeto. Em síntese, é um documento que informa os objetivos de um projeto, sua execução,
características técnicas, entre outras várias informações. Por ser um arquivo importante, ele deve
fazer parte de nossos projetos em Terraform.

Dica de sucesso 14:

Você deve ter um README consistente e informativo em todos os seus módulos e projetos Terraform.

Vamos dar uma olhada em como gerar um README usando um utilitário chamado terraform-docs.
Vamos mergulhar nesse utilitário que gera automaticamente um README.md para que você evite ter
que escrevê-lo manualmente para variáveis de entrada e saídas. Clique aqui para saber mais sobre
ele.

Para gerar um README.md para nosso projeto, siga os passos abaixo (o utilitário terraform-docs

Page 41
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

deve ter sido previamente instalado).

terraform-docs markdown table --output-file README.md --output-mode replace mo


terraform-docs markdown table --output-file README.md --output-mode replace .

Vamos enviar os README.md criados para nosso Github.

git status
git add README.md modules/aws-s3-static-website-bucket/README.md
git commit -m "Always have a README"
git push

Vá ao Github e verifique os README.md criados.

Prática 15 – Use e abuse das funções built-in


O Terraform tem várias funções incluídas (built-in) que você pode chamar junto a expressões para
alterar e combinar variáveis, indo de operações matemáticas at? manipulação de arquivos.

a rk
Por exemplo, para ler um arquivo de chave privada de SSH, você pode usar uma função do Terraform

t e rm
que permitirá a você estabelecer uma conexão SSH sem ter que armazenar a chave privada no seu
a
código.
u lt w
ef a
Ainda não podemos escrever nossas funções (user-defined functions), mas podemos usar o console
d
do Terraform para testar o comportamento das funções que queremos usar.

Dica de sucesso 15:

Use funções built-in do Terraform para manipular valores em seu código Terraform, executar
operações matemáticas, entre outras tarefas.

Vamos executar o console Terraform e testar algumas funções.

terraform console
max(11, 12, 1)
min(11, 12, 1)
lower("DEVOPS")
upper("devops")
concat(["devops", "terraform"], ["best", "practices"])
length("devops")
base64encode("devops")
base64decode("ZGV2b3Bz")
timestamp()

Page 42
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 24 – Funções do Terraform no Console

Vamos criar uma chave SSH e modificar nossos arquivos Terraform para usar a função file(). Essa
função vai ler qualquer chave pública SSH passada para ela e configurará uma instância EC2 com ela.

ssh-keygen -t rsa -b 2048 -f ~/.ssh/tf_id_rsa

Page 43
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a
Fig. 25 – Nova chave SSH gerada

Executaremos agora os passos abaixo.

cat <<__EOF__>main.tf
resource "aws_key_pair" "terraform_best_practices_demo" {
key_name = "terraform-best-practices-demo-key"
public_key = file("~/.ssh/tf_id_rsa.pub")
}

resource "aws_instance" "instance_1" {


ami = var.instance_1_ami
instance_type = var.instance_1_type
tags = {
Name = var.instance_1_name
}
key_name = "\${aws_key_pair.terraform_best_practices_demo.key_name}"
}

resource "aws_instance" "instance_2" {


ami = var.instance_2_ami
instance_type = var.instance_2_type
tags = {
Name = var.instance_2_name
}

Page 44
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

provisioner "local-exec" {
command = "echo The IP address of the Server is \${self.private_ip}"
on_failure = continue
}
key_name = "\${aws_key_pair.terraform_best_practices_demo.key_name}"
}

module "website_s3_bucket_1" {
source = "./modules/aws-s3-static-website-bucket"

bucket_prefix = var.website_s3_bucket_1_prefix

tags = {
Terraform = var.terraform
Environment = var.environment
}
}

module "website_s3_bucket_2" {
source = "./modules/aws-s3-static-website-bucket"

bucket_prefix = var.website_s3_bucket_2_prefix

a rk
tags = {
Terraform = var.terraform
a t e rm
}
Environment = var.environment
u lt w
}
__EOF__ d ef a
terraform plan -var-file=test.tfvar
terraform apply -var-file="test.tfvars"

Page 45
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 26 – Instâncias criadas com chave SSH

Podemos ver que as instâncias EC2 que tinhamos antes foram destruídas e novas foram criadas,
agora com a informação da chave SSH que deve ser utilizada para acesso.

Page 46
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Fig. 27 – Instância EC2 com chave SSH


a rk
Prática 16 – Use Workspaces a t e rm
u lt w
ef a
Cada configuração do Terraform tem um backend associado que define como o as operações são
d
executadas e onde dados persistentes, como estado, são armazenados

Os dados persistentes armazenados no backend pertencem a um workspace. O back-end inicialmente


tem apenas um workspace contendo um estado do Terraform associado a essa configuração. Alguns
backends oferecem suporte a vários workspaces nomeados, permitindo que vários estados sejam
associados a uma única configuração. A configuração ainda tem apenas um backend, mas você pode
implantar várias instâncias distintas dessa configuração sem configurar um novo backend ou alterar as
credenciais de autenticação.

Usar vários diretórios é a maneira mais simples de gerenciar várias instâncias de uma configuração
com dados de estado totalmente distintos. No entanto, esta não é a técnica mais prática para lidar
com diferentes estados.

Quando se trata de preservar diferentes estados para cada coleção de recursos que você gerencia
usando a mesma cópia de trabalho para sua configuração e os mesmos plugins e caches de módulos,
o Terraform Workspace vem em socorro. Os espaços de trabalho facilitam a transição entre várias
instâncias da mesma configuração no mesmo back-end.

Os workspaces nada mais são do que diferentes instâncias de dados de estado que podem ser
usados no mesmo diretório de trabalho, o que permite gerenciar vários grupos de recursos não
sobrepostos com a mesma configuração. Além disso, você pode usar ${terraform.workspace} para
incluir o nome do workspace atual em sua configuração do Terraform.

Page 47
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Digamos que você tenha um projeto do Terraform que provisiona um conjunto de recursos para seu
ambiente de desenvolvimento. Você poderá usar o mesmo diretório de projeto para provisionar os
mesmos recursos para outro ambiente, controle de qualidade, aproveitando o Terraform Workspace.
Você pode até criar um novo workspace e usar o mesmo diretório de projeto do Terraform para
configurar outro ambiente. Dessa forma, você terá arquivos de estado diferentes pertencentes a
workspaces diferentes para ambos os ambientes.

Dica de sucesso 16:

Use Terraform workspaces para criar múltiplos ambientes como Dev, QA, UAT, Prod, entre outros,
usando os mesmos arquivos de configuração do Terraform e salvando os arquivos de estado de cada
ambiente no mesmo backend remoto.

Vamos ver o Terraform Workspaces funcionando em real-time. Execute os seguintes comandos para
lista, criar e usar workspaces. Assim que você criar os workspaces, um arquivo de estado diferente
para cada um deles será criado dentro do nosso backend remoto (o bucket S3).

terraform workspace list


terraform workspace new dev
a rk
terraform
terraform
workspace
workspace
list
new uat
a t e rm
terraform
terraform
workspace
workspace
list
show
u lt w
terraform
terraform
workspace
workspace ef a
select dev
d
show

Page 48
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 28 – Trabalhando com Workspaces

Os comandos vão gerar essa estrutura no S3:

Page 49
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
Fig. 29 – Estrutura de Workspaces

a t e rm
u lt w
Vamos modificar nosso main.tf e utilizar o ${terraform.workspaces} como prefixo para o nome dos

d ef a
recursos. Aproveitaremos para utilizar uma função built-in: format.

cat <<__EOF__>main.tf
resource "aws_key_pair" "terraform_best_practices_demo" {
key_name = format("%s-terraform-best-practices-demo-key", terraform.worksp
public_key = file("~/.ssh/tf_id_rsa.pub")
}

resource "aws_instance" "instance_1" {


ami = var.instance_1_ami
instance_type = var.instance_1_type
tags = {
Name = format("%s-%s", terraform.workspace, var.instance_1_name)
}
key_name = format("%s", aws_key_pair.terraform_best_practices_demo.key_name)
}

resource "aws_instance" "instance_2" {


ami = var.instance_2_ami
instance_type = var.instance_2_type
tags = {
Name = format("%s-%s", terraform.workspace, varcat <<__EOF__>main.tf
resource "aws_key_pair" "terraform_best_practices_demo" {
key_name = format("%s-terraform-best-practices-demo-key"", terraform.works
public_key = file("~/.ssh/id_rsa.pub")
}

Page 50
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

resource "aws_instance" "instance_1" {


ami = var.instance_1_ami
instance_type = var.instance_1_type
tags = {
Name = format("%s-%s", terraform.workspace, var.instance_1_name)
}
key_name = format("%s", aws_key_pair.terraform_best_practices_demo.key_name)
}

resource "aws_instance" "instance_2" {


ami = var.instance_2_ami
instance_type = var.instance_2_type
tags = {
Name = format("%s-%s", terraform.workspace, var.instance_2_name)
}
provisioner "local-exec" {
command = "echo The IP address of the Server is \${self.private_ip}"
on_failure = continue
}
key_name = format("%s", aws_key_pair.terraform_best_practices_demo.key_name)
}

module "website_s3_bucket_1" {
a rk
source = "./modules/aws-s3-static-website-bucket"

a t e rm
u lt w
bucket_prefix = format("%s-%s", terraform.workspace, var.website_s3_bucket_1

tags = {
Terraform d ef a
= var.terraform
Environment = var.environment
}
}

module "website_s3_bucket_2" {
source = "./modules/aws-s3-static-website-bucket"

bucket_prefix = format("%s-%s", terraform.workspace, var.website_s3_bucket_2

tags = {
Terraform = var.terraform
Environment = var.environment
}
}
__EOF__.instance_2_name)
}
provisioner "local-exec" {
command = "echo The IP address of the Server is \${self.private_ip}"
on_failure = continue
}
key_name = format("%s", aws_key_pair.terraform_best_practices_demo.key_name)
}

module "website_s3_bucket_1" {
source = "./modules/aws-s3-static-website-bucket"

Page 51
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

bucket_prefix = format("%s-%s", terraform.workspace, var.website_s3_bucket_1

tags = {
Terraform = var.terraform
Environment = var.environment
}
}

module "website_s3_bucket_2" {
source = "./modules/aws-s3-static-website-bucket"

bucket_prefix = format("%s-%s", terraform.workspace, var.website_s3_bucket_2

tags = {
Terraform = var.terraform
Environment = var.environment
}
}
__EOF__

Vamos copiar nosso test.tfvars para dev.tfvars e uat.tfvars.

a rk
Vamos modificar o valor da variável environment para refletir os workspaces criados, criando os
arquivos de configuração de cada ambiente.
a t e rm
u lt w
cat <<__EOF__>dev.tfvars
instance_1_ami
instance_1_type d ef a
= "ami-005de95e8ff495156"
= "t2.micro"
instance_1_name = "instance-1"
instance_2_ami = "ami-005de95e8ff495156"
instance_2_type = "t2.micro"
instance_2_name = "instance-2"
website_s3_bucket_1_prefix= "terraform-best-practices-1-"
website_s3_bucket_2_prefix = "terraform-best-practices-2-"
terraform = "true"
environment = "dev"
__EOF__

cat <<__EOF__>uat.tfvars
instance_1_ami = "ami-005de95e8ff495156"
instance_1_type = "t2.micro"
instance_1_name = "instance-1"
instance_2_ami = "ami-005de95e8ff495156"
instance_2_type = "t2.micro"
instance_2_name = "instance-2"
website_s3_bucket_1_prefix= "terraform-best-practices-1-"
website_s3_bucket_2_prefix = "terraform-best-practices-2-"
terraform = "true"
environment = "uat"
__EOF__

Page 52
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Agora, entramos no workspace dev e vamos aplicar as modificações.

terraform workspace select dev


terraform plan -var-file=dev.tfvars
terraform apply -var-fie=dev.tfvars

a rk
Fig. 30 – Recursos criados como DEV
a t e rm
u lt w
d ef a
Agora, vamos executar o mesmo processo para o workspace uat.

terraform workspace select uat


terraform apply -var-file=uat.tfvars

Fig. 31 – Todos os ambientes lançados

No screenshot acima podemos ver que os recursos foram corretamente lançados para cada ambiente
e seus nomes possuem como prefixo os nomes dos workspaces.

Page 53
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Para remover os recursos, execute:

terraform destroy -var-file="uat.tfvars"


terraform workspace select dev
terraform destroy -var-file="dev.tfvars"
terraform workspace select default
terraform destroy -var-file="test.tfvars"

a rk
a t e rm
Fig. 32 – Recursos destruídos

u lt w
ef a
Fizemos várias modificações em nosso código Terraform, mas omitimos a verificação de formatação
dos arquivos. d
Então, vamos formatar nossos arquivos e enviá-los ao Github, para que tudo esteja consistente, já
que essa é uma das melhores práticas recomendadas.

Execute os comandos abaixo para verificar quais arquvos precisam de formatação, formatá-lo e enviá-
los ao Github.

git status
terraform fmt -check
terraform fmt
git status
git diff

Page 54
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Page 55
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Fig. 33 – Status antes e após a formatação

a rk
a t e rm
u lt w
d ef a

Fig. 34 – Relatório de diferenças

git add .
git commit -m 'Terraform format'
git push

Prática 17 – Jamais armazene dados sensíveis nos arquivos


Terraform
Para gerenciar os recursos do seu ambiente, o Terraform precisa de suas credenciais. Esse é o tipo

Page 56
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

de informação sensível que deveria ser mantida segura e oculta o tempo todo. AWS access key e
secret key, por exemplo, jamais devem ser armazenadas em plain text nos arquivos Terraform, já que
os arquivos de estado são armazenados localmente em formato JSON não criptografado. Além disso,
outras pessoas com acesso ao seu repositório remoto podem ter acesso a esse tipo de informação.
Essa, inclusive, é uma das principais causas de compromentimento de contas em ambiente Cloud que
trazem milhões de dólares de prejuízo para as organizações.

Qualquer credencial de acesso ao ambiente cloud (ou onde você for utilizar o Terraform), além de
armazenada de maneira segura, deve ser rotacionada de tempos em tempos.

Também não devemos armazenar secrets (como por exemplo senhas de bancos de dados) no código
Terraform. Ao invés disso, devemos armazená-las em algum sistema de gerenciamento de secrets,
como HashiCorp Vault, AWS Secrets Manager e AWS Param Store antes de referenciá-las.

Dica de sucesso 17:

JAMAIS armazene informação sensível nos arquivos Terraform. Ao invés disso, utilize sistemas de
gerenciamento de credenciais como HashiCorp Vault, AWS Secrets Manager and AWS Param Store.

maneira legível no código Terraform. NÃO FAÇA ISSO! a rk


Como podemos ver no exemplo abaixo, usuário e senha do banco de dados estão armazenados de

a t e rm
lt w
resource "aws_db_instance" "my_example" {
u
engine = "mysql"
engine_version = "5.7"
d ef a
instance_class = "db.t3.micro"
name = "my-db-instance"
username = "admin" # DO NOT DO THIS!!!
password = "admin@123Password" # DO NOT DO THIS!!!

Ao invés de escrever informação sensível nos arquivos Terraform, utilize um gerenciador de


credenciais e referencíe-o.

resource "aws_db_instance" "my_example" {


engine = "mysql"
engine_version = "5.7"
instance_class = "db.t2.micro"
name = "my-db-instance"
# Let's assume you are using some secure mechanism
username = "<some secure mechanism like HashiCorp Vault, AWS Secrets Manager
password = "<some secure mechanism like HashiCorp Vault, AWS Secrets Manager
}

Vamos exemplificar a criação de um recurso seguro de senha, que será armazenada no AWS Secrets
Manager.

cat <<__EOF__>secrets.tf

Page 57
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

resource "random_password" "password" {


length = 16
special = false
override_special = "_%@"
}

resource "aws_ssm_parameter" "securestring_parameters" {


name = format("rdspassword-%s", terraform.workspace)
type = "SecureString"
value = random_password.password.result
key_id = "f339c8d3-0a9f-4b3f-9a99-5d94b1da013f"

lifecycle {
ignore_changes = [
value,
]
}
}

resource "aws_secretsmanager_secret" "secret_database" {


name = "tf-best-practices-secrets"
kms_key_id = "f339c8d3-0a9f-4b3f-9a99-5d94b1da013f"
recovery_window_in_days = 0
a rk
}

a t e rm
secret_id
u l w
resource "aws_secretsmanager_secret_version" "secret_value" {
t
= aws_secretsmanager_secret.secret_database.id

} ef a
secret_string = random_password.password.result
d
__EOF__

Como estamos utilizando um novo módulo de Terraform, o módulo random, temos que reinicializar
nosso .terraform. Execute os comandos a seguir.

terraform init -upgrade


terraform plan -var-file=test.tfvars
terraform apply -var-file=test.tfvars

Page 58
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 35 – Senha armazenada no Secrets Manager

Page 59
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Fig. 36 – Senha armazenada no Parameter Store

Prática 18 – Use terraform import

a rk
O comando terraform import permite trazer recursos que foram provisionados com outro método para

rm
a administração do Terraform. Essa é uma excelente técnica para migrar gradualmente a
t e
infraestrutura para o Terraform ou para garantir que você possa utilizar o Terraform no futuro. Para
a
u
um nome que o Terraform reconhecerá. l w
importar um recurso, você deve criar um bloco de recursos para ele em sua configuração e dar a ele
t
Dica de sucesso 18: d ef a
Mesmo que você tenha recursos provisionados manualmente, importe-os para o Terraform. Dessa
forma, você poderá usar o Terraform para gerenciar esses recursos no futuro e ao longo de seu ciclo
de vida.

Para este exercício vamos criar na AWS um recurso de maneira manual, que será depois importado
para o nosso Terraform.

aws ec2 create-vpc --cidr-block=10.20.30.0/24 --region=us-east-1

Page 60
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e
Fig. 37 – Criação de um recurso fora do Terraform rm
u lt w
d ef a

Fig. 38 – VPC criada.

Agora vamos criar nosso recurso no Terraform e importar o recurso.

cat <<__EOF__>vpc.tf
resource "aws_vpc" "vpc" {
cidr_block = "10.20.30.0/24"

tags = {
Name = "vpc-tf-best-practices"
}
}
__EOF__

Page 61
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

terraform import -var-file=test.tfvars aws_vpc.vpc vpc-01dda105eb41616ae

Fig. 38 – Recurso importado

Podemos notar que a VPC que criamos usando aws-cli não possui a tag name. Vamos executar o
nosso código Terraform e atualizar o recurso

a rk
terraform apply -var-file=test.tfvars
a t e rm
u lt w
d ef a

Fig. 39 – VPC gerenciada pelo Terraform

Prática 19 – Automatize seu deploy com CI/CD


O Terraform automatiza várias operações por conta própria. Mais especificamente, ele gera, modifica
e versiona seus recursos. O uso do Terraform no pipeline de Integração Contínua/Implantação
Contínua (CI/CD) pode melhorar o desempenho da sua organização e garantir implantações
consistentes, mesmo muitas equipes usá-lo localmente.

A execução do Terraform localmente implica que todas as dependências estejam em vigor: o


Terraform está instalado e disponível na máquina local e os provedores são mantidos no diretório
.terraform. Este não é o caso quando você migra para pipelines sem estado. Uma das soluções mais

Page 62
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

frequentes é usar uma imagem do Docker com um binário do Terraform.

O Terraform pode ser executado em um ambiente de contêiner com arquivos de configuração


montados como um volume do Docker após a construção do ambiente. As equipes de
desenvolvimento podem usar o fluxo de trabalho de integração contínua para automatizar, fazer
autoteste, produzir rapidamente, clonar e distribuir software. Você pode limitar o número de problemas
que ocorrem à medida que as implantações migram entre ambientes incorporando a criação e a
limpeza do ambiente em seus pipelines de CI/CD. Dessa forma, visto que sua infraestrutura está
documentada, sua equipe pode se comunicar, revisar e implantá-la utilizando pipelines automatizados
em vez de orquestração manual.

O Terraform define infraestrutura como código (IaC), portanto, não há motivo para não seguir as
melhores práticas de desenvolvimento de software. Validar as mudanças planejadas na infraestrutura,
testar a infraestrutura no início do processo de desenvolvimento e implementar a entrega contínua faz
tanto sentido para a infraestrutura quanto para o código do aplicativo. Em nossa opinião, a integração
Terraform e CI/CD é uma das melhores práticas obrigatórias do Terraform para manter sua
organização em funcionamento.

Em conjunto com Terraform Workspaces, um pipeline CI/CD com Terraform pode ser capaz de lançar
rk
a infraestrutura em um ambiente de testes, aguardar um retorno de testes automatizados e então
a
a t e rm
lançar a infraestrutura no ambiente produtivo, por exemplo.

u lt w
Por fim, como você armazenará o código do Terraform em sistemas Source Code Management (SCM)

d e a
ao implementar CI/CD. Aqui estão alguns pontos para ajudá-lo a decidir se você deve manter o código
f
do Terraform no mesmo repositório que o código do aplicativo ou em um local separado, como um
repositório de infraestrutura.

1. O Terraform e o código do aplicativo são combinados em uma unidade, o que facilita a


manutenção por uma única equipe.
2. Se você tem uma equipe de infraestrutura especializada, um repositório separado para
infraestrutura é mais conveniente, pois é um projeto independente.
3. Quando o código de infraestrutura é armazenado com o código do aplicativo, pode ser
necessário usar regras de pipeline adicionais para separar os gatilhos das seções de código.
Dito isso, em alguns casos, modificações no programa ou no código de infraestrutura acionarão
a implantação.

Dica de sucesso 19:

Decida se deseja armazenar a configuração do Terraform em um repositório separado ou combiná-la


com o código do aplicativo e ter um pipeline de CI/CD para criar a infraestrutura.

Prática 20 – Mantenha-se atualizado

Page 63
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

A comunidade de desenvolvimento do Terraform é bastante ativa e novas funções estão sendo


lançadas regularmente. Quando o Terraform lançar uma nova função importante, sugerimos que você
comece a trabalhar com a versão mais recente. Caso contrário, se você pular várias versões
principais, a atualização se tornará bastante difícil.

Dica de sucesso 20:

Sempre atualize sua versão e código quando major releases do Terraform forem lançadas.

Prefira uma instalação do Terraform através do seu gerenciador de pacotes predileto ou execute
sempre o comando terraform version. Se sua versão está desatualizada, um alerta será mostrado.

Prática 21 – Sempre fixe a versão do Terraform e do provider


O bloco terraform{} é usado para configurar comportamentos do próprio Terraform, como configurar o
Terraform Cloud, configurar um back-end do Terraform, especificar uma versão do Terraform
necessária e especificar os requisitos do provedor.

rk
Como a funcionalidade do provedor pode mudar com o tempo, pois cada plug-in de um provedor tem
a
a t rm
seu próprio conjunto de versões disponíveis, cada dependência de provedor que você definir deve ter
e
uma restrição de versão especificada no argumento version para que o Terraform possa escolher a

t w
versão compatível com seu código. Embora o Terraform aceite qualquer versão do provedor como
u l
d e a
compatível se o argumento version não estiver incluído (ele é opcional), recomendamos que você
f
forneça uma limitação de versão para cada provedor do qual seu módulo depende e especifique uma
versão do provedor como uma das as Melhores Práticas do Terraform.

Para a versão do Terraform, o mesmo vale. Para determinar quais versões do Terraform podem ser
usadas com sua configuração, o parâmetro required_version aceita uma string de restrição de versão.
Assim, se a versão atual do Terraform não estiver de acordo com as limitações estabelecidas, um erro
será gerado e o Terraform será encerrado sem realizar mais atividades. Portanto, definir a versão do
Terraform também é muito importante.

Dica de sucesso 21:

Sempre configure a versão de um provedor no required_providers e a versão do Terraform com


required_version no bloco de configurações terraform{}.

O código abaixo mostra um exemplo de configuração do Terrafom com a versão do provedor na


4.16.x e a versão do Terraform na 1.2.0

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}

Page 64
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

required_version = ">= 1.2.0"


}

Prática 22 – Valide seu código Terraform


O objetivo de construir uma infraestrutura como código (IaC) com o Terraform é gerenciá-la e implantá-
la com confiabilidade, utilizando as melhores práticas. Para identificar e resolver problemas o mais
cedo possível no processo de desenvolvimento, o comando terraform validate verifica os arquivos de
configuração em um diretório, referindo-se exclusivamente à sua configuração.

Independentemente de quaisquer variáveis especificadas ou estado atual, o processo de validação


executa verificações para garantir que uma configuração seja internamente coerente e sintaticamente
correta.

Portanto, recomendo que você desenvolva o hábito de executar o comando terraform validate com
frequência e antecedência ao criar suas configurações do Terraform, pois é mais rápido e requer
menos entradas do que executar um plano.

Dica de sucesso 22:

a rk
t rm
Sempre execute o comando terraform validate enquanto estiver escrevendo os arquivos Terraform e
e
transforme isso em um hábito para identificar e corrigir problemas o mais cedo possível em seu código.
a
Execute o seguinte comando:
u lt w
d ef a
sed -i "13d" main.tf

Esse comando vai apagar a linha 13 do nosso arquivo main.tf. Vamos validar o código.

terraform validate

Fig. 40 – Erro do terraform validate

As mensagens de erro do Terraform são bastante elucidativas e, em geral, dizem exatamente onde
está o problema e qual é ele. Neste caso, não há a chave fechando o bloco de configuração que
começa na linha 6 do main.tf (que foi removido quando apagamos a linha 13).

Use o git para fazer o arquivo voltar ao seu estado antes do sed aplicado a ele e valide novamente o

Page 65
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

código.

git stash
terraform validate

Fig. 41 – Validando um código íntegro

Prática 23 – Use Checkov para analisar seu código Terraform

a rk
As configurações incorretas e a falta de boas práticas durante o desenvolvimento dos modelos do

a t rm
Terraform usados para criar a infraestrutura representam sérias preocupações porque a segurança é
e
um componente essencial de todas as estruturas de arquitetura de nuvem. E é aqui que Checkov
entra em cena para salvar o dia.
u lt w
d ef a
Checkov é uma ferramenta de análise de código estático para verificar a infraestrutura como arquivos
de código (IaC) ou seus arquivos de configuração do Terraform quanto a erros de configuração que
podem causar problemas de segurança ou conformidade. Checkov tem mais de 750 regras pré-
configuradas para procurar problemas típicos de configuração incorreta. Depois de usar o Checkov
para escanear todo o código do Terraform, você poderá ver quais testes foram bem-sucedidos, quais
não foram e o que você pode fazer para corrigir os problemas.

Dica de sucesso 23:

Você deve testar seu código Terraform da mesma maneira que você testaria qualquer outro tipo de
código. É altamente recomendado utilizar ferramentas como o Checkov.

Vamos efetuar a instalação do Checkov, usando o método do pip3. Para outras formas de instalar,
consulte a documentação do produto.

sudo pip3 install checkov


checkov --version

Page 66
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Fig. 42 – Checkov instalado corretamente

Vamos agora construir dois blocos de configuração que serão avaliados pelo Checkov.

mkdir ~/test-checkov
cd ~/test-checkov

cat <<__EOF__>s3_compliant.tf
resource "aws_s3_bucket" "foo-bucket" {
region = var.region
bucket = local.bucket_name
force_destroy = true
a rk
tags = {
a t e rm
Name = format("foo-%s", data.aws_caller_identity.current.account_id)
}
u lt w
versioning {
enabled = true
d ef a
}
logging {
target_bucket = aws_s3_bucket.log_bucket.id
target_prefix = "log/"
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.mykey.arn
sse_algorithm = "aws:kms"
}
}
}
acl = "private"
}
__EOF__

checkov -d .

Page 67
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

a rk
a t e rm
u lt w
d ef a

Fig. 43 – Checkov analizando um código

Imaginemos agora que mudamos nosso código para que o bucket seja criado com acesso público.

cat <<__EOF__>s3_compliant.tf
resource "aws_s3_bucket" "foo-bucket" {
region = var.region
bucket = local.bucket_name
force_destroy = true

tags = {
Name = format("foo-%s", data.aws_caller_identity.current.account_id)
}
versioning {
enabled = true
}
logging {
target_bucket = aws_s3_bucket.log_bucket.id
target_prefix = "log/"
}

Page 68
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.mykey.arn
sse_algorithm = "aws:kms"
}
}
}
acl = "public"
}
__EOF__

E agora o resultado do Checkov nos mostra

a rk
a t e rm
u lt w
d ef a

Fig. 44 – Checkov detectando o problema

Como exercício, analise o código que temos no diretório tf-best-practices. Tente corrigir os problemas
encontrados.

Prática 24 – Use tflint para encontrar possíveis erros e reforçar

Page 69
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

o uso de melhores práticas


O TFLint é um linter (ferramenta para melhoria de código) que examina o código do Terraform em
busca de possíveis erros, práticas recomendadas, etc. Antes que ocorram erros durante a execução
do Terraform, ele também ajudará na identificação de problemas específicos do provedor. O TFLint
auxilia os principais provedores de nuvem na identificação de possíveis problemas, como tipos de
instância ilegais, alertas sobre sintaxe obsoleta ou declarações desnecessárias e impõe práticas
padrão e regras de nomenclatura. Portanto, é importante e recomendado testar seu código Terraform
o TFLint.

Dica de sucesso 24:

Para verificar possíveis erros no código Terraform e aplicar as práticas recomendadas, considere um
linter como o TFLint.

Para instalar o TFLint em Linux, podemos fazer

curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/inst

a rk
a t e rm
u lt w
d ef a

Fig. 45 – TFLint instalado

Vamos correr o TFLint em nosso código e ver que tipo de recomendação ele nos fornece.

cd ~/tf-best-practices
tflint

Page 70
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Fig. 46 – TFLint detectando problemas

a rk
rm
O TFLint detectou que não estamos seguindo a prática 21, de sempre fixar as versões dos nossos
provedores.
a t e
u
Prática 25 – Sempre utilizelt w
execution plan files
d e f a
O comando terraform plan cria um plano de execução, que permite visualizar as alterações que o
Terraform planeja fazer em sua infraestrutura. Por padrão, quando o Terraform cria um plano, ele:

1. Lê o estado atual de qualquer objeto remoto já existente para garantir que o estado do Terraform
esteja atualizado;
2. Compara a configuração atual com o estado anterior e observa quaisquer diferenças;
3. Propõe um conjunto de ações de mudança que devem, se aplicadas, fazer com que os objetos
remotos correspondam à configuração.

O comando plan sozinho não executa as alterações propostas. Você pode usar esse comando para
verificar se as alterações propostas correspondem ao que você esperava antes de aplicar as
alterações ou compartilhar suas alterações com sua equipe para uma revisão mais ampla.

Se você estiver usando o Terraform diretamente em um terminal e espera aplicar as alterações


propostas pelo Terraform, você pode, alternativamente, executar o terraform apply diretamente. Por
padrão, o comando apply gera automaticamente um novo plano e solicita sua aprovação.

Você pode usar a opção opcional -out=FILE para salvar o plano gerado em um arquivo no disco, que
pode ser executado posteriormente passando o arquivo para terraform apply como um argumento
extra. Este fluxo de trabalho de duas etapas destina-se principalmente ao executar o Terraform em um
pipeline de IaC. Utilizar um plano salvo em um arquivo garante que o que o plano especulativo
mostrou é o que será efetivamente executado pelo Terraform e criado em nossa infraestrutura.

Page 71
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Se você executar o plano terraform sem a opção -out=FILE, ele criará um plano especulativo, que é
uma descrição do efeito do plano, mas sem qualquer intenção de realmente aplicá-lo.

Recomendo que o arquivo que armazena o plano em disco seja nomeado como tfplan. O arquivo
.gitignore da Prática 2 já o contempla e evita que ele seja versionado e enviado para o repositório
remoto, já que esse arquivo pode conter informações sensíveis sobre nossa estrutura.

O comando terraform destroy, embora gere um plano de destruição, não aceita a opção -out=FILE.
Devemos usar o comando terraform plan com -out=FILE e -destroy.

Dica de sucesso 25:

Sempre crie um arquivo de plano de execução para ações de apply ou destroy. Um arquivo de plano
de execução garante que as mudanças validadas sejam as mesmas que serão aplicadas, além de
permitir que o plano de execução seja revisto e validado por outras pessoas.

Vamos criar um plano de execução, salvá-lo em disco e utilizá-lo para lançar nossa infraestrutura.

terraform plan -out=tfplan


a rk
rm
terraform apply tfplan

a t e
u lt w
d ef a

Fig. 47 – Terraform plan com arquivo em disco

Para destruir a infraestrutura criada, executamos os comandos abaixo.

terraform plan -out=tfplan -destroy


terraform apply tfplan

Conclusão
Escrever um código limpo de Terraform não é tão simples quanto parece, mas os benefícios do
aprendizado valem o esforço. Este artigo apresentou 25 práticas recomendadas do Terraform que
permitirão que você crie um código melhor sem esforço. Essas práticas recomendadas do Terraform
ajudarão você desde o momento em que você começar a escrever seu primeiro arquivo Terraform
para provisionar a infraestrutura em qualquer uma das plataformas de nuvem com suporte.

Seguir essas práticas recomendadas do Terraform garantirá que seu código do Terraform seja limpo e
legível e esteja disponível para outros membros da equipe em um sistema de gerenciamento de
código-fonte. Os membros de sua equipe poderão contribuir e reutilizar o mesmo código. Algumas

Page 72
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

dessas práticas, como o uso do Terraform Workspace e do Terraform Import, ajudarão você a
aproveitar os recursos do Terraform que podem ajudá-lo a implantar uma nova cópia da mesma
infraestrutura e importar a infraestrutura existente.

Em uma tentativa de agregar essas boas práticas descritas, tenho um repositório no Github que pode
ser utilizado como template de um projeto Terraform, já com remote storage para o state file em S3,
devidamente versionado, com locking configurado em DynamoDB. Para contribuir com esse código,
leia CONTRIBUTING.md.

Para entrar em contato com o autor, basta mandar um e-mail para mrbits@mrbits.com.br

Clean-up
Ao final deste artigo, vamos enviar eventuais modificações de código para o Github e eliminar
recursos que lançamos e que ainda estão em nossa conta AWS.

git status
git add .
git commit -m 'Final commit. End of course'
a rk
rm
git push
terraform destroy -var-file=test.tfvars
a t e
terraform workspace select dev
terraform destroy -var-file=dev.tfvars
u lt w
terraform workspace select uat
d ef a
terraform destroy -var-file=uat.tfvars
export REMOTE_STATE=tf-best-practices-ACCOUNTID-us-east-1
aws s3 rm $REMOTE_STATE --region=us-east-1 --recursive
aws s3api delete-objects \
--bucket $REMOTE_STATE \
--delete "$(aws s3api list-object-versions \
--bucket $REMOTE_STATE \
--output=json \
--query='{Objects: Versions[].{Key:Key,VersionId:VersionId}}')"
aws s3 rb s3://tf-best-practices-ACCOUNTID-us-east-1 --region=us-east-1

Sumário
Terraform Best
Num. Dica
Practices
Armazene seu código Sempre mantenha seu código versionado e armazenado
1
em um repositório Git em um repositório remoto.
Sempre tenha um arquivo .gitignore em seu repositório
2 Use .gitignore com as regras para evitar que arquivos desnecessários
sejam gerenciados pelo versionador.
Use uma estrutura de Use sempre estruturas consistentes de diretórios e
3
arquivos consistente arquivos, não importa o tamanho de seu projeto.

Page 73
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Sempre use terraform fmt -diff para verificar e formatar


Auto-formatação de
4 seus arquivos Terraform antes de enviá-los ao repositório
arquivos Terraform
remoto.
Evite valores hard- Sempre defina variáveis, atribua valores a elas e as use
5
coded onde necessitar.
Siga sempre uma
Defina normas e padrões de nomenclatura com seu time
6 convenção de
e siga-os o tempo todo.
nomenclatura
Use a variável self quando você precisa conhecer o valor
7 Use a variável self
de uma variável antes do deploy da infraestrutura.
Sempre use módulos. Você vai economizar muito tempo
8 Use módulos
de codificação. Não há necessidade de reinventar a roda.
Matenha múltiplos arquivos .tfvars com definição de
Execute terraform com variáveis, que podem ser informados aos comandos
9
-var-file terraform plan ou terraform apply através do argumento -
var-file.
Quando trabalhamos em um projeto junto a várias outras
Armazene o arquivo de
10 estado do Terraform
a rk
pessoas, devemos sempre usar backends Terraform que
em um storage remoto
t
compartilhado.
a rm
salvam o state file em um armazenamento remoto
e
11 lt w
Bloqueie o arquivo de Sempre use bloqueio de estado quando seu arquivo de
u
estado remoto
Faça cópias de d ef aestado estiver armazenado em um backend remoto.
Sempre habilite versionamento ou backup do estado
12 segurança do arquivo remoto do seu Terraform, para que você possa recuperá-
de estado lo em caso de acidente.
Manipule o arquivo de Sempre manipule o arquivo de estado do Terraform
13 estado somente pelo através do comando terraform e evite efetuar mudanças
comando terraform manuais no arquivo.
Gere um README
Você deve ter um README consistente e informativo em
14 para cada módulo
todos os seus módulos e projetos Terraform.
desenvolvido
Use funções built-in do Terraform para manipular valores
Use e abuse das
15 em seu código Terraform, executar operações
funções built-in
matemáticas, entre outras tarefas.
Use Terraform workspaces para criar múltiplos ambientes
como Dev, QA, UAT, Prod, entre outros, usando os
16 Use workspaces mesmos arquivos de configuração do Terraform e
salvando os arquivos de estado de cada ambiente no
mesmo backend remoto.
JAMAIS armazene informação sensível nos arquivos
Jamais armazene
Terraform. Ao invés disso, utilize sistemas de
17 dados sensíveis nos
gerenciamento de credenciais como HashiCorp Vault,
arquivos Terraform
AWS Secrets Manager and AWS Param Store.

Page 74
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email

Mesmo que você tenha recursos provisionados


manualmente, importe-os para o Terraform. Dessa forma,
18 Use terraform import
você poderá usar o Terraform para gerenciar esses
recursos no futuro e ao longo de seu ciclo de vida.
Decida se deseja armazenar a configuração do Terraform
Automatize seu deploy em um repositório separado ou combiná-la com o código
19
com CI/CD do aplicativo e ter um pipeline de CI/CD para criar a
infraestrutura.
Sempre atualize sua versão e código quando major
20 Mantenha-se atualizado
releases do Terraform forem lançadas.
Sempre fixe a versão Sempre configure a versão de um provedor no
21 do Terraform e do required_providers e a versão do Terraform com
Provider required_version no bloco de configurações terraform{}.
Sempre execute o comando terraform validate
Valide seu código enquanto estiver escrevendo os arquivos Terraform e
22
Terraform transforme isso em um hábito para identificar e corrigir
problemas o mais cedo possível em seu código.

Use Checkov para


a k
Você deve testar seu código Terraform da mesma
r
maneira que você testaria qualquer outro tipo de código.
23 analisar seu código
Terraform
a t e rm
É altamente recomendado utilizar ferramentas como o

u lt w
Checkov.

24
Use tflint para
d ef
encontrar possíveis a Para verificar possíveis erros no código Terraform e
aplicar as práticas recomendadas, considere um linter
erros e reforçar o uso
como o TFLint.
de melhores práticas
Sempre crie um arquivo de plano de execução para ações
de apply ou destroy. Um arquivo de plano de execução
Sempre utilize
25 garante que as mudanças validadas sejam as mesmas
execution plan files
que serão aplicadas, além de permitir que o plano de
execução seja revisto e validado por outras pessoas.

Category

1. IaC

Tags

1. AWS
2. Cloud
3. IaC
4. Terraform

Date Created
28/10/2022
Author
mrbits

Page 75
Footer Tagline

Você também pode gostar