Pular para conteúdo

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.

main.tf
    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.

provider.tf
    provider "aws" {
        region = "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.

versions.tf
    terraform {
        required_providers{
            aws = {
                version = "~> 4.67.0"
            }
        }
    }

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.

outputs.tf
    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.

variables.tf
    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.

elastic_beanstalk.tf
    resource "aws_elastic_beanstalk_application" "application" {
        name = var.app_name
    }
Enquanto no recurso 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.

elastic_beanstalk.tf
    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.

elastic_beanstalk.tf
    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:

elastic_beanstalk.tf
    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.

elastic_beanstalk.tf
    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.

elastic_beanstalk.tf
    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:

elastic_beanstalk.tf
    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:

elastic_beanstalk.tf
    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.

elastic_beanstalk.tf
    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

elastic_beanstalk.tf
    resource "aws_elastic_beanstalk_environment" "environment" {
        ...

        setting {
            name      = "ConfigDocument"
            namespace = "aws:elasticbeanstalk:healthreporting:system"
            value     = file("${path.module}/healthcheck.json")
        }
    }
Existem outras configurações que podem ser feitas no ambiente, sendo que a lista completa pode ser consultada aqui.

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.

healthcheck.json
    {
    "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.tf
    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.

variables.tf
    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.

s3.tf
    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.

outputs.tf
    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.

iam.tf
    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.

iam.tf
    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.

assumerole.json
{
    "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).

elastic_cache.tf
    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.

outputs.tf
    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.

variables.tf
    variable "security_groud_id" {
        type = string
        default = null
    }

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:

cloudwatch.tf
    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.

variables.tf
    variable "environment_name" {
        type = string
        default = null
    }

    variable "beanstalk_autoscaling_group" {
        type = string
        default = null
    }

    variable "cluster_id" {
        type = string
        default = null
    }

alarmes