Este será um guia prático de como prover uma instância na AWS juntamente com um contêiner Docker rodando Super Mário. Caso deseje reproduzir este ambiente você vai precisar apenas de uma conta na AWS e você ainda pode utilizar o nível gratuito para isso.

image

Para começar vamos fazer precisar de dois recursos, o próprio Terraform e o AWS CLI.

Instalação Terraform -https://learn.hashicorp.com/terraform/getting-started/install.html

Instalação AWS CLI
MacOS - https://docs.aws.amazon.com/pt_br/cli/latest/userguide/install-macos.html
Linux - https://docs.aws.amazon.com/pt_br/cli/latest/userguide/install-linux.html
Windows - https://docs.aws.amazon.com/pt_br/cli/latest/userguide/install-windows.html

Configurações AWS

Vamos criar um usuário e usar suas credenciais de segurança para criar um profile no AWS CLI

image

Ao final da criação do usuário salve o ID da Chave de acesso e também a Chave de acesso Secreta. Você também pode fazer o download do CSV com estas informações.

image

Criando Profile na AWS CLI

Para configurar as credenciais de segurança do usuário que acabamos de criar basta rodar o seguinte comando no seu terminal e inserir as informações respectivas.

anderovsk@anderovsk$ aws configure --profile super_mario
AWS Access Key ID [None]:  Seu ID da Chave de acesso
AWS Secret Access Key [None]: Sua chave de acesso secreta
Default region name [None]: us-east-2
Default output format [None]: json

Com os comandos abaixo você pode verificar o profile que você acabou de criar. O caminho do arquivo credentials pode variar de acordo com seu sistema operacional.

cat ~/.aws/credentials

Iniciando com Terraform

Em um diretório de sua escolha, crie dois novos diretórios, resources e scripts.

Os primeiros arquivos que vamos criar serão dentro do diretório resources. São eles, main.tf e variables.tf.

anderovsk@anderovsk:super-mario$ tree
.
├── resources
│   ├── main.tf
│   └── variables.tf
└── scripts

No arquivo main.tf vamos colocar algumas informações principais

provider "aws" {
  profile    = "super_mario"
  region     = "${var.aws_region}"
}

Provider: O nosso provedor de cloud que no exemplo é a AWS
Profile: O profile que criamos anteriormente no AWS CLI com as credenciais de segurança do nosso usuário.
Region: Aqui informando a região que será usada na AWS.

Perceba que o valor do parâmetro region foi colocado o valor de uma variável e é aqui que começamos a usar as variáveis do Terraform.

No arquivo de variables.tf comece inserindo os seguintes parâmetros.

variable "aws_region" {
  description = "Regiao para a VPC"
  default = "us-east-2"
}

Variable: Nome da variável.
Description: não preciso comentar…
Default: É o valor padrão para aquela variável (variáveis podem ter mais de um valor mas não abordaremos aqui)

Vamos inserir as outras variáveis que usaremos futuramente, segue no arquivo com todas as variáveis que usaremos.

 
variable "aws_region" {
  description = "Regiao para a VPC"
  default = "us-east-2"
}

variable "aws_az" {
  description = "Availability Zone para todos os servicos"
  default = "us-east-2a"
}

variable "key_name" {
  description = "The key name to use for the instance"
  default     = "super_mario_key"
}

variable "vpc_cidr" {
  description = "CIDR for the VPC"
  default = "10.0.0.0/16"
}

variable "public_subnet_cidr" {
  description = "CIDR for the public subnet"
  default = "10.0.1.0/24"
}

Não se preocupe em copiar este código, no final do artigo vou disponibilizar tudo em um repositório GitHub.

Criando VPC

No diretório de resources vamos criar o arquivos vpc.tf com o seguinte conteúdo.

# Define a VPC
resource "aws_vpc" "vpc_super_mario" {
  cidr_block = "${var.vpc_cidr}"
  enable_dns_hostnames = true
  tags = {
    Name = "vpc_super_mario"
  }
}

Resource: Aqui existem dois valores, o tipo do recurso da AWS e o nome dele respectivamente (Este é o padrão de definição de todos os recursos da AWS no Terraform).
Cidr_block: Variável com a faixa de IP interno dos recursos na nossa VPC.
Tags: Apenas criamos uma tag “Name” com o nome da nossa VPC.

Subnet

Ainda no diretório resources crie o arquivo subnet.tf.

# Define a subnet
resource "aws_subnet" "subnet_publica" {
  vpc_id = "${aws_vpc.vpc_super_mario.id}"
  cidr_block = "${var.public_subnet_cidr}"
  availability_zone = "${var.aws_az}"

  tags = {
    Name = "super-subnet"
  }
}

Neste momento eu acredito que vocês já estão familiarizados com a definição de recurso que é a mesma sempre (resources “tipo do recurso” “nome do recurso”) e aqui na Subnet e nos outros recursos não será diferente.

A diferença aqui é apenas na definição na variável vpc_id que referencia um recurso existente e não uma variável criada no arquivo com essa finalidade. Nesta variável apenas definimos o ID da VPC que criamos anteriormente.

Security Group

No security group iremos criar as regras de entrada “Ingress” e saída “Egress” que serão utilizadas na nossa VPC.

 
# Define o security group
resource "aws_security_group" "mario_sg" {
  name = "mario_sg"
  description = "Permite conexao HTTP e acesso via SSH"
  
  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks =  ["0.0.0.0/0"]
  }
  egress {
    from_port       = 0
    to_port         = 0
    protocol        = "-1"
    cidr_blocks     = ["0.0.0.0/0"]
  }

  vpc_id="${aws_vpc.vpc_super_mario.id}"
}

Acredito que apenas o protocolo de saída e o cidr_bloks que necessitam de explicação neste recurso.

O valor do protocolo de saída está como “-1” pois significa que poderemos utilizar todos os protocolos a partir da nossa VPC diferente do protocolo de ingress que é apenas TCP.

O cidr_bloks está com o valor “0.0.0.0/0” pois indica que a fonte de comunicação pode ser qualquer faixa de IP.

Internet Gateway

Neste recurso apenas criamos o Internet Gateway para acessar a internet a partir da nossa instância.
Bastas criar o arquivo internet_gatway.tf na pasta de resources.

resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.vpc_super_mario.id}"
}

Route Table

Acabamos de criar o nosso Internet Gateway mas ele ainda não está associado a nossa subnet. Para fazer esta associação nos vamos precisar do Route Table criando o arquivo de nome route_table.tf no diretório resources.

Primeiramente criaremos o recurso de route_tables e associaremos ao nosso internet_gateway.

# Define a route table
resource "aws_route_table" "public-rt" {
  vpc_id = "${aws_vpc.vpc_super_mario.id}"
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }

  tags = {
    Name = "Public Subnet RT"
  }
}

Agora associamos o Route Table a nossa Subnet

# Associa a route table a subnet
resource "aws_route_table_association" "public-rt" {
  subnet_id = "${aws_subnet.subnet_publica.id}"
  route_table_id = "${aws_route_table.public-rt.id}"
}

Segue o arquivo completo:

# Define a route table
resource "aws_route_table" "public-rt" {
  vpc_id = "${aws_vpc.vpc_super_mario.id}"
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }

  tags = {
    Name = "Public Subnet RT"
  }
}

# Associa a route table a subnet
resource "aws_route_table_association" "public-rt" {
  subnet_id = "${aws_subnet.subnet_publica.id}"
  route_table_id = "${aws_route_table.public-rt.id}"
}

Finalmente criando a nossa Instância

O primeiro recurso que criaremos aqui é um data source para filtrar a AMI do ubuntu.

Arquivo ec2.tf

#AMI
data "aws_ami" "ubuntu" {
  most_recent = true
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20190627.1"]
  }
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
  owners = ["099720109477"] # Canonical
}

Agora vamos criar a nossa instância referenciando alguns recursos já criados como variáveis.

#Super_mario
resource "aws_instance" "super_mario_instace" {
  ami           = "${data.aws_ami.ubuntu.id}"
  availability_zone = "${var.aws_az}"
  instance_type = "t2.micro"
  key_name = "${var.key_name}"
  vpc_security_group_ids = ["${aws_security_group.mario_sg.id}"]
  subnet_id = "${aws_subnet.subnet_publica.id}"
  associate_public_ip_address = true
  tags = {
      Name = "Super_mario"
  }
  user_data = "${file("../scripts/super_mario_docker.sh")}"
}

Neste recurso tem duas coisas diferentes que precisamos explicar aqui.

O primeiro é Key Name que pode ser usado para acessar a nossa instância via SSH. Este recurso precisa ser criado no console da AWS utilizando o mesmo nome que colocamos no nosso arquivo de variáveis super_mario_key.

image

Apenas pela organização salve esta chave no diretório de resources.

O último e mais importante recurso que iremos definir é o User Data. Este recurso é responsável por executar alguns comandos em nossa instância no momento em que ela é iniciada.

No nosso caso iremos referenciar um arquivo de script que irá instalar o Docker e executar um contêiner onde irá rodar nosso game.

Segue o Script que iremos usar: Arquivo super_mario_docker.sh

#!/bin/bash
apt-get update -y
echo "Instalando Docker"
curl -fsSL https://get.docker.com | sh
docker run -d -p 80:8080 pengbai/docker-supermario

Créditos ao autor da imagem: https://github.com/PengBAI/mariohtml5

Abaixo a estrutura de todos os arquivos que criamos

anderovsk@anderovsk:super_mario$ tree
.
├── resources
│   ├── ec2.tf
│   ├── internet_gatway.tf
│   ├── main.tf
│   ├── route_table.tf
│   ├── security_group.tf
│   ├── subnet.tf
│   ├── variables.tf
│   └── vpc.tf
└── scripts
    └── super_mario_docker.sh

2 directories, 9 files

Criando nossos recursos na AWS com Terraform

Agora que já criamos todos os recursos necessário iremos rodar o seguinte comando para iniciar a construção dos recursos terraform

Dentro do diretório de resources, rode: terraform init

A saída do comando deve ser algo parecido com…

anderovsk@anderovsk:resources$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.57.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.57"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Utilizamos o comando abaixo para validar nossos recursos declarados e visualizar todos que devem ser criados ou removidos. terraform plan

A saída deste comando é bem extensa mas basicamente ele informa o caractere “+” para os recursos que devem ser criados e no final do arquivo ele mostra a quantidade de recursos que declaramos.

  + resource "aws_vpc" "vpc_super_mario" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = true
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "vpc_super_mario"
        }
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

anderovsk@anderovsk:resources$

Agora vamos realmente criar nossa infra com o seguinte comando. terraform apply

Este comando deve lhe pedir uma confirmação antes de realmente executar:

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

Agora você pode verificar no console da AWS que nossa instância já foi criada.

image

Você pode pegar o DNS ou o IP dessa instância e jogar a partir do seu navegador.

image

Após algumas horas de jogo você pode destruir toda aquela essa infra estrutura com apenas um comando.

terraform destroy

Este comando também requer confirmação.

Agora você pode criar e destruir essa infraestrutura de maneira mais fácil e sem perder tempo.

O Terraform salva o status da sua infra estrutura em alguns arquivos então é bom tomar cuidado com estes arquivos para que não seja necessário nenhuma ação manual no console da AWS.

Perceba que foram criados os arquivos:

terraform.tfstate 
terraform.tfstate.backup

Então é isso Senhoras e Senhores…

Os arquivos criados neste artigo estão todos comitados no seguinte repositório: https://github.com/Anderon-lima/Terraform-Docker-Super_mario