Escolar Documentos
Profissional Documentos
Cultura Documentos
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.
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
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.
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
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>
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:
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.
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
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*
Page 5
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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
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
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
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
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.
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:
Page 11
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
a rk
a t e rm
u lt w
d ef a
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
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
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.
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__
terraform init
terraform plan
Page 14
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
terraform apply
Page 15
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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.
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
cat <<__EOF__>outputs.tf
output "instance_1_id" {
description = "The ID of the instance-1"
value = try(aws_instance.instance_1.id)
}
__EOF__
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
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
Page 18
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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
}
}
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__
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
Não se esqueça de mandar seu código para o Github e destruir os recursos lançados (usando
terraform destroy)
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:
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
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
===================================
cat <<__EOF__>modules/aws-s3-static-website-bucket/main.tf
resource "aws_s3_bucket" "s3_bucket" {
bucket_prefix = var.bucket_prefix
tags = var.tags
}
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"
}
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
}
}
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__
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
Page 27
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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__
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.
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.
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.
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
}
}
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
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
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.
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.
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.
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
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
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.
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
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.
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
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
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.
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.
a rk
a t e rm
u lt w
d ef a
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
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
git status
git add README.md modules/aws-s3-static-website-bucket/README.md
git commit -m "Always have a README"
git push
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.
Use funções built-in do Terraform para manipular valores em seu código Terraform, executar
operações matemáticas, entre outras tarefas.
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
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.
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
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")
}
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
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
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.
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).
Page 48
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
a rk
a t e rm
u lt w
d ef a
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")
}
Page 50
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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"
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
tags = {
Terraform = var.terraform
Environment = var.environment
}
}
module "website_s3_bucket_2" {
source = "./modules/aws-s3-static-website-bucket"
tags = {
Terraform = var.terraform
Environment = var.environment
}
}
__EOF__
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
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.
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
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
a rk
a t e rm
u lt w
d ef a
git add .
git commit -m 'Terraform format'
git push
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.
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.
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!!!
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
lifecycle {
ignore_changes = [
value,
]
}
}
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.
Page 58
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
a rk
a t e rm
u lt w
d ef a
Page 59
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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.
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
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
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
Page 62
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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.
Page 63
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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.
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.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
Page 64
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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.
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
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
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.
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.
Page 66
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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
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__
a rk
a t e rm
u lt w
d ef a
Como exercício, analise o código que temos no diretório tf-best-practices. Tente corrigir os problemas
encontrados.
Page 69
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
Para verificar possíveis erros no código Terraform e aplicar as práticas recomendadas, considere um
linter como o TFLint.
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/inst
a rk
a t e rm
u lt w
d ef a
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
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.
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.
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.
a t e
u lt w
d ef a
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
Page 74
Footer Tagline
COMPANY NAME
Address | Phone | Link | Email
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