Terraform Begins

Dê os primeiros passos no mundo da infrastructure as code!

Terraform é uma ferramenta open source que te permite definir infraestrutura de vários cloud providers (como AWS, Google Cloud, Azure, entre outros) através de uma interessante linguagem declarativa. Além disso podemos usá-la para fazer deploy a administrar essa infraestrutura pela linha de comando.

Nesse post vou mostrar como fazer o deploy de um servidor rodando uma aplicação simples. A documentação do Terraform faz uma excelente apresentação da ferramenta e mostra como instalá-la, então vá lá, instale e depois volte aqui! Eu também presumo que você já tenha algum conhecimento sobre Amazon AWS. Para executar o exemplo, não esqueça de configurar suas credenciais através de variáveis de ambiente ou do arquivo ~/.aws/credentials, conforme o link da documentação.

O código do Terraform é escrito numa linguagem declarativa chamada HCL, em arquivos com a extensão .tf. O que você tem que fazer é descrever a infraestrutura com os recursos que você quer e o Terraform vai magicamente criá-la para você. Primeiramente, vamos declarar o provider que vamos utilizar. Crie um arquivo main.tf e dentro dele coloque:

provider "aws" {
region = "eu-west-1"
}

Aqui dizemos para o Terraform que vamos usar o AWS e que essa infra será criada na região "eu-west-1" (Irlanda). Dependendo de cada provider há uma grande gama de recursos diponíveis como servidores, load balancers, bancos de dados e por aí vai. Nesse exemplo vamos precisar de uma EC2 instance. Para criar essa instância, adicione ao main.tf:

resource "aws_instance" "servidor" {
ami = "ami-9398d3e0"
instance_type = "t2.micro"
}

Para usar um recurso precisamos especificar seu tipo (aws_instance), seu nome (servidor) e as configurações específicas daquele recurso. Você pode ver a lista completa de parâmetros na documentação. Por enquanto só vamos usar os obrigatórios:

  • ami: É a Amazon Machine Image que vai ser instalada na nossa EC2 Instance. No exemplo estamos usando um Ubuntu Server 16.04.
  • instance_type: O tipo da EC2 Instance. Os tipos de instâncias disponíveis variam bastante em suas configurações, mas para fins de aprendizado, vamos com a t2.micro por que é de graça :)

Vá para o terminal e rode o comando terraform plan:

> terraform plan
Refreshing Terraform state in-memory prior to plan...
(...)
+ aws_instance.servidor
ami: "ami-9398d3e0"
associate_public_ip_address: "<computed>"
availability_zone: "<computed>"
ebs_block_device.#: "<computed>"
ephemeral_block_device.#: "<computed>"
instance_state: "<computed>"
instance_type: "t2.micro"
key_name: "<computed>"
network_interface_id: "<computed>"
placement_group: "<computed>"
private_dns: "<computed>"
private_ip: "<computed>"
public_dns: "<computed>"
public_ip: "<computed>"
root_block_device.#: "<computed>"
security_groups.#: "<computed>"
source_dest_check: "true"
subnet_id: "<computed>"
tenancy: "<computed>"
vpc_security_group_ids.#: "<computed>"
Plan: 1 to add, 0 to change, 0 to destroy.

Esse comando mostra o planejamento do Terraform, nada é executado com ele, mas assim temos uma forma prática de verificar as mudanças antes de efetivamente alterar nossa infra. A saída gerada nos mostra os recursos que serão criados marcando-os com o sinal (+), os que serão removidos com o sinal (-) e os que serão alterados com o (~). No nosso exemplo, só uma instância EC2 será criada.

Para efetivamente criar a instância, execute o comando terraform apply:

> terraform apply
aws_instance.servidor: Creating...
ami: "" => "ami-9398d3e0"
associate_public_ip_address: "" => "<computed>"
availability_zone: "" => "<computed>"
ebs_block_device.#: "" => "<computed>"
ephemeral_block_device.#: "" => "<computed>"
instance_state: "" => "<computed>"
instance_type: "" => "t2.micro"
key_name: "" => "<computed>"
network_interface_id: "" => "<computed>"
placement_group: "" => "<computed>"
private_dns: "" => "<computed>"
private_ip: "" => "<computed>"
public_dns: "" => "<computed>"
public_ip: "" => "<computed>"
root_block_device.#: "" => "<computed>"
security_groups.#: "" => "<computed>"
source_dest_check: "" => "true"
subnet_id: "" => "<computed>"
tenancy: "" => "<computed>"
vpc_security_group_ids.#: "" => "<computed>"
aws_instance.servidor: Still creating... (10s elapsed)
aws_instance.servidor: Still creating... (20s elapsed)
aws_instance.servidor: Still creating... (30s elapsed)
aws_instance.servidor: Creation complete
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate

Parabéns, você acaba de criar um servidor com Terraform! Você pode verificar que ele foi criado logando na sua conta:

Muito legal, mas nossa instância ainda não faz nada. E agora?

Colocando a instância pra fazer alguma coisa

Vamos rodar um web server e expor nosso trabalho para o mundo:

#!/bin/bash
echo "Na na nan na nan nan na BATMAN!!!" > index.html
nohup python -m SimpleHTTPServer 8080 &

Aqui temos um bash script que cria um arquivo index.html a roda um web server na porta 8080 usando python (que já vem instalado no ubuntu por padrão). O comando é executado junto com o nohup para garantir que o web server continua rodando mesmo depois da saída do script, e adiciona um & no final para executar o processo em backgroud.

Para executar esse script, vamos adicioná-lo como parte do User Data do EC2, o qual será executado pelo AWS na inicialização da instância:

resource "aws_instance" "servidor" {
ami = "ami-9398d3e0"
instance_type = "t2.micro"
  user_data = <<-EOF
#!/bin/bash
echo "Na na nan na nan nan nan BATMAN!!!" > index.html
nohup python -m SimpleHTTPServer 8080 &
EOF
}

O EOF é sintaxe do Terraform, leia maiores detalhes aqui.

Por padrão o AWS não permite acesso à uma instância, então precisamos de mais um recurso, vamos criar um Security Group que vai permitir acesso pela porta 8080:

resource "aws_security_group" "servidorsg" {
name = "terraform-security-group"
  ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}

Esse código cria o security group (pense nele como um firewall) e define a permissão de acesso via TCP na porta 8080. O CIDR block é uma forma de definir o range de ips os quais o SG permite o acesso, nesse caso, de qualquer lugar. Isso feito, vamos indicar para a nossa instância que ela tem de usar o Security Group criado, passando o seu id para a instância:

resource "aws_instance" "servidor" {
ami = "ami-9398d3e0"
instance_type = "t2.micro"
  vpc_security_group_ids = ["${aws_security_group.servidorsg.id}"]
  ...
}

No Terraform cada recurso possui um conjunto de atributos que podem ser referenciados, através da sintaxe “${tipo.nome.atributo}”. Você vai encontrar tudo o que precisa saber sobre os atributos na documentação de cada recurso.

Ao fazer essa referência entre os recursos, estamos criando uma referência implícita. Nada tema, como dito no começo, o Terraform magicamente vai descobrir a ordem correta em que os recursos devem ser criados.

E já que estamos trabalhando com uma ferramenta tão poderosa, nem precisamos olhar no console do AWS para descobrir qual é o ip da nossa instâcia recém criada. Vamos definir uma variável de saída (ou output variable):

output "public_ip" {
value = "${aws_instance.servidor.public_ip}"
}

Usando aquela sintax já citada, estamos referenciando o atributo public_ip de um recurso aws_instance. Sobre o output, entrarei em maiores detalhes em futuros posts. Vamos executar terraform apply novamente, e dessa vez a saída sera algo como:

> terraform apply
aws_instance.servidor: Refreshing state... (ID: i-0dbc3b2d645e4318f)
aws_security_group.servidorsg: Creating...
description: "" => "Managed by Terraform"
egress.#: "" => "<computed>"
ingress.#: "" => "1"
ingress.516175195.cidr_blocks.#: "" => "1"
ingress.516175195.cidr_blocks.0: "" => "0.0.0.0/0"
ingress.516175195.from_port: "" => "8080"
ingress.516175195.protocol: "" => "tcp"
ingress.516175195.security_groups.#: "" => "0"
ingress.516175195.self: "" => "false"
ingress.516175195.to_port: "" => "8080"
name: "" => "terraform-security-group"
owner_id: "" => "<computed>"
vpc_id: "" => "<computed>"
aws_security_group.servidorsg: Creation complete
aws_instance.servidor: Modifying...
vpc_security_group_ids.1609934486: "" => "sg-1df7bc7b"
vpc_security_group_ids.681070395: "sg-4de49f2a" => ""
aws_instance.servidor: Modifications complete
Apply complete! Resources: 1 added, 1 changed, 0 destroyed.
Outputs:
public_ip = 52.214.110.59

Um recurso foi adicionado (security group) e a instância foi alterada, já que a referência ao sg foi adicionada, exatamente o que era esperado. Por fim, temos o ip público à disposição, espere alguns instantes para que o AWS possa terminar de subir a instância e tente acessar aquele ip na porta 8080 via curl (ou pelo browser mesmo):

> curl http://52.214.110.59:8080/
Na na nan na nan nan nan BATMAN!!!

Sucesso! Um servidor rodando uma verdadeira (mais ou menos) aplicação! Claro que é a mais simples possível, mas não tenha pressa pequeno gafanhoto, esse é a apenas o primeiro passo na longa jornada de aprendizado dessa poderosa ferramenta. Há muitos aspectos que não estou considerando nessa introdução, em um deploy real temos que considerar a criação de uma VPC, Subnets, Rounting tables, e recursos interessantes como Auto Scaling Groups. Futuros posts tratarão disso!

IMPORTANTE! LIMPE A BAGUNÇA!

Assim que terminar seus estudos, lembre de remover todos os recursos criados, você não quer que a Amazon te cobre por eles. Acredite. Removê-los é bem fácil, apenas execute o comando terraform destroy:

> terraform destroy
Do you really want to destroy?
Terraform will delete all your managed infrastructure.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value:

Digite yes e o Terraform vai encontrar todos os recursos que ele criou e removê-los em alguns instantes.

Concluindo

Agora você tem uma noção de como a ferramenta funciona. Espero que esse post desperte seu interesse pelo Terraform e pela infrastructure as code. O que você leu aqui é só a ponta do iceberg, ainda vou escrever muito sobre essa ferramenta, desde exemplos mais complexos até o uso de Docker e outros cloud providers.

Você pode ver o código do exemplo completo no meu Github.

Gostou do post? Curta, compartilhe e deixe sua opinião nos comentários!