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.