>> Apresentação e considerações

Voltamos a falar de Terraform, se você não viu o nosso primeiro artigo falando sobre a teoria e instalação do Terraform convido a todos que leiam.

Conhecendo o Terraform

Nesse artigo vou mostrar um exemplo prático de Terraform na AWS, ele é baseado na versão “Terraform v0.9.11”.

Let’s go!!!

>> O que veremos nesse artigo?

Estou utilizando nesse artigo itens que entram no quesito FreeTier na AWS, ou seja, se você possuir uma conta na AWS e seguir esse tutorial você não será cobrado pelos seus testes, certo disso vamos ver como:

  • Provisionar uma instância EC2 na AWS
  • Criar as TAGS para a instância
  • Execução de UserData com script de instalação e start do Nginx
  • 2 Regras Security Group INBOUND, portas 80 e 22
  • 1 Regra de Security Group OUTBOUND para a instância ter acesso à internet

>> Pré requisitos

>> Clonando o repositório com o git

~$ git clone https://github.com/rdglinux/terraform-aws-churrops-project-one.git

~$ cd terraform-aws-churrops-project-one

Segue uma breve descrição de cada arquivo que foi baixado

  • provider.tf
    Informações de conexão com a AWS
  • main.tf
    Nesse arquivo declaramos as informações da instância que vamos criar
  • security-group.tf
    Security group que será criado e adicionado na instância
  • nginx.sh
    Sccript para ser utilizado no UserData que instala o NGINX
  • output.tf
    Aqui declaramos que o ip da instância será exibido após o lançamento
  • vars.tf
    O arquivo vars, é onde podemos definir as variáveis principais que serão utilizadas
    Basicamente para rodar esse Terraform você precisa setar os parâmetros: aws_access_key, aws_secret_key, key_name, sendo que as chaves você pode inserir em tempo de exeução
variable "aws_access_key" {}
variable "aws_secret_key" {}

variable "aws_region" {
  default = "us-east-1"
}

variable "amis" {
  type    = "map"
  default = {
    us-east-1 = "ami-a4c7edb2"
  }
}

variable "key_name" {
  default = "rdglinux-awskey-us"
}

variable "instance_type" {
  default = "t2.micro"
}

>> Vamos planejar a nossa Infraestrutura

O comando utilizando é o terraform plan, através dele vamos poder ver todos os itens que serão implantados em nosso Infra, baseado nas configurações ele monta um plano de execução e exibe os resultados sem de fato aplicar.

Da forma que está o nosso código, ao ser executado ele irá solicitar de forma interativa as chaves AWS para poder realizar o plano

~$ terraform init

~$ terraform plan

var.aws_access_key
  Enter a value: 

var.aws_secret_key
  Enter a value: 

Abaixo é o resultado do plan temos basicamente chave/valor, tudo o que está com o valor computed será criado em tempo de execução, ou seja, são valores que não foram definidos na nossa receita, os demais são valores que foram declarados.


Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_instance.churrops
    ami:                          "ami-a4c7edb2"
    associate_public_ip_address:  "<computed>"
    availability_zone:            "<computed>"
    ebs_block_device.#:           "<computed>"
    ephemeral_block_device.#:     "<computed>"
    instance_state:               "<computed>"
    instance_type:                "t2.micro"
    ipv6_address_count:           "<computed>"
    ipv6_addresses.#:             "<computed>"
    key_name:                     "rdglinux-awskey-us"
    network_interface.#:          "<computed>"
    network_interface_id:         "<computed>"
    placement_group:              "<computed>"
    primary_network_interface_id: "<computed>"
    private_dns:                  "<computed>"
    private_ip:                   "<computed>"
    public_dns:                   "<computed>"
    public_ip:                    "<computed>"
    root_block_device.#:          "<computed>"
    security_groups.#:            "1"
    security_groups.3344374217:   "sg_DefaultWebserver"
    source_dest_check:            "true"
    subnet_id:                    "<computed>"
    tags.%:                       "3"
    tags.Name:                    "churrops"
    tags.Provider:                "terraform"
    tags.Role:                    "test"
    tenancy:                      "<computed>"
    user_data:                    "37f0a6f40ea50e1784ae62ac96c55fe1aac34ac3"
    volume_tags.%:                "<computed>"
    vpc_security_group_ids.#:     "<computed>"

+ aws_security_group.sg_DefaultWebserver
    description:                           "Allow all outbound traffic and inbound 22/80"
    egress.#:                              "1"
    egress.482069346.cidr_blocks.#:        "1"
    egress.482069346.cidr_blocks.0:        "0.0.0.0/0"
    egress.482069346.from_port:            "0"
    egress.482069346.ipv6_cidr_blocks.#:   "0"
    egress.482069346.prefix_list_ids.#:    "0"
    egress.482069346.protocol:             "-1"
    egress.482069346.security_groups.#:    "0"
    egress.482069346.self:                 "false"
    egress.482069346.to_port:              "0"
    ingress.#:                             "2"
    ingress.2214680975.cidr_blocks.#:      "1"
    ingress.2214680975.cidr_blocks.0:      "0.0.0.0/0"
    ingress.2214680975.from_port:          "80"
    ingress.2214680975.ipv6_cidr_blocks.#: "0"
    ingress.2214680975.protocol:           "tcp"
    ingress.2214680975.security_groups.#:  "0"
    ingress.2214680975.self:               "false"
    ingress.2214680975.to_port:            "80"
    ingress.2541437006.cidr_blocks.#:      "1"
    ingress.2541437006.cidr_blocks.0:      "0.0.0.0/0"
    ingress.2541437006.from_port:          "22"
    ingress.2541437006.ipv6_cidr_blocks.#: "0"
    ingress.2541437006.protocol:           "tcp"
    ingress.2541437006.security_groups.#:  "0"
    ingress.2541437006.self:               "false"
    ingress.2541437006.to_port:            "22"
    name:                                  "sg_DefaultWebserver"
    owner_id:                              "<computed>"
    tags.%:                                "2"
    tags.Name:                             "sg_DefaultWebserver"
    tags.Provisioner:                      "terraform"
    vpc_id:                                "<computed>"

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

Ao final de tudo temos o resultado onde vemos que serão adicionados 2 recursos, que seria 1 instância EC2 e 1 Security Group, ou seja, ele entendo como os recursos de Infraestrutura.

>> Fazendo o build da Infraestrutura

Depois de utilizarmos o terraform plan vamos usar o terraform apply, estando tudo ok, ao executar o comando todos os recursos declarados serão criados na AWS

Na execução, novamente precisa informar as chaves

~$ terraform apply
var.aws_access_key
  Enter a value: AKIAIF334ROEKSYDTA

var.aws_secret_key
  Enter a value: qskeirjd85jfufjdjsk5jAwOD3CG6V4Nu/Brhf

Agora vamos acompanhar os recursos sendo criados.


aws_instance.churrops: Creating...
  ami:                          "" => "ami-a4c7edb2"
  associate_public_ip_address:  "" => "<computed>"
  availability_zone:            "" => "<computed>"
  ebs_block_device.#:           "" => "<computed>"
  ephemeral_block_device.#:     "" => "<computed>"
  instance_state:               "" => "<computed>"
  instance_type:                "" => "t2.micro"
  ipv6_address_count:           "" => "<computed>"
  ipv6_addresses.#:             "" => "<computed>"
  key_name:                     "" => "rdglinux-awskey-us"
  network_interface.#:          "" => "<computed>"
  network_interface_id:         "" => "<computed>"
  placement_group:              "" => "<computed>"
  primary_network_interface_id: "" => "<computed>"
  private_dns:                  "" => "<computed>"
  private_ip:                   "" => "<computed>"
  public_dns:                   "" => "<computed>"
  public_ip:                    "" => "<computed>"
  root_block_device.#:          "" => "<computed>"
  security_groups.#:            "" => "1"
  security_groups.3344374217:   "" => "sg_DefaultWebserver"
  source_dest_check:            "" => "true"
  subnet_id:                    "" => "<computed>"
  tags.%:                       "" => "3"
  tags.Name:                    "" => "churrops"
  tags.Provider:                "" => "terraform"
  tags.Role:                    "" => "test"
  tenancy:                      "" => "<computed>"
  user_data:                    "" => "37f0a6f40ea50e1784ae62ac96c55fe1aac34ac3"
  volume_tags.%:                "" => "<computed>"
  vpc_security_group_ids.#:     "" => "<computed>"
aws_security_group.sg_DefaultWebserver: Creating...
  description:                           "" => "Allow all outbound traffic and inbound 22/80"
  egress.#:                              "" => "1"
  egress.482069346.cidr_blocks.#:        "" => "1"
  egress.482069346.cidr_blocks.0:        "" => "0.0.0.0/0"
  egress.482069346.from_port:            "" => "0"
  egress.482069346.ipv6_cidr_blocks.#:   "" => "0"
  egress.482069346.prefix_list_ids.#:    "" => "0"
  egress.482069346.protocol:             "" => "-1"
  egress.482069346.security_groups.#:    "" => "0"
  egress.482069346.self:                 "" => "false"
  egress.482069346.to_port:              "" => "0"
  ingress.#:                             "" => "2"
  ingress.2214680975.cidr_blocks.#:      "" => "1"
  ingress.2214680975.cidr_blocks.0:      "" => "0.0.0.0/0"
  ingress.2214680975.from_port:          "" => "80"
  ingress.2214680975.ipv6_cidr_blocks.#: "" => "0"
  ingress.2214680975.protocol:           "" => "tcp"
  ingress.2214680975.security_groups.#:  "" => "0"
  ingress.2214680975.self:               "" => "false"
  ingress.2214680975.to_port:            "" => "80"
  ingress.2541437006.cidr_blocks.#:      "" => "1"
  ingress.2541437006.cidr_blocks.0:      "" => "0.0.0.0/0"
  ingress.2541437006.from_port:          "" => "22"
  ingress.2541437006.ipv6_cidr_blocks.#: "" => "0"
  ingress.2541437006.protocol:           "" => "tcp"
  ingress.2541437006.security_groups.#:  "" => "0"
  ingress.2541437006.self:               "" => "false"
  ingress.2541437006.to_port:            "" => "22"
  name:                                  "" => "sg_DefaultWebserver"
  owner_id:                              "" => "<computed>"
  tags.%:                                "" => "2"
  tags.Name:                             "" => "sg_DefaultWebserver"
  tags.Provisioner:                      "" => "terraform"
  vpc_id:                                "" => "<computed>"
aws_security_group.sg_DefaultWebserver: Creation complete (ID: sg-f26b8c82)
aws_instance.churrops: Still creating... (10s elapsed)
aws_instance.churrops: Still creating... (20s elapsed)
aws_instance.churrops: Still creating... (31s elapsed)
aws_instance.churrops: Still creating... (41s elapsed)
aws_instance.churrops: Creation complete (ID: i-010171b96e02657d5)

Apply complete! Resources: 2 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:

Outputs:

ip = 52.54.109.23

>> State

Ao final da execução o terraform cria um arquivo com o estado da Infraestrutura chamado: terraform.tfstate, esse arquivo é importante para que o terraform tenha controle das alterações, para mais informações:

https://www.terraform.io/docs/state/

>> terraform graph

O comando de graph é usado para gerar uma representação visual de uma configuração ou plano de execução. A saída está no formato DOT, que pode ser usado pelo GraphViz para gerar gráficos.

https://www.terraform.io/docs/commands/graph.html

~$ terraform graph
digraph {
	compound = "true"
	newrank = "true"
	subgraph "root" {
		"[root] aws_instance.churrops" [label = "aws_instance.churrops", shape = "box"]
		"[root] aws_security_group.sg_DefaultWebserver" [label = "aws_security_group.sg_DefaultWebserver", shape = "box"]
		"[root] provider.aws" [label = "provider.aws", shape = "diamond"]
		"[root] aws_instance.churrops" -> "[root] provider.aws"
		"[root] aws_instance.churrops" -> "[root] var.amis"
		"[root] aws_instance.churrops" -> "[root] var.instance_type"
		"[root] aws_instance.churrops" -> "[root] var.key_name"
		"[root] aws_security_group.sg_DefaultWebserver" -> "[root] provider.aws"
		"[root] meta.count-boundary (count boundary fixup)" -> "[root] aws_security_group.sg_DefaultWebserver"
		"[root] meta.count-boundary (count boundary fixup)" -> "[root] output.ip"
		"[root] output.ip" -> "[root] aws_instance.churrops"
		"[root] provider.aws (close)" -> "[root] aws_instance.churrops"
		"[root] provider.aws (close)" -> "[root] aws_security_group.sg_DefaultWebserver"
		"[root] provider.aws" -> "[root] var.aws_access_key"
		"[root] provider.aws" -> "[root] var.aws_region"
		"[root] provider.aws" -> "[root] var.aws_secret_key"
		"[root] root" -> "[root] meta.count-boundary (count boundary fixup)"
		"[root] root" -> "[root] provider.aws (close)"
	}
}

Utilizando um gerador de Graphiz online, podemos ver o resultado!

http://www.webgraphviz.com/

Seleção_041

>> terraform show

~$ terraform show

O comando terraform show é usado para fornecer saída legível por humanos de um arquivo de estado ou plano. Isso pode ser usado para inspecionar um plano para assegurar que as operações planejadas são esperadas, ou para inspecionar o estado atual como Terraform vê.

aws_instance.churrops:
  id = i-010171b96e02657d5
  ami = ami-a4c7edb2
  associate_public_ip_address = true
  availability_zone = us-east-1c
  disable_api_termination = false
  ebs_block_device.# = 0
  ebs_optimized = false
  ephemeral_block_device.# = 0
  iam_instance_profile =
  instance_state = running
  instance_type = t2.micro
  ipv6_addresses.# = 0
  key_name = rdglinux-awskey-us
  monitoring = false
  network_interface.# = 0
  network_interface_id = eni-5335dd86
  primary_network_interface_id = eni-5335dd86
  private_dns = ip-172-31-0-196.ec2.internal
  private_ip = 172.31.0.196
  public_dns = ec2-52-54-109-23.compute-1.amazonaws.com
  public_ip = 52.54.109.23
  root_block_device.# = 1
  root_block_device.0.delete_on_termination = true
  root_block_device.0.iops = 100
  root_block_device.0.volume_size = 8
  root_block_device.0.volume_type = gp2
  security_groups.# = 1
  security_groups.3344374217 = sg_DefaultWebserver
  source_dest_check = true
  subnet_id = subnet-fe0402b7
  tags.% = 3
  tags.Name = churrops
  tags.Provider = terraform
  tags.Role = test
  tenancy = default
  user_data = 37f0a6f40ea50e1784ae62ac96c55fe1aac34ac3
  volume_tags.% = 0
  vpc_security_group_ids.# = 0
aws_security_group.sg_DefaultWebserver:
  id = sg-f26b8c82
  description = Allow all outbound traffic and inbound 22/80
  egress.# = 1
  egress.482069346.cidr_blocks.# = 1
  egress.482069346.cidr_blocks.0 = 0.0.0.0/0
  egress.482069346.from_port = 0
  egress.482069346.ipv6_cidr_blocks.# = 0
  egress.482069346.prefix_list_ids.# = 0
  egress.482069346.protocol = -1
  egress.482069346.security_groups.# = 0
  egress.482069346.self = false
  egress.482069346.to_port = 0
  ingress.# = 2
  ingress.2214680975.cidr_blocks.# = 1
  ingress.2214680975.cidr_blocks.0 = 0.0.0.0/0
  ingress.2214680975.from_port = 80
  ingress.2214680975.ipv6_cidr_blocks.# = 0
  ingress.2214680975.protocol = tcp
  ingress.2214680975.security_groups.# = 0
  ingress.2214680975.self = false
  ingress.2214680975.to_port = 80
  ingress.2541437006.cidr_blocks.# = 1
  ingress.2541437006.cidr_blocks.0 = 0.0.0.0/0
  ingress.2541437006.from_port = 22
  ingress.2541437006.ipv6_cidr_blocks.# = 0
  ingress.2541437006.protocol = tcp
  ingress.2541437006.security_groups.# = 0
  ingress.2541437006.self = false
  ingress.2541437006.to_port = 22
  name = sg_DefaultWebserver
  owner_id = 745505968289
  tags.% = 2
  tags.Name = sg_DefaultWebserver
  tags.Provisioner = terraform
  vpc_id = vpc-bc2f4ada

Outputs:

ip = 52.54.109.23

>> Será que funcionou? Vamos validar

Foi passado um output na declaração onde o terraform nos devolveu o IP da nossa instância criada, ou seja, com o output podemos extrair informações dos recursos criados de forma simples

$ terraform output
ip = 52.54.109.23

Podemos ver no painel da AWS que a nossa instância foi criada conforme solicitamos

Captura de Tela 2017-08-02 às 23.42.47

Como utilizamos o UserData da AWS para a instalação do NGINX podemos abrir a página no Browser e validar que está acessível.

Captura de Tela 2017-08-02 às 23.41.32

SENSACIONAL, tudo funcionando, se chegou até aqui parabéns você acaba que criar uma Infraestrutura como código!

>> Destruindo tudo o que foi criado

Sucesso!!! já construimos a nossa Infra, validamos e podemos ver que realmente funciona, agora vamos destruir os recursos criados utilizando o próprio terraform, para isso utilizamos o terraform destroy

ATENÇÃO: MUITO CUIDADO ao usar o destroy, pois ele DESTROY DE VERDADE, se der esse comando quando não deveria, você pode ter sérios problemas

~$ 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: yes

var.aws_access_key
  Enter a value: 

var.aws_secret_key
  Enter a value:

Simples assim, abaixo podemos ver que os recursos estão sendo destruídos

aws_security_group.sg_DefaultWebserver: Refreshing state... (ID: sg-f26b8c82)
aws_instance.churrops: Refreshing state... (ID: i-010171b96e02657d5)
aws_security_group.sg_DefaultWebserver: Destroying... (ID: sg-f26b8c82)
aws_instance.churrops: Destroying... (ID: i-010171b96e02657d5)
aws_instance.churrops: Still destroying... (ID: i-010171b96e02657d5, 10s elapsed)
aws_security_group.sg_DefaultWebserver: Still destroying... (ID: sg-f26b8c82, 10s elapsed)
aws_instance.churrops: Still destroying... (ID: i-010171b96e02657d5, 20s elapsed)
aws_security_group.sg_DefaultWebserver: Still destroying... (ID: sg-f26b8c82, 20s elapsed)
aws_security_group.sg_DefaultWebserver: Still destroying... (ID: sg-f26b8c82, 30s elapsed)
aws_instance.churrops: Still destroying... (ID: i-010171b96e02657d5, 30s elapsed)
aws_instance.churrops: Still destroying... (ID: i-010171b96e02657d5, 40s elapsed)
aws_security_group.sg_DefaultWebserver: Still destroying... (ID: sg-f26b8c82, 40s elapsed)
aws_instance.churrops: Still destroying... (ID: i-010171b96e02657d5, 50s elapsed)
aws_security_group.sg_DefaultWebserver: Still destroying... (ID: sg-f26b8c82, 50s elapsed)
aws_security_group.sg_DefaultWebserver: Still destroying... (ID: sg-f26b8c82, 1m0s elapsed)
aws_instance.churrops: Still destroying... (ID: i-010171b96e02657d5, 1m0s elapsed)
aws_instance.churrops: Destruction complete
aws_security_group.sg_DefaultWebserver: Destruction complete

Destroy complete! Resources: 2 destroyed.

Ao final vemos que foram destruídos um total de 2 recursos!

E podemos olhar no painel da AWS que a instância foi terminada com sucesso!

Captura de Tela 2017-08-03 às 00.24.10

>> Conclusão

Bom, esse não foi um artigo fácil de se escrever, me custou bastante testes para validar o funcionamento de fato dos recursos criados, espero que assim como eu aprendi você possa também se beneficiar com esse tutorial.

O Terraform nos dá MUITAS, mais MUITAS possibilidades mesmo, aqui não tem as melhores práticas, ou as melhores formas de se utilizar, mais acredito que com esse Post já fica mais fácil na evolução ferramenta!

Obrigado a todos e não deixem de compartilhar!