Criando um template YAML do CloudFormation do zero

Rodolfo Costa
7 min readJun 28, 2020

Ser um desenvolvedor web atualmente não é uma tarefa fácil. Milhares de ferramentas e tecnologias foram criadas para nos ajudar a desenvolver software mais rapidamente e eficientemente, obtendo resultados em um tempo cada vez menor.

Entretanto, no mundo web, elaborar um código enxuto e funcional não é suficiente para ter seu devido reconhecimento se você não conseguir subir a aplicação e executá-la em um servidor web para que outras pessoas possam testá-la e usá-la. É nesse ponto que os desenvolvedores normalmente sofrem.

Diferentes soluções em nuvem existem para tornar essa etapa menos complexa como a AWS e Azure. Porém, elas demandam um certo entendimento de infraestrutura e dos seus serviços, o que adiciona uma outra curva de aprendizado no processo como um todo.

Devido ao fato de estarem entre os primeiros a oferecer IaaS e PaaS, AWS é amplamente utilizada e é a escolhida na maioria das empresas. Provisionamento de recursos de computador sob demanda é mais barato e escalável comparado aos famosos Data Centers.

Recentemente minha equipe foi encarregada de migrar as nossas aplicações para o ambiente da AWS. Nós queríamos automatizar todo o processo de implantação o máximo possível e, para isso, precisaríamos primeiro encontrar uma forma de automatizar o processo de provisionamento dos recursos da aplicação.

E é nesse momento que o AWS CloudFormation entra em jogo.

AWS CloudFormation é um serviço que “permite usar linguagens de programação ou um arquivo de texto simples para modelar e provisionar de forma automática e segura todos os recursos necessários para os aplicativos em todas as regiões e contas”.

Exatamente o que precisamos.

Em uma tentativa de ajudar outros desenvolvedores, esse artigo dá uma pincelada sobre a criação de um template do CloudFormation bem como fornece um arquivo template base pronto para ser usado.

Botando a mão na massa

Portanto, vamos diretamente ao nosso template. Ele descreve uma pilha de software, que é basicamente uma coleção de recursos AWS que podem ser gerenciados como um único elemento. Em nossa pilha, nós definimos nossos recursos com base nos seguintes serviços:

  • AWS VPC: Permite a definição de uma infraestrutura de rede em uma seção isolada da nuvem AWS.
  • AWS EC2: Permite a definição da capacidade computacional na nuvem de maneira escalável.
  • AWS Elastic Beanstalk: Permite a implantação de uma aplicação com balanceamento de carga e escalabilidade automáticos.
  • AWS CloudWatch: Permite o monitoramento de aplicações por meio da coleta de dados de monitoramento e operações na forma de logs, métricas e eventos.

Para cada recurso, nós definimos configurações que podem ser pré-existentes (já foram criadas pelo console AWS ou pela Interface de Linha de Comando AWS) ou não. Para configurações que não definidas no template, AWS cria uma configuração padrão com valores padrão como, por exemplo, um Security Group, que é um recurso que define regras de entrada e saída de tráfego de rede para uma instância EC2).

O template é dividido em duas seções básicas: Parameters (Parâmetros) e Resources (Recursos). Os parâmetros definem valores a serem usados pela pilha e pelo ambiente da aplicação. Os recursos definem as soluções constituintes da pilha. Essa divisão foi feita assim para facilitar a reutilização do template para múltiplas aplicações.

Quero código!

Aqui está o código para a seção Parameters (Parâmetros).

Parameters:
AppEnvironment:
Type: String
Default: production
EC2InstanceType:
Type: String
Default: a1.medium
AccountVpc:
Type: AWS::EC2::VPC::Id
Description: Account VPC
Default: vpc-id
EC2Subnet:
Type: AWS::EC2::Subnet::Id
Description: Subnet subnet-1
Default: subnet-1-id
LoadBalancerSubnet:
Type: AWS::EC2::Subnet::Id
Description: Subnet subnet-1
Default: subnet-1-id
LoadBalancerVisibility:
Type: String
Default: public

AppEnvironment é uma variável de ambiente para a nossa aplicação. Nesse caso, nós definimos apenas um ambiente com uma única variável de ambiente para nossa aplicação, mas é possível definir n ambientes com n variáveis de ambiente.

EC2InstanceType define qual será o tipo de instância nossa instância EC2. Uma lista de tipos de instância disponíveis pode ser vista em https://aws.amazon.com/ec2/instance-types/.

No nosso caso, a infraestrutura de rede já está definida e, por isso, nós apenas precisamos dizer qual VPC e quais Subnets queremos usar (para ter uma aplicação pública ou privada por exemplo). Então, AccountVpc, EC2Subnet e LoadBalancerSubnet receberão apenas IDs. Esses IDs podem ser obtidos no menu “Serviços” > “VPC” no console AWS.

LoadBalancerVisibility pode ser definido com os valores public (público) ou internal (interno) de acordo com a subnet escolhida para LoadBalancerSubnet (se ela for pública ou privada). Isso define se a aplicação será alcançável pela internet (pública) ou não (interna).

Todos esses valores de parâmetros serão utilizados na seção Resources (Recursos).

Resources:
CloudWatchLogsRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ["ec2.amazonaws.com"]
Action: ["sts:AssumeRole"]
Policies:
- PolicyName: AppLogsPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:GetLogEvents",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:FilterLogEvents",
"logs:PutRetentionPolicy"
]
Resource: "*" InstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
InstanceProfileName: !Sub ${App}-${AppEnvironment}-CloudWatch
Roles:
- !Ref CloudWatchLogsRole LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/elasticbeanstalk/${App}-${AppEnvironment}/var/log/eb-docker/containers/eb-current-app/stdouterr.log
RetentionInDays: 7 App:
Type: "AWS::ElasticBeanstalk::Application"
Properties:
ApplicationName: app-name
Description: Application description AppVersion:
Type: "AWS::ElasticBeanstalk::ApplicationVersion"
Properties:
ApplicationName: !Ref App
Description: String
SourceBundle:
S3Bucket: s3-bucket-name
S3Key: s3-key-name.zip AppEnvironment:
Type: "AWS::ElasticBeanstalk::Environment"
Properties:
EnvironmentName: !Sub ${App}-${AppEnvironment}
ApplicationName: !Ref App
SolutionStackName: "64bit Amazon Linux 2018.03 v2.15.1 running Docker 19.03.6-ce"
Tier:
Name: WebServer
Type: Standard
Version: "1.0"
VersionLabel: !Ref AppVersion
OptionSettings:
- Namespace: aws:elasticbeanstalk:application:environment
OptionName: APP_ENV
Value: !Ref AppEnvironment
- Namespace: aws:elasticbeanstalk:command
OptionName: DeploymentPolicy
Value: RollingWithAdditionalBatch
- Namespace: aws:elasticbeanstalk:command
OptionName: BatchSizeType
Value: Fixed
- Namespace: aws:elasticbeanstalk:command
OptionName: BatchSize
Value: 1
- Namespace: aws:ec2:vpc
OptionName: VPCId
Value: !Ref AccountVpc
- Namespace: aws:ec2:vpc
OptionName: Subnets
Value: !Ref EC2Subnet
- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: InstanceType
Value: !Ref EC2InstanceType
- Namespace: aws:elasticbeanstalk:environment
OptionName: EnvironmentType
Value: LoadBalanced
- Namespace: "aws:elasticbeanstalk:environment"
OptionName: LoadBalancerType
Value: network
- Namespace: "aws:ec2:vpc"
OptionName: ELBSubnets
Value: !Ref LoadBalancerSubnet
- Namespace: "aws:ec2:vpc"
OptionName: ELBScheme
Value: !Sub ${LoadBalancerVisibility}
- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: IamInstanceProfile
Value: !Ref InstanceProfile
- Namespace: aws:elasticbeanstalk:cloudwatch:logs
OptionName: StreamLogs
Value: true

Eita! Esse trecho é um pouquinho grande. Vamos analisar parte a parte.

CloudWatchLogsRole define um IAM Role (Perfil) com Policies (Permissões) para habilitar integração com o serviço de logs do CloudWatch. Essas permissões possibilidade o uso de Log Groups, Log Streams e Log Retention. O Log Stream é uma sequência de eventos de log. O Log Group é um grupo de Log Streams que possuem a mesma retenção (Log Retention). Log Retention representa a data de expiração dos logs. Exemplos de permissões podem ser encontrados aqui: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-identity-based-access-control-cwl.html.

InstanceProfile anexa as informações definidas em CloudWatchLogsRole a nossa instância EC2 para que a integração com o CloudWatch Logs ocorra. Uma instância EC2 pode ter múltiplos perfis de instância e, por isso, deve-se elaborar um nome bem claro e definido para cada um, visto que essa é a única forma de identificá-los posteriormente.

LogGroup define a partir de qual tipo de log iremos criar um Log Group. No nosso caso faremos uma implantação de uma aplicação Elastic Beanstalk com uma imagem Docker, o que significa que os logs da aplicação são fornecidos para um log group cujo nome é determinado pelo seguinte padrão de nomenclatura: /aws/elasticbeanstalk/nome_do_ambiente/var/log/eb-docker/containers/eb-current-app/stdouterr.log. Para uma lista de log groups padrão por plataforma, veja https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/AWSHowTo.cloudwatchlogs.html.

App define o nome da aplicação dentro do serviço de Elastic Beanstalk.

AppVersion define a versão atual da App (aplicação) sob a forma de um SourceBundle. Um source bundle é um arquivo ZIP ou WAR contendo o código fonte e arquivos de configuração do ambiente Beanstalk para que a implantação da aplicação seja possível. Alguns exemplos de criação de source bundle: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/applications-sourcebundle.html. Observação: Um bucket S3 com nome s3-bucket-name contendo um arquivo com o nome s3-key-name.zip deve ser criado antes de utilizar esse template no console do AWS CloudFormation.

AppEnvironment define todas as propriedades do ambiente da nossa aplicação. Primeiro, deve-se definir EnvironmentName visto que uma aplicação pode ter vários ambientes. Depois, especificar a plataforma (SolutionStackName) escolhida para hospedar nossa aplicação, no nosso caso um Docker Container. As plataformas disponíveis podem ser vistas aqui: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/concepts.platforms.html.

Por último, OptionSettings aplica todos os parâmetros definidos na seção Parameters (parâmetros). Detalhando as configurações:

  1. Definindo a variável de ambiente
- Namespace: aws:elasticbeanstalk:application:environment
OptionName: APP_ENV
Value: !Ref AppEnvironment

2. Definindo política de implantação

- Namespace: aws:elasticbeanstalk:command
OptionName: DeploymentPolicy
Value: RollingWithAdditionalBatch
- Namespace: aws:elasticbeanstalk:command
OptionName: BatchSizeType
Value: Fixed
- Namespace: aws:elasticbeanstalk:command
OptionName: BatchSize
Value: 1

3. Definindo a VPC a ser usada

- Namespace: aws:ec2:vpc
OptionName: VPCId
Value: !Ref AccountVpc

4. Definindo a subnet da instância EC2

- Namespace: aws:ec2:vpc
OptionName: Subnets
Value: !Ref EC2Subnet

5. Definindo o tipo de instância EC2

- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: InstanceType
Value: !Ref EC2InstanceType

6. Definindo o tipo do Balanceador de Carga

- Namespace: aws:elasticbeanstalk:environment
OptionName: EnvironmentType
Value: LoadBalanced
- Namespace: "aws:elasticbeanstalk:environment"
OptionName: LoadBalancerType
Value: network

7. Definindo a subnet e visibilidade do Balanceador de Carga

- Namespace: "aws:ec2:vpc"
OptionName: ELBSubnets
Value: !Ref LoadBalancerSubnet
- Namespace: "aws:ec2:vpc"
OptionName: ELBScheme
Value: !Sub ${LoadBalancerVisibility}

8. Definindo o Perfil de instância

- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: IamInstanceProfile
Value: !Ref InstanceProfile

9. Habilitando o fornecimento de logs (stream logs) para o CloudWatch

- Namespace: aws:elasticbeanstalk:cloudwatch:logs
OptionName: StreamLogs
Value: true

Conclusão

Ao combinar ambas seções Parameters e Resources, um template CloudFormation simples foi criado e pode ser utilizado para fazer a implantação de aplicações em diferentes linguagens. O seu uso torna o gerenciamento da aplicação e dos recursos em nuvem mais seguro, fácil e rápido. Para um time com diversas aplicações, essa solução representa uma padronização do processo de implantação ao mesmo tempo que permite a personalização de acordo com as necessidades e demandas de cada projeto/aplicação.

Por fim, segue o template completo abaixo.

--

--

Rodolfo Costa
Rodolfo Costa

Written by Rodolfo Costa

Software Engineer and Tech lover

No responses yet