Mateus Müller

O carinha do Linux

13 abr. 2020

Como provisionar EC2 com Ansible

Oi pessoal! Como todos sabem, estou estudando forte para a certificação AWS Solutions Architect e de vez em quando estou brincando com Terraform e Ansible.

Durante os estudos descobri que o Ansible também pode ser usado (não que seja recomendado) para provisionar infraestrutura e já fiz o teste diretamente com as instâncias EC2 da Amazon e decidi escrever mais sobre isso hoje.

Requisitos

É importante que você tenha claro alguns conceitos que vou usar, mas não se preocupe, vou deixar alguns links complementares para vocês estudarem.

  • Ter a Access Key e a Secret Key da conta AWS
  • Entender autenticação usando chaves assimétricas (se não souber veja esse vídeo)
  • Confortável com a linha de comando
  • Saber o que é o Ansible e como pode ser usado (veja esse vídeo).

Instalação

Para trabalhar com Ansible, você precisa do Ansible (obviamente) e um ambiente com Python. Além disso, você precisa da biblioteca Boto 3, que é basicamente o SDK da AWS para Python. Essa biblioteca que vai permitir a comunicação com a AWS.

$ sudo apt install python3 -y
$ sudo apt install python3-pip -y
$ pip3 install boto boto3 ansible

Você também pode instalar o Ansible pelo apt se quiser.

$ sudo apt install ansible -y

Chave SSH

Você precisa de um par de chaves que são usados para se autenticar nas instâncias.

Em poucas palavras, sua chave pública será colocada dentro das EC2 e você poderá se autenticar com a chave privada sem precisar digitar usuário e senha.

$ ssh-keygen

Estrutura de diretórios

Crie um diretório qualquer para organizar o seu projeto dentro dele. Este link mostra as boas práticas para organizar um projeto com Ansible.

$ mkdir -p $HOME/projeto-ec2/groups_vars/all/
$ cd $HOME/projeto-ec2/
$ touch playbook.yml

Usando ansible-vault

O Vault já é um produto conhecido por prover segurança para o gerenciamento de chaves. O Ansible já trás um componente chamado de ansible-vault que faz isso também.

Bom, vamos estar lidando com chaves estritamente secretas e que dão acesso diretamente para a conta da AWS. É claro que queremos proteger isso, certo?

Vamos criar um arquivo com as chaves e encriptar ele.

$ echo "senha" >> vault.pass
$ ansible-vault create group_vars/all/pass.yml --vault-password-file vault.pass

Basicamente, ele vai encriptar o arquivo pass.yaml e você só pode ler quando digitar a senha que está no arquivo vault.pass. Mas, como somos preguiçosos, já colocamos a senha em um arquivo pra não precisar digitar toda a vez!

É claro que em um ambiente real, você não vai colocar em um arquivo. E mesmo que colocasse, pode utilizar alguma ferramenta para gerar uma senha mais segura.

Por exemplo:

$ openssl rand -base64 2048 > vault.pass

Depois de gerar a senha segura (se preferir), você precisa encriptar o arquivo de senha de novo, ok?

Assim que executar o comando de create, ele deve abrir um editor de texto, seja o Nano ou Vim para você editar o arquivo e adicionar as variáveis.

O editor virá da variável ambiente EDITOR. Se não funcionar, exporte ela.

$ export EDITOR=vim

Quando estiver editando o arquivo, adicione algo como:

ec2_access_key: "sua_access_key"
ec2_secret_key: "sua_secret_key"

Depois é só salvar e sair.

O playbook

Agora é hora de editar o playbook.yaml.

Vou explicar por partes o arquivo e por último vou postar o arquivo final.

O primeiro passo é garantir que as informações estão apontando para localhost. Isso é algo que me confundia muito. Basicamente, estamos dizendo para o Ansible… “Cara, usa a Boto 3 que eu instalei localmente para se conectar na AWS, não tem nada externo aqui”.

O gather_facts definido para False não vai buscar informações antes de executar (até porque elas nem existem). Vai simplesmente executar. Esse módulo pode te ajudar a entender mais sobre isso.

- hosts: localhost
  connection: local
  gather_facts: False

Vamos declarar também uma sessão de variáveis. Lembre-se que as variáveis que declaramos dentro do group_vars já serão adicionadas automaticamente.

Isso é mais para uma perspectiva de reuso de código.

Por exemplo, como tudo será criado na mesma região, se um dia precisar mudar, não preciso mudar manualmente em cada item, apenas mudo na variável.

Sem contar que as variáveis ficam no começo do código, que facilita muito.

  vars:
    region: us-east-1
    image: ami-04ac550b78324f651 # ubuntu 16.04
    id: "mm"
    sec_group: "{{ id }}-sec"

Agora começamos a parte de tasks que é justamente o que deve ser executado.

Primeiro, vamos criar um par de chaves na AWS usando a chave que criamos nos passos iniciais.

Note que basicamente tudo vem das variáveis que criamos na parte acima e também as variáveis que estão encriptadas no outro arquivo.

O key_material vai buscar dinamicamente a minha chave pública, então caso eu mude, ele vai pegar sempre a nova.

  tasks:
  - name: Upload public key to AWS
    ec2_key:
      name: "{{ id }}_key"
      key_material: "{{ lookup('file', '/home/mateus/.ssh/id_rsa.pub') }}"
      region: "{{ region }}"
      aws_access_key: "{{ec2_access_key}}"
      aws_secret_key: "{{ec2_secret_key}}"

Depois de uma chave, precisamos de um security group que permita o acesso via SSH.

Note que ao final estou criando um register que é uma variável que armazena a saída do módulo. Cada módulo possui uma saída. Vou deixar uns links complementares ao final do artigo.

  - name: Create security group
    ec2_group:
      name: "{{ sec_group }}"
      description: "Sec group for app {{ id }}"
      region: "{{ region }}"
      aws_access_key: "{{ec2_access_key}}"
      aws_secret_key: "{{ec2_secret_key}}"
      rules:
        - proto: tcp
          ports:
            - 22
          cidr_ip: 0.0.0.0/0
          rule_desc: allow all on ssh port
    register: sec_group_id

E por último, usamos os recursos criados acima para subir o EC2.

  - name: Provision instance(s)
    ec2:
      aws_access_key: "{{ec2_access_key}}"
      aws_secret_key: "{{ec2_secret_key}}"
      key_name: "{{ id }}_key"
      id: "{{ id }}_instance"
      group_id: "{{ sec_group_id.group_id }}"
      image: "{{ image }}"
      instance_type: t2.micro
      region: "{{ region }}"
      wait: yes
      count: 1

O arquivo final ficou:

# AWS playbook
---

- hosts: localhost
  connection: local
  gather_facts: False

  vars:
    region: us-east-1
    image: ami-04ac550b78324f651 # ubuntu 16.04
    id: "mm"
    sec_group: "{{ id }}-sec"

  tasks:
  - name: Upload public key to AWS
    ec2_key:
      name: "{{ id }}_key"
      key_material: "{{ lookup('file', '/home/mateus/.ssh/id_rsa.pub') }}"
      region: "{{ region }}"
      aws_access_key: "{{ec2_access_key}}"
      aws_secret_key: "{{ec2_secret_key}}"

  - name: Create security group
    ec2_group:
      name: "{{ sec_group }}"
      description: "Sec group for app {{ id }}"
      region: "{{ region }}"
      aws_access_key: "{{ec2_access_key}}"
      aws_secret_key: "{{ec2_secret_key}}"
      rules:
        - proto: tcp
          ports:
            - 22
          cidr_ip: 0.0.0.0/0
          rule_desc: allow all on ssh port
    register: sec_group_id

  - name: Provision instance(s)
    ec2:
      aws_access_key: "{{ec2_access_key}}"
      aws_secret_key: "{{ec2_secret_key}}"
      key_name: "{{ id }}_key"
      id: "{{ id }}_instance"
      group_id: "{{ sec_group_id.group_id }}"
      image: "{{ image }}"
      instance_type: t2.micro
      region: "{{ region }}"
      wait: yes
      count: 1

Agora é só executar.

$ ansible-playbook playbook.yml --vault-password-file vault.pass

Aí é só conectar:

$ ssh user@dns-da-instancia

E boom! Olha na AWS e deve ter criado o que você precisa já.

Vou deixar mais algumas recomendações abaixo:

  • Se quiser debugar, por exemplo, receber o DNS da instância já na saída do ansible-playbook, você precisa deste módulo
  • Post em Inglês que me ajudou muito

Espero que tenha gostado, comenta aí se curtiu! Abração.

Comentários Disqus