Entendendo a infraestrutura
Estrutura do projeto
├───application
│ application.zip
│
└───terraform
│ main.tf
│ outputs.tf
│ provider.tf
│ variables.tf
│ versions.tf
│
│
├───cloudwatch
│ cloudwatch.tf
│ variables.tf
│
├───elastic_beanstalk
│ elastic_beanstalk.tf
│ healthcheck.json
│ outputs.tf
│ variables.tf
│
├───elastic_cache
│ elastic_cache.tf
│ outputs.tf
│ variables.tf
│
├───iam
│ assumerole.json
│ iam.tf
│
└───s3
outputs.tf
s3.tf
Raiz do projeto
main.tf
O arquivo main.tf
é o arquivo principal do projeto, nele são definidos os módulos que serão utilizados e as ligações entre eles.
module "elastic_beanstalk" {
source = "./elastic_beanstalk"
redis_endpoint = module.elastic_cache.redis_endpoint
bucket_id = module.s3.bucket_id
bucket_object_id = module.s3.bucket_object_id
}
module "elastic_cache" {
source = "./elastic_cache"
security_groud_id = module.elastic_beanstalk.allow_redis_id
}
module "s3" {
source = "./s3"
}
module "iam" {
source = "./iam"
}
module "cloudwatch" {
source = "./cloudwatch"
beanstalk_autoscaling_group = module.elastic_beanstalk.beanstalk_autoscaling_group
cluster_id = module.elastic_cache.cluster_id
environment_name = var.environment_name
}
Como é possível notar existem 5 módulos, sendo que cada um deles está em uma pasta diferente. Também é possível notar que alguns módulos recebem parâmetros, como por exemplo o módulo elastic_beanstalk
recebe os parâmetros redis_endpoint
, bucket_id
e bucket_object_id
. Esses parâmetros são definidos no arquivo variables.tf
de cada módulo, sendo que alguns deles também são definidos no arquivo variables.tf
do projeto principal para que a configuração possa ser feita na raiz do projeto.
provider.tf
No arquivo provider.tf
é definido o provedor que será utilizado, no caso o provedor da AWS na região us-east-1
.
versions.tf
No arquivo versions.tf
é definida a versão do Terraform que será utilizada. Nesse caso será utilizada a versão 4.67.0
, sendo que por ter o ~>
na frente, o Terraform só poderá usar versões superiores em que o número mais a direita seja maior do que 0, sendo os outros 2 números fixos.
outputs.tf
No arquivo outputs.tf
retornará no terminal o comando para fazer o deploy da aplicação. Infelizmente o Terraform não consegue realizar o deploy. Para isso é necessário utilizar o AWS CLI que irá atualizar o ambiente com a nova versão da aplicação.
output "aws_command" {
value = "aws elasticbeanstalk update-environment --application-name ${var.app_name} --version-label ${var.app_version} --environment-name ${var.environment_name}"
}
variables.tf
No arquivo variables.tf
serão definidas as variáveis que serão utilizadas no projeto. Nesse caso serão definidas as variáveis app_name
, app_version
, app_version_description
e environment_name
. Sendo o default
o valor que será passado para os módulos.
variable "environment_name" {
type = string
default = "beanstalk-environment"
}
variable "app_name" {
type = string
default = "app-beanstalk"
}
variable "app_version" {
type = string
default = "v1"
}
variable "app_version_description" {
type = string
default = "Flask application"
}
Módulos
elastic_beanstalk
O módulo elastic_beanstalk
é composto por 4 arquivos, sendo eles elastic_beanstalk.tf
, healthcheck.json
, outputs.tf
e variables.tf
. Nele são definidas as configurações para a criação do ambiente do Elastic Beanstalk.
Observação
Para que o Beanstalk se conecte com o Elastic Cache é necessário que estejam na mesma VPC. Para o projeto será utilizada a VPC padrão da AWS e portanto não será criada uma nova, mas dependendo da ocasião será necessário.
Observação
Para o projeto foram utilizados os valores padrões para o auto scaling group, que são de 1 a 4 instâncias, sendo o tipo de instância t3.micro
, portanto, com exceção do grupo de segurança para o Redis, não foram feitas outras configurações.
Observação
Para o projeto foram utilizados os valores padrões para o load balancer, portanto não foram feitas modificações.
elastic_beanstalk.tf
O elastic_beanstalk.tf
é o arquivo principal do módulo. Nele são definidos a aplicação e sua versão, o ambiente e suas configurações e o grupo de segurança que será necessário para conectar com o Elastic Cache.
- Aplicação e versão
No recurso aws_elastic_beanstalk_application
é definido o nome da aplicação que será criada.
resource "aws_elastic_beanstalk_application" "application" {
name = var.app_name
}
aws_elastic_beanstalk_application_version
é definido a versão da aplicação que será criada, bem como seus parâmetros como o nome da versão, sua descrição, o nome da aplicação no qual ela será criada e o bucket e o objeto que contém o código da aplicação.
resource "aws_elastic_beanstalk_application_version" "app_version" {
name = var.app_version
application = var.app_name
description = var.app_version_description
bucket = var.bucket_id
key = var.bucket_object_id
}
- Grupo de segurança
No recurso aws_security_group
é definido o grupo de segurança que será utilizado para conectar com o Elastic Cache. Sendo que serão permitidas as conexões de entrada na porta 6379
para o protocolo tcp
e todos os endereços. Como esse grupo de segurança será adicionada as instâncias, ou seja, o grupo de segurança padrão criado pelo Beanstalk não será afetado, não é necessário se preocupar com outras regras.
resource "aws_security_group" "allow_redis" {
name = "allow_redis"
ingress {
description = "Allow Redis"
from_port = 6379
to_port = 6379
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
- Ambiente
No recurso aws_elastic_beanstalk_environment
serão definidos o nome do ambiente, configurações para o ambiente, a aplicação que será utilizada e a plataforma que será utilizado nas instâncias. Nesse caso será utilizado a plataforma 64bit Amazon Linux 2023 v4.0.0 running Python 3.11
, já que a aplicação teste será feita em Flask que é um framework feito em Python. A lista completa com todas as plataformas disponíveis pode ser consultada aqui. O nome do ambiente, a aplicação que será utilizada e a plataforma que será utilizada nas instâncias foram definidas da seguinte forma:
resource "aws_elastic_beanstalk_environment" "environment" {
name = var.environment_name
application = aws_elastic_beanstalk_application.application.name
solution_stack_name = "64bit Amazon Linux 2023 v4.0.0 running Python 3.11"
...
}
Para as configurações foi definida uma variável de ambiente chamada REDIS_ENDPOINT
que será utilizada pela aplicação para se conectar com o Elastic Cache.
resource "aws_elastic_beanstalk_environment" "environment" {
...
setting {
namespace = "aws:elasticbeanstalk:application:environment"
name = "REDIS_ENDPOINT"
value = var.redis_endpoint
}
...
}
Outra configuração feita foi a do grupo de segurança para conectar com o Elastic Cache, citado anteriormente no tópico Grupo de segurança. Sendo atribuído ao autoscaling
nesse momento.
resource "aws_elastic_beanstalk_environment" "environment" {
...
setting {
namespace = "aws:autoscaling:launchconfiguration"
name = "SecurityGroups"
value = aws_security_group.allow_redis.name
}
...
}
E por fim as outras 4 configurações foram feitas para passar métricas do ambiente para o CloudWatch. Sendo a primeira StreamLogs
que permite fazer a transmissão dos logs do ambiente para o CloudWatch:
resource "aws_elastic_beanstalk_environment" "environment" {
...
setting {
name = "StreamLogs"
namespace = "aws:elasticbeanstalk:cloudwatch:logs"
value = "true"
}
...
}
A segunda HealthStreamingEnabled
que permite a transmissão de métricas de saúde do ambiente para o CloudWatch:
resource "aws_elastic_beanstalk_environment" "environment" {
...
setting {
name = "HealthStreamingEnabled"
namespace = "aws:elasticbeanstalk:cloudwatch:logs"
value = "true"
}
...
}
A terceira DeleteOnTerminate
que deleta os logs quando o ambiente é encerrado.
resource "aws_elastic_beanstalk_environment" "environment" {
...
setting {
name = "DeleteOnTerminate"
namespace = "aws:elasticbeanstalk:cloudwatch:logs"
value = "true"
}
...
}
Observação
Essa configuração é feita para que os logs não sejam armazenados, já que o objetivo é apenas testar o funcionamento do Elastic Beanstalk com o CloudWatch. Para cada ocasião é necessário analisar se essa configuração é necessária ou não.
E por fim a quarta ConfigDocument
que define o documento de configuração do CloudWatch que será utilizado para enviar métricas personalizadas para o CloudWatch. O documento de configuração é definido no arquivo healthcheck.json
resource "aws_elastic_beanstalk_environment" "environment" {
...
setting {
name = "ConfigDocument"
namespace = "aws:elasticbeanstalk:healthreporting:system"
value = file("${path.module}/healthcheck.json")
}
}
healthcheck.json
O arquivo healthcheck.json
define métricas do ambiente que por padrão não são enviadas para o CloudWatch. Sendo que foi definida a métrica ApplicationRequestsTotal
, com um tempo de 1 minuto. Essa métrica é definida para enviar o número de requisições que o ambiente recebeu no período de 1 minuto.
{
"CloudWatchMetrics":
{
"Environment":
{
"ApplicationRequestsTotal": 60
}
},
"Version":1
}
Observação
O tempo de 1 minuto foi definido apenas para testar o funcionamento do Elastic Beanstalk com o CloudWatch. Para cada ocasião é necessário analisar se esse tempo é adequado ou não.
Dica
Para saber mais sobre quais métricas podem ser obtidas, consulte a documentação.
output.tf
No arquivo output.tf
são definidas as saídas do módulo. Sendo que foi definida a saída do grupo de segurança (allow_redis_id
), que será utilizada para permitir a conexão com o Elastic Cache e o grupo de auto scaling (beanstalk_autoscaling_group
) para ser utilizado no CloudWatch
output "allow_redis_id" {
value = aws_security_group.allow_redis.id
}
output "beanstalk_autoscaling_group" {
value = aws_elastic_beanstalk_environment.environment.autoscaling_groups[0]
}
variables.tf
No arquivo variables.tf
são definidas as variáveis do módulo, sendo que foram definidas as variáveis environment_name
, redis_endpoint
, bucket_id
, bucket_object_id
, app_name
, app_version
e app_version_description
para configurar o beanstalk.
variable "redis_endpoint" {
type = string
default = null
}
variable "bucket_id" {
type = string
default = null
}
variable "bucket_object_id" {
type = string
default = null
}
variable "environment_name" {
type = string
default = "beanstalk-environment"
}
variable "app_name" {
type = string
default = "app-beanstalk"
}
variable "app_version" {
type = string
default = "v1"
}
variable "app_version_description" {
type = string
default = "Flask application"
}
s3
O módulo s3
é responsável por criar o bucket que será utilizado para armazenar o código da aplicação.
s3.tf
Para criar o bucket foi utilizado o recurso aws_s3_bucket
e definido o nome do bucket. Também foi utilizado o recurso aws_s3_object
para definir em key
o nome e o caminho do arquivo que será armazenado no S3 Bucket e em source
o caminho para o arquivo.
resource "aws_s3_bucket" "bucket_applicationversion" {
bucket = "bucket-applicationversion"
}
resource "aws_s3_object" "bucket_object" {
bucket = aws_s3_bucket.bucket_applicationversion.id
key = "application.zip"
source = "../application/application.zip"
}
outputs.tf
Para o outputs.tf
foram definidas as saídas do módulo, sendo as saídas bucket_id
e bucket_object_id
que serão utilizadas no módulo elastic_beanstalk
.
output "bucket_id" {
value = aws_s3_bucket.bucket_applicationversion.id
}
output "bucket_object_id" {
value = aws_s3_object.bucket_object.id
}
iam
O módulo iam
é responsável por criar as políticas e as funções que serão utilizadas pelo Elastic Beanstalk. Sem elas o Elastic Beanstalk não conseguiria executar as ações necessárias para o funcionamento da aplicação.
iam.tf
No arquivo iam.tf
é criada uma função IAM e é definida a política que será utilizada por ela. A política utilizada é a AWSElasticBeanstalkWebTier
, que é uma política gerenciada pela AWS e que permite que o Elastic Beanstalk execute as ações necessárias para o funcionamento da aplicação. E quem poderá assumir a função é definido no arquivo assumerole.json.
data "aws_iam_policy" "managed_policy" {
name = "AWSElasticBeanstalkWebTier"
}
resource "aws_iam_role" "beanstalk" {
name = "iam_for_beanstalk"
assume_role_policy = file("${path.module}/assumerole.json")
managed_policy_arns = [data.aws_iam_policy.managed_policy.arn]
}
Por fim essa função é associada a um perfil chamado aws-elasticbeanstalk-ec2-role
que controla as permissões que as instâncias EC2 lançadas pelo Elastic Beanstalk terão.
resource "aws_iam_instance_profile" "beanstalk_instance_profile" {
name = "aws-elasticbeanstalk-ec2-role"
role = aws_iam_role.beanstalk.name
}
assumerole.json
Define quem assume a função, se ela é permitida ou não e quais são as ações que podem ser executadas. Nesse caso a função pode ser assumida pelo serviço EC2 e a ação permitida é sts:AssumeRole
, sendo a versão da política a 2012-10-17
.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}]
}
elastic_cache
O módulo elastic_cache
é responsável por criar o Elastic Cache que será utilizado pela aplicação.
elastic_cache.tf
Foi criado o recurso aws_elasticache_cluster
para criar um cluster Redis do Elastic Cache. Nele é possível definir o id, a engine (Redis nesse caso), o tipo de nó (nesse caso cache.t2.micro
, para consultar todos os tipos de nós disponíveis consulte a documentação), o número de nós (nesse caso 1), a porta (foi utilizada a padrão 6379), o nome do grupo de parâmetros (nesse caso default.redis7
) e o id do grupo de segurança (foi utilizado o grupo de segurança criado no módulo do Beanstalk).
resource "aws_elasticache_cluster" "cluster" {
cluster_id = "cluster-example"
engine = "redis"
node_type = "cache.t2.micro"
num_cache_nodes = 1
parameter_group_name = "default.redis7"
port = 6379
security_group_ids = [var.security_groud_id]
}
outputs.tf
No arquivo de saídas foi definido o endpoint do Elastic Cache que será utilizado no módulo elastic_beanstalk
e o id do cluster para ser usado no módulo cloudwatch
.
output "redis_endpoint" {
value = aws_elasticache_cluster.cluster.cache_nodes.0.address
}
output "cluster_id"{
value = aws_elasticache_cluster.cluster.id
}
variables.tf
Foi definida a variável do grupo de segurança que foi definido no módulo elastic_beanstalk
.
cloudwatch
O módulo cloudwatch
é responsável por criar alarmes que serão utilizados para monitorar o Elastic Cache e o ambiente do Elastic Beanstalk. No total foram criados 10 alarmes.
cloudwatch.tf
Para a criação dos alarmes foi considerado um período de 1 minuto e a quantidade de períodos que o alarme deve ficar em estado de alarme para que o alarme seja acionado foi definido como 1.
Na tabela abaixo é possível ver os alarmes criados, o recurso que ele monitora, a métrica, o threshold (ponto de cortes), o operador (se é maior ou menor que o threshold) e a estatística (se é a média, soma, máximo ou mínimo). Para mais informações sobre configurações dos alarmes consulte a documentação.
Nome do alarme | Recurso | Métrica | Threshold | Operador | Estatística |
---|---|---|---|---|---|
beanstalk-requests | AWS/ElasticBeanstalk | ApplicationRequestsTotal | 5 | GreaterThanOrEqualToThreshold | Average |
beanstalk-environment-health | AWS/EnvironmentHealth | EnvironmentHealth | 1 | GreaterThanThreshold | Average |
beanstalk-cache-capacity | AWS/ElastiCache | DatabaseCapacityUsagePercentage | 75 | GreaterThanThreshold | Average |
beanstalk-cache-cpu | AWS/ElastiCache | CPUUtilization | 75 | GreaterThanThreshold | Average |
cache_network_in | AWS/ElastiCache | NetworkBytesIn | 2000000 (2MB) | GreaterThanThreshold | Sum |
cache_network_out | AWS/ElastiCache | NetworkBytesOut | 2000000 (2MB) | GreaterThanThreshold | Sum |
cache_misses | AWS/ElastiCache | CacheMisses | 5 | GreaterThanThreshold | Average |
beanstalk-cpu-auto-scaling | AWS/EC2 | CPUUtilization | 75 | GreaterThanThreshold | Average |
credit_balance_auto_scaling | AWS/EC2 | CPUCreditBalance | 10 | LessThanOrEqualToThreshold | Minimum |
credit_usage_auto_scaling | AWS/EC2 | CPUCreditUsage | 80 | GreaterThanThreshold | Maximum |
E para concluir a configuração, em dimensions
foi passado o recurso do qual ele irá obter a métrica. Tornando o código final assim:
resource "aws_cloudwatch_metric_alarm" "requests" {
alarm_name = "beanstalk-requests"
alarm_description = "This metric monitors application requests"
comparison_operator = "GreaterThanOrEqualToThreshold"
threshold = 5
evaluation_periods = 1
metric_name = "ApplicationRequestsTotal"
namespace = "AWS/ElasticBeanstalk"
period = 60
statistic = "Average"
dimensions = {
EnvironmentName = var.environment_name
}
}
resource "aws_cloudwatch_metric_alarm" "environment_health" {
alarm_name = "beanstalk-environment-health"
alarm_description = "This metric monitors environment health"
comparison_operator = "GreaterThanThreshold"
threshold = 1
evaluation_periods = 2
metric_name = "EnvironmentHealth"
namespace = "AWS/ElasticBeanstalk"
period = 60
statistic = "Average"
dimensions = {
EnvironmentName = var.environment_name
}
}
resource "aws_cloudwatch_metric_alarm" "cache_capacity" {
alarm_name = "beanstalk-cache-capacity"
alarm_description = "This metric monitors cache capacity"
comparison_operator = "GreaterThanThreshold"
threshold = 75
evaluation_periods = 1
metric_name = "DatabaseCapacityUsagePercentage"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Average"
dimensions = {
CacheClusterId = var.cluster_id
}
}
resource "aws_cloudwatch_metric_alarm" "cache_cpu" {
alarm_name = "beanstalk-cache-cpu"
alarm_description = "This metric monitors cache cpu usage"
comparison_operator = "GreaterThanThreshold"
threshold = 75
evaluation_periods = 1
metric_name = "CPUUtilization"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Average"
dimensions = {
CacheClusterId = var.cluster_id
}
}
resource "aws_cloudwatch_metric_alarm" "cache_network_in" {
alarm_name = "cache_network_in"
alarm_description = "This metric monitors cache network in"
comparison_operator = "GreaterThanThreshold"
threshold = 2000000 # 2MB
evaluation_periods = 1
metric_name = "NetworkBytesIn"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Sum"
dimensions = {
CacheClusterId = var.cluster_id
}
}
resource "aws_cloudwatch_metric_alarm" "cache_network_out" {
alarm_name = "cache_network_out"
alarm_description = "This metric monitors cache network out"
comparison_operator = "GreaterThanThreshold"
threshold = 2000000 # 2MB
evaluation_periods = 1
metric_name = "NetworkBytesOut"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Sum"
dimensions = {
CacheClusterId = var.cluster_id
}
}
resource "aws_cloudwatch_metric_alarm" "cache_misses" {
alarm_name = "cache_misses"
alarm_description = "This metric monitors cache misses"
comparison_operator = "GreaterThanThreshold"
threshold = 5
evaluation_periods = 1
metric_name = "CacheMisses"
namespace = "AWS/ElastiCache"
period = 60
statistic = "Average"
dimensions = {
CacheClusterId = var.cluster_id
}
}
resource "aws_cloudwatch_metric_alarm" "cpu_auto_scaling" {
alarm_name = "beanstalk-cpu-auto-scaling"
alarm_description = "This metric monitors cpu usage auto scaling"
comparison_operator = "GreaterThanThreshold"
threshold = 75
evaluation_periods = 1
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 60
statistic = "Average"
dimensions = {
AutoScalingGroupName = var.beanstalk_autoscaling_group
}
}
resource "aws_cloudwatch_metric_alarm" "credit_balance_auto_scaling" {
alarm_name = "credit_balance_auto_scaling"
alarm_description = "This metric monitors credit balance auto scaling"
comparison_operator = "LessThanOrEqualToThreshold"
threshold = 10
evaluation_periods = 1
metric_name = "CPUCreditBalance"
namespace = "AWS/EC2"
period = 60
statistic = "Minimum"
dimensions = {
AutoScalingGroupName = var.beanstalk_autoscaling_group
}
}
resource "aws_cloudwatch_metric_alarm" "credit_usage_auto_scaling" {
alarm_name = "credit_usage_auto_scaling"
alarm_description = "This metric monitors credit usage auto scaling"
comparison_operator = "GreaterThanThreshold"
threshold = 80
evaluation_periods = 1
metric_name = "CPUCreditUsage"
namespace = "AWS/EC2"
period = 60
statistic = "Maximum"
dimensions = {
AutoScalingGroupName = var.beanstalk_autoscaling_group
}
}
variables.tf
E por fim em variables.tf
foram adicionadas as variáveis que foram utilizadas para configurar os alarmes, uma para cada recurso.