Skip to content

Commit

Permalink
Merge pull request #7 from A3Data/feature/docker-deploy
Browse files Browse the repository at this point in the history
Feature/docker deploy
  • Loading branch information
henrique-tostes-a3 authored Sep 27, 2024
2 parents 3ae163f + cbe9fcc commit ed8d523
Show file tree
Hide file tree
Showing 16 changed files with 1,112 additions and 288 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,6 @@ dmypy.json
cython_debug/

/data

# terraform
.terraform*
54 changes: 53 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ VENV_DIR = .venv

# Define o nome do comando para o Python
PYTHON = python3
DOCKER_IMAGE_NAME = eml-api
REPO_NAME = $(DOCKER_IMAGE_NAME)
AWS_REGION = us-east-1
ACCOUNT_ID = $(shell aws sts get-caller-identity --query Account --output text)
REPO_URI = $(ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(REPO_NAME)
API_PORT = 8000
TERRAFORM_BUCKET = eml-terraform-bucket-$(ACCOUNT_ID)

.PHONY: venv
venv:
Expand Down Expand Up @@ -42,7 +49,7 @@ install-pre-commit: install-dependencies
@echo "Instalando hooks de pre-commit"
$(VENV_DIR)/bin/poetry run pre-commit install --hook-type pre-push --hook-type post-checkout --hook-type pre-commit

## [PADRÃO] Prepara todo o repositório com o poetry e pre-commit
## Prepara todo o repositório com o poetry e pre-commit
.PHONY: init
init: install-pre-commit
$(VENV_DIR)/bin/poetry run dvc remote add -f s3bucket s3://framework-eml-a3data/dvc/
Expand All @@ -69,6 +76,51 @@ lint:
format:
$(VENV_DIR)/bin/poetry run ruff format

## Builda a imagem docker da API
.PHONY: build-image
build-image:
docker build -t $(DOCKER_IMAGE_NAME) -f deployment/docker/Dockerfile .


.PHONY: create-ecr
create-ecr:
chmod +x deployment/scripts/create_ecr.sh
./deployment/scripts/create_ecr.sh $(REPO_NAME) $(AWS_REGION)

## Faz o push da última versão da imagem para o repositório ECR
.PHONY: push-image
push-image: create-ecr
docker tag "$(DOCKER_IMAGE_NAME):latest" "$(REPO_URI):latest"
aws ecr get-login-password --region "$(AWS_REGION)" | docker login --username AWS --password-stdin "$(REPO_URI)"
docker push "$(REPO_URI):latest"

## Inicia a API localmente
.PHONY: api
api: build-image
@echo "Lançando a API localmente..."
docker run -p $(API_PORT):$(API_PORT) $(DOCKER_IMAGE_NAME):latest


.PHONY: create-bucket
create-bucket:
@aws s3api head-bucket --bucket $(TERRAFORM_BUCKET) 2>/dev/null || \
(aws s3api create-bucket --bucket $(TERRAFORM_BUCKET) --region $(AWS_REGION); \
echo "Bucket $(TERRAFORM_BUCKET) criado.")

## Cria toda a infraestrutura do deploy da API, desconsiderando a parte do Docker
.PHONY: create-infra
create-infra: create-bucket
terraform -chdir="deployment/infrastructure/terraform" init -backend-config="bucket=$(TERRAFORM_BUCKET)" -backend-config="region=$(AWS_REGION)"
terraform -chdir="deployment/infrastructure/terraform" apply -auto-approve

## Faz todo o deploy, desde de construir a imagem docker até provisionar toda infraestrutura
.PHONY: deploy
deploy: build-image push-image create-infra

## Deleta toda infraestrutura provisionada anteriormente
.PHONY: destroy
destroy:
terraform -chdir="deployment/infrastructure/terraform" destroy -auto-approve

#################################################################################
# Self Documenting Commands #
Expand Down
67 changes: 55 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Este repositório apresenta um pipeline de Machine Learning completo utilizando o dataset Iris. Ele cobre as etapas de download de dados, pré-processamento, treinamento de modelos e predição.

## Estrutura do repositório
## Estrutura do repositório (desatualizada)

```
├── api # Código da API para interagir com o modelo
Expand Down Expand Up @@ -65,7 +65,16 @@ Este repositório apresenta um pipeline de Machine Learning completo utilizando

Antes de começar, certifique-se de ter as seguintes dependências instaladas:

- Python 3.8+
- WSL (caso voce esteja usando Windows):
- Abra um powershell e execute os comandos abaixo na ordem.
- `wsl --set-default-version 2`
- `wsl --install -d Ubuntu`
- `wsl --set-default Ubuntu`
- Daqui em diante, execute todos os comandos dentro do terminal do wsl.
- Python 3.10+ - https://www.python.org/downloads/
- VSCode - https://code.visualstudio.com/download
- O VSCode permite instalar extensões, é recomendado instalar ao menos a extensão que integra o VSCode ao WSL.
- Cole o seguinte ID na aba de extensões: `ms-vscode-remote.remote-wsl`

## Passo a Passo para preparar o ambiente e executar treino

Expand Down Expand Up @@ -120,14 +129,42 @@ Você pode realizar previsões de duas formas:
#### Previsão em batch

Para realizar previsões em batch utilizando um arquivo CSV de entrada:

```
python -m src.pipelines.predict predict-batch caminho/para/arquivo.csv

```
#### Previsão via linha de comando

Para realizar previsões passando as features diretamente pela linha de comando:

```
python -m src.pipelines.predict predict 5.1 3.5 1.4 0.2
```

## API - Como testar local e como fazer deploy na nuvem

Caso você ainda não tenha ativado, ative o ambiente virtual
```
source venv/bin/activate
```

Para lançar a API basta executar o comando do make, lembrando que é necessário ter o Docker instalado (https://docs.docker.com/desktop/install/windows-install/).
```
make api
```

Você pode fazer qualquer alteração nos arquivos dentro da pasta `api`, só é necessário que no arquivo main.py dentro da pasta esteja o objeto `FastAPI` com o nome `app`, que é representado pela linha: `app = FastAPI()`

Após ficar satisfeito com a API você pode fazer o deploy dela na nuvem.

Para isso, algumas ferramentas precisam ser baixadas e configuradas, faça a instalação no WSL caso esteja usando o Windows, então siga as instruções do Linux:
1. AWS CLI v2: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
2. Terraform: https://developer.hashicorp.com/terraform/install?product_intent=terraform

Na AWS será necessário configurar as credenciais da conta que será provisionado a infraestrutura, utilize o comando `aws configure` e coloque as chaves.

Com essas configurações pontas, para fazer o deploy faça:
```
make deploy
```

## Features do repositório

Expand Down Expand Up @@ -157,13 +194,19 @@ O DVC (Data Version Control) é uma ferramenta open-source que facilita o contro
## Comandos do Makefile

O projeto inclui um Makefile para facilitar o gerenciamento do ambiente e das dependências. Alguns comandos disponíveis:

- **make init**: Configura o ambiente virtual, instala as dependências e configura o pre-commit e o DVC.
- **make clean**: Remove o ambiente virtual e desinstala o pre-commit.
- **make update**: Atualiza as dependências do Poetry.
- **make lint**: Executa o lint no código-fonte com o Ruff.
- **make format**: Formata o código-fonte com o Ruff.
```
make init Prepara todo o repositório com o poetry e pre-commit
make clean Remove todo o ambiente virtual e desconfigura o pre-commit
make update Atualiza as dependências no poetry, útil quando alterar bibliotecas em pyproject.toml
make lint Lint usando ruff (use `make format` para formatação)
make format Formata o código fonte com ruff
make build-image Builda a imagem docker da API
make push-image Faz o push da última versão da imagem para o repositório ECR
make api Inicia a API localmente
make create-infra Cria toda a infraestrutura do deploy da API, desconsiderando a parte do Docker
make deploy Faz todo o deploy, desde de construir a imagem docker até provisionar toda infraestrutura
```

## Boas Práticas para Commits com Pré-commit

- Durante o commit, o Pré-commit verifica e corrige a formatação do código, mas não inclui essas alterações automaticamente no commit. É necessário executar `git add` novamente para registrar essas modificações. O Git não faz isso automaticamente para que você tenha controle total sobre o que será comitado.
- Durante o commit, o Pré-commit verifica e corrige a formatação do código, mas não inclui essas alterações automaticamente no commit. É necessário executar `git add` novamente para registrar essas modificações. O Git não faz isso automaticamente para que você tenha controle total sobre o que será comitado.
26 changes: 26 additions & 0 deletions deployment/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Use a imagem base do Python
FROM python:3.10-slim

# Define a pasta de trabalho dentro do container
WORKDIR /app

# Instala o Poetry via pip
RUN pip install poetry

# Copia o arquivo pyproject.toml para instalar dependências
COPY pyproject.toml .

# Instala as dependências do projeto sem utilizar o poetry.lock
RUN poetry lock \
&& poetry install --no-dev

COPY artifacts/ artifacts/
COPY api/ api/
COPY src/ src/
COPY config/ config/

# Expõe a porta 8000 para o servidor FastAPI
EXPOSE 8000

# Comando para rodar a aplicação FastAPI com Uvicorn
CMD ["poetry", "run", "uvicorn", "api.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
28 changes: 28 additions & 0 deletions deployment/infrastructure/terraform/ec2-iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
resource "aws_iam_role" "ecs_instance_role" {
name = "${local.default_prefix}-ecs-instance-role"

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Principal = {
Service = "ec2.amazonaws.com"
},
Action = "sts:AssumeRole"
}]
})

tags = {
Name = "${local.default_prefix}-ecs-instance-role"
}
}

resource "aws_iam_role_policy_attachment" "ecs_instance_role_policy" {
role = aws_iam_role.ecs_instance_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}

resource "aws_iam_instance_profile" "ecs_instance_profile" {
name = "${local.default_prefix}-ecs-instance-profile"
role = aws_iam_role.ecs_instance_role.name
}
43 changes: 43 additions & 0 deletions deployment/infrastructure/terraform/ec2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
resource "tls_private_key" "private_key" {
algorithm = "RSA"
rsa_bits = 2048
}

resource "aws_key_pair" "generated_key" {
key_name = "${local.default_prefix}-ec2-key"
public_key = tls_private_key.private_key.public_key_openssh
}


data "aws_ami" "ecs_ami" {
most_recent = true
owners = ["amazon"]

filter {
name = "name"
values = ["amzn2-ami-ecs-hvm-*-x86_64-ebs"]
}
}

resource "aws_instance" "ecs_instance" {
ami = data.aws_ami.ecs_ami.id
instance_type = var.ec2_type
key_name = aws_key_pair.generated_key.key_name
iam_instance_profile = aws_iam_instance_profile.ecs_instance_profile.name

user_data = <<-EOF
#!/bin/bash
echo ECS_CLUSTER=${aws_ecs_cluster.eml_cluster.name} >> /etc/ecs/ecs.config
EOF

subnet_id = data.aws_subnets.default.ids[0]

vpc_security_group_ids = [aws_security_group.app_sg.id]

associate_public_ip_address = true

tags = {
Name = "${local.default_prefix}-ecs-instance"
}
depends_on = [aws_ecs_cluster.eml_cluster]
}
82 changes: 82 additions & 0 deletions deployment/infrastructure/terraform/ecs-iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
resource "aws_iam_role" "ecs_execution_role" {
name = "${local.default_prefix}-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
Service = "ecs-tasks.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}

resource "aws_iam_role" "ecs_task_role" {
name = "${local.default_prefix}-task-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
Service = "ecs-tasks.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}

resource "aws_iam_policy" "ecr_policy" {
name = "${local.default_prefix}_ECR_Access_Policy"
description = "Allows ECS execution role to pull from ECR"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
Resource = "*"
}
]
})
}

resource "aws_iam_policy" "logs_service_policy" {
name = "${local.default_prefix}_Logs_Service_Policy"
description = "Allows service to log"
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"${aws_cloudwatch_log_group.ecs_service_logs.arn}:*"
]
}
]
})
}

resource "aws_iam_role_policy_attachment" "ecr_policy_attachment" {
role = aws_iam_role.ecs_execution_role.name
policy_arn = aws_iam_policy.ecr_policy.arn
}

resource "aws_iam_role_policy_attachment" "log_policy_attachment" {
role = aws_iam_role.ecs_execution_role.name
policy_arn = aws_iam_policy.logs_service_policy.arn
}
Loading

0 comments on commit ed8d523

Please sign in to comment.