Ambiente local de ELT (Extract, Load, Transform) com Apache Airflow, MinIO e PostgreSQL via Docker.
Basta clonar e rodar .\setup.ps1 para ter o lab completo funcionando.
# 1. Clonar
git clone https://github.com/flrodrigues21/elt.git
cd elt
# 2. Subir tudo (gera .env com senhas seguras automaticamente)
.\setup.ps1
# 3. Rodar o pipeline
# Abra http://localhost:8080, ative e execute a DAG elt_municipios_ibgePrerequisitos: Git, Windows PowerShell 5.1 ou superior, Docker Desktop com Docker Compose v2, aproximadamente 8 GB de RAM livre e acesso de rede aos registries Docker, GitHub e PyPI durante a primeira construcao.
| Componente | Imagem | Porta | Descricao |
|---|---|---|---|
| PostgreSQL | postgres:16-alpine |
127.0.0.1:5432 |
5 bancos: elt, bronze, silver, gold, airflow |
| Airflow Webserver | apache/airflow:2.9.3-python3.11 |
127.0.0.1:8080 |
Interface de gerenciamento de DAGs |
| Airflow Scheduler | apache/airflow:2.9.3-python3.11 |
- | Execucao agendada de DAGs |
| MinIO API | minio/minio:RELEASE.2024-09-22T00-33-43Z |
127.0.0.1:9000 |
API S3-compativel |
| MinIO Console | minio/minio:RELEASE.2024-09-22T00-33-43Z |
127.0.0.1:9001 |
Interface web MinIO |
| JupyterLab | jupyter/pyspark-notebook:python-3.11 |
127.0.0.1:8888 |
IDE interativo com 13 notebooks de dados |
| OpenMetadata | docker.getcollate.io/openmetadata/server:2.0.2 |
127.0.0.1:8585 por padrao |
Catalogo, governanca, lineage e qualidade |
Portas: Todas as portas sao publicadas apenas em
127.0.0.1por seguranca. Atencao: Alterar para0.0.0.0expoe os servicos a rede local/externa. Ao fazer isso, garanta: (1) firewall com whitelist de IPs, (2) credenciais fortes, (3) TLS/proxy reverso na frente, e (4) redes Docker dedicadas.
| Componente | O que e | Papel no projeto | Onde e usado | Licenca | Links |
|---|---|---|---|---|---|
| Python 3.12 | Linguagem de programacao interpretada | Linguagem dos extractors, conectores e controller | Todo o codigo-fonte do projeto | PSF License | python.org | License |
| PostgreSQL 16 | Sistema de gerenciamento de banco relacional | Banco para todas as camadas (bronze/silver/gold/elt/airflow) | docker-compose.yml (servico postgres) |
PostgreSQL License | postgresql.org | License |
| Apache Airflow 2.9.3 | Orquestrador de workflows (DAGs) | Executa, agenda e monitora pipelines ELT | docker-compose.yml (servicos airflow-*) |
Apache 2.0 | airflow.apache.org | License |
| Docker | Plataforma de containerizacao | Empacota servicos em containers isolados | Infraestrutura base do lab | Apache 2.0 | docker.com | License |
| Docker Compose | Orquestrador multi-container | Define e gerencia todos os servicos com um comando | docker-compose.yml |
Apache 2.0 | docs.docker.com/compose | License |
| MinIO | Object storage S3-compativel | Datalake em formato Parquet (camada bronze/silver/gold) | docker-compose.yml (servico minio), src/extractors/minio.py |
AGPL-3.0 | min.io | License |
| MinIO SDK (minio-py) | Cliente Python para API S3 do MinIO | Upload/download de objetos Parquet no datalake | src/connectors/minio_connector.py |
Apache 2.0 | min.io/docs/minio/python | License |
| SQLAlchemy | Toolkit ORM e SQL para Python | Conexao com PostgreSQL/Oracle via pool de conexoes | src/connectors/postgres_connector.py, src/connectors/oracle_connector.py |
MIT | sqlalchemy.org | License |
| pandas | Biblioteca de manipulacao de dados tabulares | Leitura, transformacao e escrita de DataFrames | src/extractors/*.py, src/models/ |
BSD-3-Clause | pandas.pydata.org | License |
| PyArrow | Implementacao Apache Arrow para Python | Suporte a formato Parquet e vetores columnares | src/extractors/s3.py (leitura parquet) |
Apache 2.0 | arrow.apache.org/docs/python | License |
| OpenPyXL | Leitura/escrita de arquivos Excel (.xlsx) | Extracao de dados de planilhas Excel | src/extractors/xlsx.py |
MIT | openpyxl.readthedocs.io | License |
| Requests | HTTP client para Python | Chamadas a APIs REST e downloads de CSV | src/extractors/api.py, src/extractors/s3.py, src/extractors/_security.py |
Apache 2.0 | requests.readthedocs.io | License |
| psycopg2 | Adaptador nativo PostgreSQL para Python | Conexao direta com PostgreSQL via libpq | src/connectors/postgres_connector.py |
LGPL-2.1+ | ycopg.org | License |
| python-oracledb | Driver Oracle para Python (modo Thin) | Conexao com Oracle sem Oracle Client | src/connectors/oracle_connector.py |
Apache 2.0 | oracle.github.io/python-oracledb | License |
| google-api-python-client | Cliente Python para Google APIs | Acesso ao Google Sheets API v4 | src/extractors/google_sheets.py |
Apache 2.0 | github.com/googleapis/google-api-python-client | License |
| gspread | Wrapper Python para Google Sheets | Abstracao simplificada do Sheets API | src/extractors/google_sheets.py |
MIT | github.com/burnash/gspread | License |
| JupyterLab | IDE interativo para notebooks | Ambiente de exploracao e aprendizado de dados | docker/Dockerfile.jupyter, data_lab/*.ipynb |
BSD-3-Clause | jupyter.org | License |
| PySpark | API Python para Apache Spark | Processamento distribuido de dados (notebooks 10-12) | docker/Dockerfile.jupyter (base image) |
Apache 2.0 | spark.apache.org | License |
| NumPy | Computacao numerica vetorial | Arrays e operacoes matriciais (notebook 02) | docker/Dockerfile.jupyter |
BSD-3-Clause | numpy.org | License |
| Polars | DataFrame framework em Rust | Alternativa rapida ao Pandas (notebook 04) | docker/Dockerfile.jupyter |
MIT | pola.rs | License |
| DuckDB | Database analitico embutido | SQL sobre DataFrames (notebook 04) | docker/Dockerfile.jupyter |
MIT | duckdb.org | License |
| Matplotlib | Visualizacao 2D para Python | Graficos estaticos (notebook 08) | docker/Dockerfile.jupyter |
PSF-based | matplotlib.org | License |
| Seaborn | Visualizacao estatistica | Graficos de distribuicao, correlacao (notebook 08) | docker/Dockerfile.jupyter |
BSD-3-Clause | seaborn.pydata.org | License |
| Plotly | Visualizacao interativa | Dashboards e graficos interativos (notebook 08) | docker/Dockerfile.jupyter |
MIT | plotly.com | License |
| scikit-learn | Machine Learning para Python | Classificacao, regressao, pipelines (notebook 09) | docker/Dockerfile.jupyter |
BSD-3-Clause | scikit-learn.org | License |
| OpenMetadata | Plataforma de catalogo e governanca | Descoberta, glossario, ownership, lineage e qualidade | docker-compose.yml |
Apache 2.0 | open-metadata.org | License |
As credenciais sao geradas automaticamente pelo .\setup.ps1 no arquivo .env.
Nunca commite o arquivo .env ao repositorio (protegido pelo .gitignore).
| Servico | Variavel no .env |
Como acessar |
|---|---|---|
| PostgreSQL | POSTGRES_USER, POSTGRES_PASSWORD |
localhost:5432 com cliente SQL |
| Airflow | AIRFLOW_ADMIN_USERNAME, AIRFLOW_ADMIN_PASSWORD |
http://localhost:8080 |
| MinIO | MINIO_ROOT_USER, MINIO_ROOT_PASSWORD |
http://localhost:9001 |
| OpenMetadata | OM_ADMIN_PASSWORD |
admin@open-metadata.org em http://localhost:8585 |
Para ver as credenciais, abra o arquivo .env na raiz do projeto.
elt.global.schedule (tabela de controle)
|
+---------------------+----------------------+
| | |
load_bronze load_silver load_gold
(extratores) (transformacoes SQL) (modelo dimensional)
| | |
GOOGLE SHEETS DW query DW query
XLSX -> silver -> gold
S3 / CKAN / CSV_URL
Oracle / PostgreSQL
MinIO (parquet)
API REST
FTP
| Camada | Descricao | Destino |
|---|---|---|
| Bronze | Extracao de dados brutos de fontes heterogeneas | PostgreSQL bronze ou MinIO (parquet) |
| Silver | Transformacao/limpeza via queries SQL | PostgreSQL silver ou MinIO (parquet) |
| Gold | Modelo dimensional (dimensoes e fatos) | PostgreSQL gold ou MinIO (parquet) |
| Controle | Tabela de schedule e log de execucoes | PostgreSQL elt |
Cada camada pode gravar em PostgreSQL (padrao) ou MinIO (parquet), basta configurar via
configna tabelaschedule.
| Banco | Funcao | Schema |
|---|---|---|
elt |
Controle do framework | global.schedule, global.controle_execucao |
bronze |
Dados brutos (extracao) | global.* |
silver |
Dados transformados | global.* |
gold |
Modelo dimensional | global.* |
airflow |
Metadados do Airflow | airflow_* |
O OpenMetadata faz parte do mesmo docker-compose.yml e do projeto Docker elt.
As credenciais sao geradas a partir do unico .env local; os servicos persistem
somente os dados de autenticacao necessarios em seus bancos internos.
# Sobe ELT, Airflow, Jupyter e OpenMetadata em uma unica stack
.\setup.ps1
# Depois de executar a DAG elt_municipios_ibge, catalogue os ativos
.\scripts\openmetadata\manage.ps1 bootstrapAcesse http://localhost:8585 (ou a porta definida em OPENMETADATA_PORT) com
o usuario fixo admin@open-metadata.org e OM_ADMIN_PASSWORD do .env. A senha
administrativa default do OpenMetadata e substituida automaticamente por uma
senha aleatoria antes do setup concluir. O bootstrap pode ser repetido sem
duplicar os objetos gerenciados e cria:
- Services dos bancos
elt,bronze,silveregolde do Airflow - Dicionario de dados para o pipeline de municipios
- Equipes e ownership demonstrativos
- Glossario e classificacao de sensibilidade
- Lineage de tabela e coluna entre Bronze, Silver e Gold
- Testes de qualidade com resultados publicados no catalogo
Documentacao: politica de governanca e roteiro de demonstracao.
.\scripts\openmetadata\manage.ps1 status
.\scripts\openmetadata\manage.ps1 health
.\scripts\openmetadata\manage.ps1 stopO setup completo requer aproximadamente 8 GB livres. Todos os containers e volumes sao agrupados no projeto Docker
elt.
A versao anterior desta branch usava o projeto Compose separado
elt-openmetadata. O setup atual detecta containers desse projeto e para sem
alterar seus volumes. Antes de atualizar, preserve o .env.openmetadata e faca
backup dos volumes elt-openmetadata_openmetadata-postgres-data e
elt-openmetadata_openmetadata-elasticsearch-data se precisar manter o catalogo
antigo. Remova apenas os containers legados e execute setup.ps1; os volumes
antigos permanecem intactos e o catalogo demonstrativo pode ser recriado com o
bootstrap.
docker ps -a --filter "label=com.docker.compose.project=elt-openmetadata"
docker rm -f elt-openmetadata-server elt-openmetadata-migrate elt-openmetadata-elasticsearch elt-openmetadata-postgres
.\setup.ps1
.\scripts\openmetadata\manage.ps1 bootstrapNao use docker volume rm nesse fluxo sem ter confirmado o backup ou a perda
intencional do catalogo anterior.
.\setup.ps1 # Subir tudo
.\setup.ps1 -Down # Parar (volumes preservados)
.\setup.ps1 -PurgeVolumes # Parar e apagar dados (pede confirmacao)
.\setup.ps1 -Status # Ver status dos containers
.\setup.ps1 -Logs # Ver logs (escolhe container)Depois que setup.ps1 gerar e preencher o .env, a stack tambem pode ser
iniciada manualmente:
docker compose up -d --build| type_source | Descricao | Parametros principais |
|---|---|---|
GOOGLE_SHEETS |
Google Sheets via API | url, conexao_origem_id, table_source, header_row_source |
XLSX |
Arquivo Excel local | table_source, header_row_source |
DW |
Query SQL em banco PostgreSQL | database_source, schema_source, query_source |
ORACLE |
Extracao de banco Oracle | config.connection_airflow, schema_source, table_source |
POSTGRE |
Extracao de banco PostgreSQL | config.connection_airflow, schema_source, table_source |
MINIO |
Arquivos Parquet/CSV do MinIO | config.endpoint, config.bucket, config.object_name |
FTP |
Download de arquivos via FTP | config.ftp_host, config.ftp_base, config.file_pattern, config.file_format |
S3 / CKAN / CSV_URL |
Download de CSV/Parquet via URL | url, config.delimiter, config.encoding |
API |
API REST generica | config.connection_airflow, config.base_url, config.endpoint |
- Senhas geradas automaticamente com caracteres aleatorios criptograficos
- Chave Fernet do Airflow gerada criptograficamente
- Portas publicadas apenas em
127.0.0.1(localhost) - Arquivos
.envnao versionados (gitignore) - Credenciais nao expostas no terminal durante setup
- Imagens Docker base fixadas por tag e digest SHA-256
- Protecao contra path traversal em downloads
- Protecao contra SSRF (validacao de esquema e resolucao DNS)
- Limites de tamanho em downloads e extracao de ZIPs
- Validacao de identificadores SQL em queries
- Credenciais mascaradas em logs
Este repositorio e projetado para desenvolvimento e testes locais. Para uso em producao:
- Secrets: Use Docker secrets, Vault, ou o gerenciamento de secrets do Airflow (nao arquivos
.env) - TLS: Configure TLS/proxy reverso para todas as portas expostas
- Network: Use redes Docker dedicadas; nunca exponha servicos diretamente
- Monitoring: Adicione Prometheus/Grafana para metricas de Airflow e PostgreSQL
- Backup: Configure backup automatico dos volumes
elt_elt-pgdataeelt_elt-miniodata - Logs: Centralize logs com ELK/Fluentd/Loki
- Resource Limits: Adicione
deploy.resources.limitsnodocker-compose.yml
elt/
+-- main.py # DAG Airflow (geracao dinamica)
+-- controller/ # Orquestracao
| +-- load_bronze.py
| +-- load_silver.py
| +-- load_gold.py
| +-- dags.py # Notificacao por email
+-- models/
| +-- bronze/extract.py # Extrator generico
| +-- silver/transform.py # Transformacao generica
| +-- gold/transform.py # Modelo dimensional generico
+-- src/
| +-- schedule/ # Leitura da tabela de controle
| +-- connectors/ # Conectores de banco e servicos
| | +-- postgres_connector.py
| | +-- oracle_connector.py
| | +-- minio_connector.py
| | +-- airflow_connections.py
| +-- extractors/ # Extratores registraveis
| | +-- base.py
| | +-- _security.py # Utilitarios de seguranca
| | +-- google_sheets.py
| | +-- xlsx.py
| | +-- oracle.py
| | +-- postgres.py
| | +-- minio.py
| | +-- ftp.py
| | +-- s3.py
| | +-- api.py
| +-- utils/
| | +-- validation.py # Validacao de identificadores SQL
| +-- historics/ # SCD Tipo 2
+-- sql/init/ # Scripts de inicializacao do banco
+-- docker/
| +-- Dockerfile.airflow
| +-- airflow_init.sh
| +-- requirements-airflow.txt
+-- docker-compose.yml
+-- .env # Credenciais (nao versionado)
+-- .env.example # Template de configuracao
+-- data_lab/ # JupyterLab notebooks (Data Lab)
| +-- 00_...ipynb ate 12_...ipynb
| +-- datasets/ # Datasets gerados pelos notebooks
+-- THIRD_PARTY_NOTICES.md # Licencas de dependencias
+-- LICENSE # MIT License
O ELT Lab inclui um ambiente JupyterLab com 13 notebooks para explorar e aprender conceitos de dados.
- O setup ja inicia o JupyterLab automaticamente
- Abra http://localhost:8888
- Na tela de login, insira a senha gerada no arquivo
.env(JUPYTER_PASSWORD) - O usuario e sempre
jovyan(configurado viaJUPYTER_USERNAMEno.env) - Navegue ate a pasta
Data Labno JupyterLab
| # | Notebook | Conteudo |
|---|---|---|
| 00 | Boas Vindas e Validacao | Verifica Python, Spark, PostgreSQL, todas as bibliotecas |
| 01 | Python para Dados | Lists, dicts, comprehensions, funcoes, try/except, JSON |
| 02 | NumPy | Arrays, indexacao, broadcasting, agregacoes |
| 03 | Pandas | DataFrame, filtros, groupby, merge, NaN |
| 04 | Polars e DuckDB | LazyFrame, SQL direto no DataFrame, performance |
| 05 | Arquivos | CSV, Excel, Parquet, particionamento |
| 06 | APIs e JSON | Mock server local, GET, erros, json_normalize |
| 07 | SQLAlchemy e PostgreSQL | Conexao, DDL, DML, transacoes com cleanup |
| 08 | Visualizacao de Dados | Matplotlib, Seaborn, Plotly (offline) |
| 09 | Scikit-Learn Basico | Pipeline, train/test split, classification report |
| 10 | PySpark Fundamentos | Schema, select, groupBy, join, window, explain |
| 11 | PySpark Parquet e Medalhao | Bronze/Silver/Gold com PySpark + Parquet |
| 12 | Desafios de Entrevista | Running total, gap-and-islands, deduplicacao, pivot |
| Biblioteca | Versao | Uso |
|---|---|---|
| NumPy | 1.26.4 | Arrays numericos |
| pandas | 2.2.3 | Manipulacao de DataFrames |
| Polars | 1.12.0 | DataFrame em Rust (rapido) |
| DuckDB | 1.1.3 | SQL analitico embutido |
| PyArrow | 17.0.0 | Formato Parquet |
| OpenPyXL | 3.1.5 | Leitura/escrita Excel |
| SQLAlchemy | 2.0.36 | Conexao com bancos |
| psycopg2 | 2.9.10 | Adaptador PostgreSQL |
| Matplotlib | 3.9.2 | Graficos estaticos |
| Seaborn | 0.13.2 | Graficos estatisticos |
| Plotly | 5.24.1 | Graficos interativos |
| scikit-learn | 1.5.2 | Machine Learning |
| PySpark | (base image) | Processamento distribuido |
- Porta publicada apenas em
127.0.0.1(localhost) - Autenticacao por senha (hash SHA-256, token desabilitado)
- Tela de login obrigatoria no navegador (senha unica, sem token)
- Todos os notebooks funcionam offline (sem internet)
- Dados sinteticos (sem dados reais ou credenciais)
- Diretorio ELT montado como somente leitura
- Limites de recursos: 4GB RAM, 2 CPUs
- Container nao roda como root
- Imagem Docker com SHA-256 fixado (reprodutibilidade)
A tabela elt.global.schedule e o coracao do framework. Cada linha define um step de extracao ou transformacao.
SELECT id, type_source, layer, projeto, table_destiny, schedule_cron
FROM elt.global.schedule
WHERE ativo = TRUE
ORDER BY projeto, layer, ordem;| Connection ID | Banco | Descricao |
|---|---|---|
elt_bronze |
bronze | Extracao de dados |
elt_silver |
silver | Transformacao |
elt_gold |
gold | Modelo dimensional |
elt_schedule |
elt | Schedule + controle_execucao |
google_sheets |
- | Service account (se necessario) |
minio |
- | Credenciais MinIO (se utilizado) |
Cada projeto precisa de ao menos um INSERT por camada (bronze, silver, gold):
-- Bronze: extracao de CSV
INSERT INTO elt.global.schedule
(type_source, layer, projeto, ordem, ativo,
schedule_cron,
url, table_source, table_destiny,
schema_destiny, strategy_destiny,
config)
VALUES
('CSV_URL', 'bronze', 'meu_projeto', 1, TRUE,
'0 7 * * 5',
'https://exemplo.com/dados.csv',
'dados', 'minha_tabela',
'global', 'truncate',
'{"delimiter": ",", "encoding": "utf-8"}');
-- Silver: transformacao SQL
INSERT INTO elt.global.schedule
(type_source, layer, projeto, ordem, ativo,
schedule_cron,
database_source, schema_source,
query_source,
table_destiny, schema_destiny, strategy_destiny)
VALUES
('DW', 'silver', 'meu_projeto', 1, TRUE,
'0 7 * * 5',
'bronze', 'global',
'SELECT * FROM global.minha_tabela WHERE coluna = valor',
'minha_tabela_tratada', 'global', 'truncate');
-- Gold: modelo dimensional
INSERT INTO elt.global.schedule
(type_source, layer, projeto, ordem, ativo,
schedule_cron,
database_source, schema_source,
query_source,
table_destiny, schema_destiny, strategy_destiny)
VALUES
('DW', 'gold', 'meu_projeto', 1, TRUE,
'0 7 * * 5',
'silver', 'global',
'SELECT categoria, COUNT(*) AS total FROM global.minha_tabela_tratada GROUP BY categoria',
'dm_categoria', 'global', 'truncate');Para gravar uma camada como parquet no MinIO, adicione "minio" no config:
-- Bronze: gravar no MinIO
INSERT INTO elt.global.schedule
(type_source, layer, projeto, ordem, ativo,
schedule_cron,
url, table_destiny, schema_destiny, strategy_destiny,
config)
VALUES
('CSV_URL', 'bronze', 'meu_projeto', 1, TRUE,
'0 7 * * 5',
'https://exemplo.com/dados.csv',
'minha_tabela', 'global', 'truncate',
'{"minio": {"bucket": "elt-datalake", "format": "parquet", "object_name": "bronze/minha_tabela.parquet"}}');| Camada | Origem | Destino | Config |
|---|---|---|---|
| Bronze | Qualquer fonte | PostgreSQL (padrao) | Sem config |
| Bronze | Qualquer fonte | MinIO | config.minio |
| Silver | PostgreSQL (SQL) | PostgreSQL (padrao) | Sem config |
| Silver | MinIO (parquet) | PostgreSQL | config.minio_source |
| Silver | PostgreSQL | MinIO | config.minio |
| Gold | PostgreSQL (SQL) | PostgreSQL (padrao) | Sem config |
| Gold | MinIO (parquet) | PostgreSQL | config.minio_source |
| Gold | PostgreSQL | MinIO | config.minio |
O Airflow cria automaticamente uma DAG para cada projeto com schedule_cron preenchido:
| Projeto | DAG gerada | Schedule |
|---|---|---|
municipios_ibge |
elt_municipios_ibge |
0 4 * * 1 (segundas 04:00) |
Alem disso, a DAG elt_pipeline (geral) executa todos os projetos.
A coluna schedule_cron usa o formato padrao do Airflow:
minuto hora dia_do_mes mes dia_da_semana
| Cron | Significado |
|---|---|
0 6 * * 1 |
Toda segunda-feira as 06:00 |
0 7 * * 1-5 |
Dias uteis (seg-sex) as 07:00 |
30 8 * * 1,3,5 |
Segunda, quarta e sexta as 08:30 |
0 9 1 * * |
Dia 1 de cada mes as 09:00 |
0 */2 * * * |
A cada 2 horas |
Para testar expressoes cron: https://crontab.guru
- Imagens Docker base: Fixadas com tag e digest SHA-256
- Python (requirements-airflow.txt): Versoes sem upper bound, resolvidas via constraints oficiais
- Airflow constraints:
Dockerfile.airflowusa constraints oficiais da versao 2.9.3 (constraints-3.11.txt), garantindo compatibilidade entre providers e SDKs - Dependabot: Configurado em
.github/dependabot.ymlpara monitorar atualizacoes semanais de pip e Docker - Security:
src/extractors/_security.pyfornece protecao contra path traversal, SSRF (validacao de esquema, IP, allowlist de hosts/CIDRs e redirects manuais com urljoin), e limites de download. DNS rebinding/TOCTOU e um risco residual documentado no modulo — para ambientes de alta seguranca, use firewall de egress, proxy ou allowlist de dominios
- pip-audit: Adicionar etapa
pip-audit --descao pipeline CI para varredura periodica de vulnerabilidades nas dependencias Python - SBOM CycloneDX: Integrar geracao de SBOM (
cyclonedx-py) no CI/CD para rastreabilidade completa de componentes de software
MIT License - veja LICENSE para detalhes.
Dependencias de terceiros: veja THIRD_PARTY_NOTICES.md.