Olá, estou tentando construir uma previsão ao vivo com o YOLO. O objetivo é transmitir os dados com algum tipo de transformação da inferência para um frontend final.
O fluxo deve ser assim:
- treinamento de modelo (processo separado)
- Inferência que salva os dados no Postgres
- Processo ELT que lê os dados do postgres, os transforma e os salva em uma nova tabela no postgres
- Uma API expõe a tabela final
- O frontend usa a API para mostrar os dados em um painel
A ideia é colocar tudo em uma estrutura de micro serviços para ter soluções independentes e escaláveis. Sei que em uma escala maior uma arquitetura com Kafka e Spark seria a maneira mais eficiente, mas para este projeto quero colocar uma arquitetura de micro serviços.
Meu problema agora é que eu quero compartilhar alguns utilitários e também alguns esquemas entre os serviços. Minha ideia é usar um contêiner base que estou construindo para que eu possa usar esse contêiner como imagem base para todos os contêineres que precisam dos esquemas. Devido ao motivo de que tudo deve acabar em um produto, eu também quero tê-lo em um monorepo.
Também sei que o compartilhamento de esquemas para microsserviços não é uma prática recomendada, mas para esse caso de uso ajudaria muito.
Aqui está uma ideia simplificada para nossa estrutura (aqui com vÃdeos que são calculados ao vivo):
.
├── data/
│ ├── weights
│ ├── model_data
│ └── inference_tests
├── model_training/
│ ├── train.py
│ ├── prep.py
│ └── eval.py
├── services/
│ ├── shared/
│ │ ├── Dockerfile
│ │ ├── schemas/
│ │ │ ├── stats.py
│ │ │ └── raw_data.py
│ │ └── db_utils
│ ├── inference/
│ │ ├── Dockerfile
│ │ ├── pyproject.toml
│ │ ├── main.py
│ │ └── src/
│ │ └── all_stuff.py
│ ├── etl_process/
│ │ ├── Dockerfile
│ │ ├── pyproject.toml
│ │ ├── main.py
│ │ └── src/
│ │ └── all_stuff.py
│ ├── backend_for_frontend/
│ │ ├── Dockerfile
│ │ ├── pyproject.toml
│ │ ├── main.py
│ │ └── src/
│ │ └── all_stuff.py
│ └── frontend/
│ ├── Dockerfile
│ ├── pyproject.toml
│ ├── main.py
│ └── src/
│ └── all_stuff.py
└── docker-compose.yaml
No final, quero combinar tudo com docker-compose assim:
version: "3.8"
services:
# Base image for shared code
shared-base:
build:
context: ./services/shared
dockerfile: Dockerfile.base
image: shared-base-image
db:
image: postgres:13
volumes:
- db_data:.local/postgresql/data # .local is in the .gitignore
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
ports:
- "5432:5432"
inference:
build:
context: ./services/inference
dockerfile: Dockerfile
depends_on:
- shared-base
- db
volumes:
- ./data/video:/input_videos # not stream yet
environment:
DB_HOST: db
DB_USER: myuser
DB_PASSWORD: mypassword
DB_NAME: mydb
etl-process:
build:
context: ./services/etl-process
dockerfile: Dockerfile
depends_on:
- shared-base
- db
environment:
DB_HOST: db
DB_USER: myuser
DB_PASSWORD: mypassword
DB_NAME: mydb
backend:
build:
context: ./services/backend_for_frontend
dockerfile: Dockerfile
depends_on:
- shared-base
- db
ports:
- "8000:8000"
environment:
DB_HOST: db
DB_USER: myuser
DB_PASSWORD: mypassword
DB_NAME: mydb
frontend:
build:
context: ./services/frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
db_data:
Para ter os módulos e esquemas compartilhados, quero usar o contêiner base que estou construindo como imagem base para outros contêineres que precisam dos esquemas e utilitários compartilhados.
Veja como eu quero implementá-lo:
FROM python:3.9-slim-buster
WORKDIR /app
COPY . /shared
Próximo arquivo que depende dele:
FROM shared-base-image
RUN pip install uv
COPY . .
ENRTRYPOINT["uv", "run", "main.py"]
Agora minha pergunta final: Qual seria a estrutura final para esse fluxo de trabalho e design? Existem alguns padrões de design que são realmente úteis?
Com essa estrutura, também enfrento os problemas de não poder executar facilmente o script e os módulos sem o contêiner. Faz sentido anexar caminhos com base na existência do caminho?
Quero dizer, eu também poderia ter apenas uma pasta bif src, mas todos os serviços teriam as mesmas dependências, o que também seria uma sobrecarga.
Obrigado desde já pela sua ajuda e espero que você tenha alguma informação para melhorar a estrutura.
Espero que você possa me dar alguma ideia de como estruturá-lo efetivamente. É principalmente sobre design e padrão de design.
Você deve tratar a biblioteca compartilhada como uma biblioteca Python comum. Ela não precisa de um Dockerfile, mas precisa do seu próprio
pyproject.toml
. Então seus outros serviços podem depender dela normalmenteIsso introduz o caso em que um Dockerfile precisa incluir conteúdo de fora de seu próprio diretório. No arquivo Compose, você precisa alterar o
build: { context: }
ponto para algum diretório pai e alterar odockerfile:
ponto de volta para o subdiretórioe também altere as
COPY
instruções do Dockerfile para fazer referência ao subdiretórioNesta configuração não há um "Dockerfile base". Este padrão não é bem suportado pelo Compose. Seu código Python compartilhado provavelmente não é grande e, desde que as primeiras linhas do Dockerfile sejam as mesmas em seus vários serviços, as camadas de imagem Docker subjacentes podem ser compartilhadas.
Eu também exploraria os méritos de usar apenas uma única imagem. No seu diretório raiz, você poderia ter um
pyproject.toml
que dependesse de todos os subprojetos, o que também traria seus scripts de ponto de entrada Python. Na medida em que você tem grandes dependências, isso provavelmente requer menos espaço em disco: um contêiner compartilha espaço com sua imagem, e você terá apenas uma cópia de cada dependência, independentemente de quantos projetos as usam. Agora, um commit em qualquer lugar do seu repositório produz uma única imagem nova.Também pode haver algum valor em dividir essa configuração em repositórios separados. Se você puder usar um repositório de pacotes Python ou carregar sua biblioteca no PyPI, então você pode usar um Dockerfile mais simples. Você também não será forçado a reconstruir e reiniciar seu frontend porque o trabalho ETL mudou. A desvantagem, como é, é que é mais difÃcil fazer alterações de interrupção entre serviços, mas espero que isso seja um evento raro (e o versionamento semântico adequado em sua biblioteca pode mitigar os problemas um pouco).