Alisson Machado
05 November 2019

CloudFormation: Infraestrutura como código na AWS

Infraestrutura como Código é uma pratica utilizada para fazer o versionamento da infraestrutura assim como fazemos como os códigos em programação, existem diversas ferramentas que podem ser utilizadas, uma das mais famosas é o Terraform, Com ele é possível provisionar infraestruturas em praticamente todas as Clouds , ferramentas de virtualização e diversas outras tecnologias que existem atualmente. Mas se o foco é AWS, existe um serviço específico chamado CloudFormation, nele podemos descrever toda na nossa infraestrutura no formato YAML ou JSON e depois provisionar usando os comandos AWS CLI, sem ter qualquer iteração com o o painel. Então vamos lá, primeiro passo é instalar o aws cli.
python3 -m pip install awscli
Caso tenha sido instalado corretamente você pode executar esse comando:
PS C:\Users\1511 MXTI> aws --version                                                                                                                                       aws-cli/1.16.274 Python/3.7.3 Windows/10 botocore/1.13.10
PS C:\Users\1511 MXTI>                                                                                                                                                                                                                                                                                                                                                                                                                
No meu caso estou usando a versão 3.7 do Python e 1.13 do Boto3. Como esse tutorial é relativamente mais avançado, estou levando como premissa de que você já sabe os comandos e os recursos básicos da AWS. Agora vamos definir as credenciais de acesso.
PS C:\Users\1511 MXTI> aws configure                                                                                                                                       AWS Access Key ID [****************AB4Z]:
AWS Secret Access Key [****************GuGW]:
Default region name [us-west-1]:
Default output format [None]:
PS C:\Users\1511 MXTI>                                             
Esses dados de acesso vocês pode cria-los utilizando o IAM da AWS ( https://aws.amazon.com/pt/iam/ ). Com tudo configurado e o aws cli instalado, vamos direto ao CloudFormation. Um comando básico é fazer a listagem dos Stacks que você já tem criado, como acabamos de iniciar não temos nenhum.
PS C:\Users\1511 MXTI> aws cloudformation list-stacks                                                                                                                      {
    {
        "StackSummaries": [] 
    }
Sabemos que para criar uma instância EC2, precisamos dos seguintes recursos que antecedem o EC2, que seriam. - VPC - Subnet - SecurityGroup - InternetGateway - RoutingTable Então já vamos cria-los como código, ficando da seguinte maneira.
Resources:  
  VPC:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: "192.168.0.0/24"
      EnableDnsHostnames: true
      EnableDnsSupport: true
  
  # InternetAccess to VPC
  InternetGateway:
    Type: "AWS::EC2::InternetGateway"

  VPCGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId:
        Ref: InternetGateway
      VpcId: 
        Ref: VPC
  
  #Creating routes
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: VPC
      Tags:
      - Key: Name
        Value: Public Subnets
      - Key: Network
        Value: Public

  Route:
    DependsOn: VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: InternetGateway
    
  # Security group that allows all traffic
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      VpcId:
          Ref: VPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 0
        ToPort: 65535
        CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: 0
        ToPort: 65535
        CidrIp: 0.0.0.0/0

  Subnet:
    Type: AWS::EC2::Subnet
    Properties: 
      AvailabilityZone: "us-east-2a"
      CidrBlock: "192.168.0.0/24"
      VpcId:
        Ref: VPC
O RoutingTable o InternetGateway são itens praticamente obrigatórios para acessar a instância de fora da AWS, pois ao efetuar uma conexão SSH para a máquina, ela precisa ter acesso a internet e as rotas devem estar configuradas para que a comunicação de resposta possa ser estabelecida. Sendo assim foi criada a seguinte rota: Destination: 0.0.0.0/0 - Que seria a rota default, passa pelo InternetGateway, que no nosso caso também recebeu o nome de InternetGateway. Note que para fazer o vinculo entre os recursos criados é utilizada a instrução Ref, essa instrução serve para pegar o ID do recursos que foi especificado através do Nome, se você reparar no começo do YAML, está da seguinte maneira.
Resources:  
  VPC:
    Type: AWS::EC2::VPC
A primeira linha é definida a instrução Resources, pois abaixo desse nível teremos os recursos criados, que ficam na seguinte sequência.
Resources:  
  NomeDoRecurso:
    Type: TipoDoRecurso
Então apesar de eu ter usado VPC no nome do meu recurso, o que define efetivamente que é uma VPC é a instrução Type, que no caso foi AWS::EC2::VPC. Agora que o arquivo já tem os itens básicos para provisionar as instancias EC2, vamos fazer a criação do Stack.
PS C:\Users\1511 MXTI> aws cloudformation create-stack --stack-name blog --template-body file://tutorial.yml
{
    "StackId": "arn:aws:cloudformation:us-east-2:360560397478:stack/blog/0d2f8500-0016-11ea-a411-0608930970a0"
}
Para saber se a criação foi executada com sucesso, você pode executar o seguinte comando:
PS C:\Users\1511 MXTI> aws cloudformation list-stacks
{
    "StackSummaries": [
        {
            "StackId": "arn:aws:cloudformation:us-east-2:360560397478:stack/blog/0d2f8500-0016-11ea-a411-0608930970a0",
            "StackName": "blog",
            "CreationTime": "2019-11-05T21:48:49.984Z",
            "StackStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        },
Veja que o status está como CREATE_COMPLETE, com o StackName blog. Ou seja, toda a parte de comunicação e redes está funcionando. Agora vamos criar uma EC2 dentro dessa infraestrututura. Para isso no mesmo arquivo YAML , vamos editar e inserir um recurso do tipo EC2.
Resources:  
  VPC:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: "192.168.0.0/24"
      EnableDnsHostnames: true
      EnableDnsSupport: true
  
  # InternetAccess to VPC
  InternetGateway:
    Type: "AWS::EC2::InternetGateway"

  VPCGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId:
        Ref: InternetGateway
      VpcId: 
        Ref: VPC
  
  #Creating routes
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: VPC
      Tags:
      - Key: Name
        Value: Public Subnets
      - Key: Network
        Value: Public

  Route:
    DependsOn: VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: InternetGateway
    
  # Security group that allows all traffic
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      VpcId:
          Ref: VPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 0
        ToPort: 65535
        CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: 0
        ToPort: 65535
        CidrIp: 0.0.0.0/0

  Subnet:
    Type: AWS::EC2::Subnet
    Properties: 
      AvailabilityZone: "us-east-2a"
      CidrBlock: "192.168.0.0/24"
      VpcId:
        Ref: VPC

# EC2 comeca aqui
  EC2Blog: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: "ami-0d5d9d301c853a04a" # ubuntu 18.04
      KeyName: "blog"
      InstanceType: "t2.micro"  
      NetworkInterfaces: 
      - AssociatePublicIpAddress: "true"
        DeviceIndex: "0"
        GroupSet: 
          - Ref: "SecurityGroup"
        SubnetId: 
          Ref: "Subnet"
      BlockDeviceMappings: 
      - DeviceName: "/dev/sdm"
        Ebs: 
          VolumeType: "io1"
          Iops: "200"
          DeleteOnTermination: "true"
          VolumeSize: "20"
Note que as linhas de código referente a instância EC2 foram colocadas no final. Agora para atualizar o nosso Stack, utilize o comando:
PS C:\Users\1511 MXTI> aws cloudformation update-stack --stack-name blog --template-body file://tutorial.yml
{
    "StackId": "arn:aws:cloudformation:us-east-2:360560397478:stack/blog/0d2f8500-0016-11ea-a411-0608930970a0"
}
Vamos rodar o list-stacks novamente e ver se está tudo pronto:
PS C:\Users\1511 MXTI> aws cloudformation list-stacks
{
    "StackSummaries": [
        {
            "StackId": "arn:aws:cloudformation:us-east-2:360560397478:stack/blog/0d2f8500-0016-11ea-a411-0608930970a0",
            "StackName": "blog",
            "CreationTime": "2019-11-05T21:48:49.984Z",
            "LastUpdatedTime": "2019-11-05T22:13:33.799Z",
            "StackStatus": "UPDATE_COMPLETE",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        },
Veja que o resultado é um UPDATE_COMPLETE. Para confirmar se a instância está criada.
PS C:\Users\1511 MXTI> aws ec2 describe-instances
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                    "AmiLaunchIndex": 0,
                    "ImageId": "ami-0d5d9d301c853a04a",
                    "InstanceId": "i-056111b27163add35",
                    "InstanceType": "t2.micro",
                    "KeyName": "blog",
                    "LaunchTime": "2019-11-05T22:13:38.000Z",
                    "Monitoring": {
                        "State": "disabled"
                    },
                    "Placement": {
                        "AvailabilityZone": "us-east-2a",
                        "GroupName": "",
                        "Tenancy": "default"
                    },
                    "PrivateDnsName": "ip-192-168-0-47.us-east-2.compute.internal",
                    "PrivateIpAddress": "192.168.0.47",
                    "ProductCodes": [],
                    "PublicDnsName": "ec2-3-15-149-77.us-east-2.compute.amazonaws.com",
                    "PublicIpAddress": "3.15.149.77",
                    "State": {
                        "Code": 16,
                        "Name": "running"
                    },
Veja que o State dela está como Running e o endereço de IP Publico é: 3.15.149.77. Agora vamos acessar e ver se tudo está funcionando corretamente:
PS C:\Users\1511 MXTI> ssh -i .\Downloads\blog.pem ubuntu@3.15.149.77
The authenticity of host '3.15.149.77 (3.15.149.77)' can't be established.
ECDSA key fingerprint is SHA256:3hhSOEHmTIXem31Bo4nRYkxi1PjtaF3zxuGW9yEOKrk.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '3.15.149.77' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-1051-aws x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Tue Nov  5 22:22:41 UTC 2019

  System load:  0.0               Processes:           85
  Usage of /:   13.6% of 7.69GB   Users logged in:     0
  Memory usage: 15%               IP address for eth0: 192.168.0.47
  Swap usage:   0%

0 packages can be updated.
0 updates are security updates.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo ".
See "man sudo_root" for details.

ubuntu@ip-192-168-0-47:~$
Está tudo perfeito, então como foi só um teste. Vamos deletar o stack para não gastar dinheiro haha.
PS C:\Users\1511 MXTI> aws cloudformation delete-stack --stack-name blog
Pronto! Lembrando que tudo o que foi executado aqui também aparece no Dashboard da AWS, eu costumo manter ele aberto para visualizar os logs da criação do ambiente, então fica ai a dica =)