Alta Disponibilidade e Escalabilidade com Load Balancer e Auto Scaling na AWS usando Terraform
Published: 2023-08-10 19:00:19

Publicar uma aplicação não é uma tarefa simples, dentre as incontáveis etapas que vão desde o projeto de engenharia de software até o desenvolvimento do código e os inúmeros casos de testes, ainda é necessário pensar na disponibilização para o usuário final, e é neste ponto que entra o tema que será abordado nesse post: Alta disponibilidade.
Através da AWS (Amazon Web Services) é possível encontrar variados recursos com os mais diversos casos de uso, nesse post estaremos falando sobre dois deles: o ELB (Elastic Load Balancer) e o ASG (Auto Scaling Group), enquanto também demonstramos a implementação de uma infraestrutura que utiliza esses recursos via Terraform. A seguir, apresentamos o diagrama do projeto proposto:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4wi2st2s2d8pb326v640.png)
Para o provisionamento do ELB e do ASG, precisamos primeiramente criar alguns recursos que serão fundamentais para o funcionamento da infraestrutura, são eles: a criação de uma EC2 (Elastic Compute Cloud), disponibilização de uma VPC (Virtual Private Cloud), e a criação de uma AMI (Amazon Machine Image) que será usada pelo ASG.
Vamos iniciar pelo arquivo main.tf, onde iremos criar uma instância EC2 e uma AMI derivada dessa mesma instância.

```
provider "aws" {
  region = var.region
}

#Cria a instancia EC2
resource "aws_instance" "terraform" {
  ami                         = var.ami
  instance_type               = var.instance_type
  subnet_id                   = aws_subnet.public_subnet.id
  vpc_security_group_ids      = [aws_security_group.ec2_sg.id]
  associate_public_ip_address = true
  tags = {
    Name = "${var.nome_instancia}"
  }

}

resource "aws_ebs_snapshot" "ec2_snapshot" {
  volume_id = aws_instance.terraform.root_block_device[0].volume_id
}

#Cria uma AMI com base na EC2 criada anteriormente
resource "aws_ami" "ami_app" {
  name                = var.nome-ami
  description         = var.descricao-ami
  root_device_name    = var.root-device-ami
  virtualization_type = var.tipo-virtualizacao

  ebs_block_device {
    device_name = var.root-device-ami
    snapshot_id = aws_ebs_snapshot.ec2_snapshot.id
    volume_size = 10
    delete_on_termination = true
  }

  tags = {
    Name = var.nome-ami
  }
}

#Associa um IP elastico a uma instancia
resource "aws_eip" "eip" {
  instance = aws_instance.terraform.id
}

#Exibe o IP publico associado
output "IP" {
  value = aws_eip.eip.public_ip

}
```

Logo em seguida criamos o variables.tf,que conterá as seguintes linhas:

```
variable "region" {
  type        = string
  description = "Regiao"
  default     = "us-east-1"

}

variable "ami" {
  type        = string
  description = "Imagem"
  default     = "ami-053b0d53c279acc90"
}

variable "nome_instancia" {
  type        = string
  description = "Nome da instancia"
  default     = "terraform-app"

}


variable "nome-ami" {
  type        = string
  description = "AMI gerada pelo recurso aws_ami "
  default     = "ami-app-db"
}

variable "descricao-ami" {
  type        = string
  description = "AMI criada a partir da EC2 "
  default     = "AMI criada a partir da EC2"
}

variable "root-device-ami" {
  type        = string
  description = "Disco raiz da AMI"
  default     = "/dev/sda1"
}

variable "tipo-virtualizacao" {
  type        = string
  description = "Tipo de virtualizacao"
  default     = "hvm"
}

variable "instance_type" {
  type        = string
  description = "Tamanho instancia"
  default     = "t2.micro"

}

variable "lb_type" {
  type        = string
  description = "Tipo de lb"
  default     = "application"

}
```
A seguir, criaremos o arquivovpc.tf, que será a rede responsável por alocar todos os nossos recursos criados, juntamente com seus respectivos grupos de segurança:

```
#Cria a VPC
resource "aws_vpc" "apptf_vpc" {
  cidr_block = "10.0.0.0/16" # Bloco de endereços IP da VPC

  tags = {
    Name = "tf-vpc"
  }
}

#Cria uma subnet pública
resource "aws_subnet" "public_subnet" {
  vpc_id     = aws_vpc.apptf_vpc.id
  cidr_block = "10.0.1.0/24" # Bloco de endereços IP da subnet
  availability_zone = "us-east-1a"
  tags = {
    Name = "public_subnet_a"
  }
}

#Cria uma subnet pública
resource "aws_subnet" "public_subnet_b" {
  vpc_id     = aws_vpc.apptf_vpc.id
  cidr_block = "10.0.2.0/24" # Bloco de endereços IP da subnet
  availability_zone = "us-east-1b"
  tags = {
    Name = "public_subnet_b"
  }
}

#Cria um grupo de segurança para a subnet pública
resource "aws_security_group" "public_security_group" {
  name_prefix = "public_security_group"
  vpc_id      = aws_vpc.apptf_vpc.id


   ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "public_security_group"
  }
}

#Cria um grupo de segurança para o ELB
resource "aws_security_group" "elb_sg" {
  name        = "elb_sg"
  description = "Acesso a partir do ELB"
  vpc_id      = aws_vpc.apptf_vpc.id

   ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "elb_sg"
  }
}


#Cria um grupo de segurança para o ASG
resource "aws_security_group" "asg_sg" {
  name        = "asg_sg"
  description = "Grupo de Seguranca do ASG"
  vpc_id      = aws_vpc.apptf_vpc.id

    ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "asg_sg"
  }
}

resource "aws_security_group" "ec2_sg" {
  name        = "ec2_sg"
  description = "Acesso"
  vpc_id      = aws_vpc.apptf_vpc.id

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "ec2_sg"
  }
}

#Cria uma subnet privada
resource "aws_subnet" "private_subnet" {
  vpc_id     = aws_vpc.apptf_vpc.id
  cidr_block = "10.0.3.0/24" # Bloco de endereços IP da subnet
  availability_zone = "us-east-1a"
  tags = {
    Name = "private_subnet_a"
  }
}

#Cria uma segunda subnet privada
resource "aws_subnet" "private_subnet_b" {
  vpc_id     = aws_vpc.apptf_vpc.id
  cidr_block = "10.0.4.0/24" # Bloco de endereços IP da subnet
  availability_zone = "us-east-1b"
  tags = {
    Name = "private_subnet_b"
  }
}

# Cria um gateway de internet para a VPC
resource "aws_internet_gateway" "gateway_internet" {
  vpc_id = aws_vpc.apptf_vpc.id

  tags = {
    Name = "gateway_internet"
  }
}

#Cria uma rota para permitir o tráfego da subnet pública para a Internet
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.apptf_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gateway_internet.id
  }

  tags = {
    Name = "public_route_table"
  }
}

#Associa a subnet pública com a tabela de rotas pública
resource "aws_route_table_association" "public_association" {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_route_table.id
}

#Associa a subnet pública B com a tabela de rotas pública
resource "aws_route_table_association" "public_association_b" {
  subnet_id      = aws_subnet.public_subnet_b.id
  route_table_id = aws_route_table.public_route_table.id
}
```
Vamos agora à criação das peças centrais deste post: o ELB e o ASG. Começaremos pelo elb.tfque será o responsável por fazer o balanceamento das requisições entre as instancias nas AZs (Availability Zones) configuradas, criaremos então o arquivo elb.tf:

```
resource "aws_lb" "tf_alb" {
  name               = "apprds-lb-tf"
  internal           = false
  load_balancer_type = var.lb_type
  security_groups    = [aws_security_group.elb_sg.id]
  subnets            = [aws_subnet.public_subnet.id, aws_subnet.public_subnet_b.id]  # IDs das sub-redes

  tags = {
    Name = "app-db-alb"
  }
}

#Cria o target group do ALB
resource "aws_lb_target_group" "alb_target" {
  name     = "target-group"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.apptf_vpc.id 

  health_check {
    path                = "/"
    interval            = 30
    timeout             = 10
    healthy_threshold   = 3
    unhealthy_threshold = 3
  }
}

#Associa a instancia ao target group do ALB
resource "aws_lb_target_group_attachment" "intance_attachment" {
  target_group_arn = aws_lb_target_group.alb_target.arn
  target_id        = aws_instance.terraform.id
  port             = 80
}


#Cria o listener group do ALB
resource "aws_lb_listener" "alb_listener" {
  load_balancer_arn = aws_lb.tf_alb.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    target_group_arn = aws_lb_target_group.alb_target.arn
    type             = "forward"
  }
}

#Exibe o endpoint do LB
output "elb_dns_name" {
  value = aws_lb.tf_alb.dns_name
}
```
Agora, por fim, prosseguimos com a criação do asg.tf , que basicamente será o responsável pela escalabilidade da aplicação, adicionando e removendo as instancias conforme a necessidade:

```
#Configuracao do launch template
resource "aws_launch_template" "lt_asg" {
  name_prefix   = "launch-template"
  image_id      = aws_ami.ami_app.id 
  instance_type = var.instance_type 
  vpc_security_group_ids = [aws_security_group.ec2_sg.id]
}

#Cria o recurso do ASG
resource "aws_autoscaling_group" "asg" {
  name             = "asg"
  min_size         = 2  
  max_size         = 4  
  desired_capacity = 2  
  health_check_type = "ELB"
  vpc_zone_identifier = [aws_subnet.public_subnet.id, aws_subnet.public_subnet_b.id] 


  launch_template {
    id      = aws_launch_template.lt_asg.id
    version = "$Latest"
  }

    tag {
    key                 = "Name"
    value               = "asg-terraform"
    propagate_at_launch = true
  }
}

#Cria uma associação do ASG com o ALB
resource "aws_autoscaling_attachment" "attachment_alb_asg" {
  autoscaling_group_name = aws_autoscaling_group.asg.id
  lb_target_group_arn    = aws_lb_target_group.alb_target.arn
}
```
Após a adição dos recursos acima, agora vamos aplicar os recusos na AWS, iremos então executar alguns comandos, são eles:

```
terraform init # Usado para inicializar o Terraform
```

```
terraform plan # Usado para visualizar as mudanças propostas pela configuração Terraform
```
E por fim o:

```
terraform apply # Usado para criar a infraestrutura na AWS
```
Dessa forma, teremos uma infraestrutura altamente disponível e escalável na AWS, capaz de garantir a continuidade operacional, minimizar interrupções e assegurar o acesso à aplicação, mesmo diante de falhas de hardware ou software.

**Principais benefícios da utilização do Load Balancer e do Auto Scaling:**

- **Elasticidade:** O ASG permite o dimensionamento automático dos recursos provisionados conforme a demanda, aumentando em momentos de picos de tráfego e diminuindo durante períodos de baixa demanda. Isso possibilita a otimização de recursos e a redução de custos.
- **Alta Disponibilidade:** Por meio das zonas de disponibilidade aplicadas ao ELB, é possível manter a aplicação operacional mesmo que um data center completo da AWS esteja inacessível.
- **Tolerância a falhas:** Em caso de falha de uma instância, o ASG substitui automaticamente por uma nova, garantindo a disponibilidade contínua da aplicação.
- **Distribuição de Tráfego:** O ELB realiza a distribuição de tráfego entre as instancias de maneira automática, assim evitando uma possível sobrecarga de um único servidor e uma potencial indisponibilidade da aplicação.

Por fim, conforme apresentado, pudemos acompanhar na prática a implementação de uma infraestrutura altamente disponível na AWS. Nesse contexto, utilizamos o Terraform, que tem se mostrado como uma ferramenta poderosa e cada vez mais essencial para um deploy eficaz e ágil, nesse caso, na nuvem da AWS.

**Link do projeto:**

{% embed https://github.com/lucasgabriel-y/medium-elb-asg %}