MLOps Fundamentals: as 9 etapas chave para passar de zero a operacional
MLOps Fundamentals: o essencial em um artigo — código real, diagramas e etapas concretas, extraídos de um curso de 72 lições.
Todo mundo pode aprender MLOps Fundamentals — desde que siga as etapas na ordem correta. Condensamos um curso completo de 72 lições em um percurso claro, com os trechos de código mais úteis.
- Instalar o ambiente MLOps
- Descobrir o MLOps
- Versionamento de dados e modelos
- Pipelines ML com MLflow
- Containerizar os modelos ML
Guia Completo do Projeto Final – Passo a Passo
Curso MLOps Fundamentals • Detecção de Fraude por Cartão de Crédito • Pipeline MLOps End-to-End
Este guia detalhado acompanha você passo a passo na realização do projeto final. Cada seção corresponde a uma etapa-chave do pipeline MLOps. Siga as etapas em ordem. Todo o código é fornecido e explicado. Duração estimada: 8–12 horas no total.
① Etapa 1 – Inicialização do projeto e ambiente
Crie a estrutura do projeto e o ambiente Conda:
mkdir fraud-detection-mlops
cd fraud-detection-mlops
git init
git config user.email "vous@email.com"
git config user.name "Votre Nom"
conda create -n fraud-mlops python=3.10 -y
conda activate fraud-mlops
pip install scikit-learn xgboost pandas numpy mlflow dvc \
fastapi uvicorn pydantic evidently \
pytest pytest-cov httpx joblib matplotlib \
seaborn imbalanced-learn flake8 black
conda env export > environment.ymlCrie o arquivo requirements.txt:
scikit-learn==1.4.0 xgboost==2.0.3 pandas==2.1.4 numpy==1.26.3 mlflow==2.10.0 dvc==3.38.1 fastapi==0.109.0 uvicorn==0.27.0 pydantic==2.5.3 evidently==0.4.16 pytest==7.4.4 pytest-cov==4.1.0 httpx==0.26.0 joblib==1.3.2 matplotlib==3.8.2 seaborn==0.13.1 imbalanced-learn==0.11.0
Crie a estrutura de pastas:
mkdir -p data/raw data/processed src api monitoring/reports tests models .github/workflows
__pycache__/ *.pyc *.pyo .env models/ mlruns/ *.pkl data/raw/ data/processed/ monitoring/reports/*.html monitoring/reports/*.json .coverage htmlcov/
dvc init git add . git commit -m "feat: init project structure and DVC"
② Etapa 2 – Geração e versionamento dos dados
Crie src/generate_synthetic_data.py (caso não tenha acesso ao dataset Kaggle):
"""Gera um dataset sintético de fraude de cartão de crédito."""
import pandas as pd
import numpy as np
def generate_fraud_dataset(n_samples=50000, fraud_ratio=0.002, random_state=42):
np.random.seed(random_state)
n_fraud = int(n_samples * fraud_ratio)
n_legit = n_samples - n_fraud
legit = pd.DataFrame({
f'V{i}': np.random.normal(0, 1, n_legit) for i in range(1, 29)
})
legit['Amount'] = np.abs(np.random.exponential(scale=88, size=n_legit))
legit['Time'] = np.sort(np.random.uniform(0, 172800, n_legit))
legit['Class'] = 0
fraud = pd.DataFrame({
f'V{i}': np.random.normal(np.random.uniform(-3, 3), 2, n_fraud) for i in range(1, 29)
})
fraud['Amount'] = np.abs(np.random.exponential(scale=122, size=n_fraud))
fraud['Time'] = np.random.uniform(0, 172800, n_fraud)
fraud['Class'] = 1
df = pd.concat([legit, fraud], ignore_index=True)
df = df.sample(frac=1, random_state=random_state).reset_index(drop=True)
return df
if __name__ == "__main__":
df = generate_fraud_dataset()
df.to_csv("data/raw/creditcard.csv", index=False)
print(f"Dataset gerado: {len(df)} linhas")
print(f"Fraudes: {df['Class'].sum()} ({df['Class'].mean()*100:.3f}%)")Dockerfile para um modelo ML: build e run
Curso MLOps Fundamentals — Capítulo 04 — Containerizar os modelos ML
- Compreender cada instrução de um Dockerfile para um modelo ML
- Escrever um Dockerfile completo para uma API scikit-learn com FastAPI
- Construir uma imagem Docker com
docker build - Executar e testar um contêiner ML com
docker run - Depurar os problemas comuns de contêineres ML
1. Estrutura do projeto ML a ser containerizado
Antes de escrever o Dockerfile, vejamos a estrutura do projeto que vamos containerizar. Trata-se de uma API de classificação de vinhos baseada em um modelo Random Forest treinado com scikit-learn, servido via FastAPI.
wine-classifier/
âââ Dockerfile # Nosso arquivo de configuração Docker
âââ .dockerignore # Arquivos a excluir da imagem
âââ requirements.txt # Dependências Python
âââ src/
âââ app.py # Aplicação FastAPI (ponto de entrada)
âââ predict.py # Lógica de previsão
âââ preprocessing.py # Pré-processamento das features
âââ models/
âââ rf_classifier.pkl # Modelo Random Forest serializado
âââ scaler.pkl # StandardScaler salvoO arquivo requirements.txt
O requirements.txt lista todas as dependências Python com suas versões fixadas. Isso é crucial para a reprodutibilidade: sem versão fixada, pip install poderia baixar uma versão mais recente e incompatível.
# requirements.txt # Framework web fastapi==0.110.0 uvicorn[standard]==0.27.1 # Machine Learning scikit-learn==1.4.1 numpy==1.26.4 pandas==2.2.1 # Serialização do modelo joblib==1.3.2 # Validação de dados pydantic==2.6.3
Evite
scikit-learn>=1.0 ou scikit-learn sem versão. Se o scikit-learn publicar a versão 2.0 com mudanças de API, seu contêiner quebrará no próximo rebuild. Use sempre scikit-learn==1.4.1.A aplicação FastAPI (src/app.py)
# src/app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib
import numpy as np
import os
app = FastAPI(
title="Wine Quality Classifier API",
description="API de classificação da qualidade do vinho baseada em Random Forest",
version="1.0.0"
)
# Carregamento do modelo na inicialização (não a cada requisição)
MODEL_PATH = os.getenv("MODEL_PATH", "/app/models/rf_classifier.pkl")
SCALER_PATH = os.getenv("SCALER_PATH", "/app/models/scaler.pkl")
model = joblib.load(MODEL_PATH)
scaler = joblib.load(SCALER_PATH)
class WineFeatures(BaseModel):
fixed_acidity: float
volatile_acidity: float
citric_acid: float
residual_sugar: float
chlorides: float
free_sulfur_dioxide: float
total_sulfur_dioxide: float
density: float
pH: float
sulphates: float
alcohol: float
@app.get("/health")
def health_check():
return {"status": "ok", "model": "rf_classifier", "version": "1.0.0"}
@app.post("/predict")
def predict(features: WineFeatures):
try:
X = np.array([[
features.fixed_acidity, features.volatile_acidity,
features.citric_acid, features.residual_sugar,
features.chlorides, features.free_sulfur_dioxide,
features.total_sulfur_dioxide, features.density,
features.pH, features.sulphates, features.alcohol
]])
X_scaled = scaler.transform(X)
prediction = model.predict(X_scaled)[0]
probability = model.predict_proba(X_scaled)[0].max()
return {
"quality": int(prediction),
"confidence": round(float(probability), 4),
"label": "bom vinho" if prediction >= 6 else "vinho médio"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))2. O Dockerfile completo e comentado
Aqui está o Dockerfile completo. Vamos analisar cada instrução em detalhe imediatamente após.
# ============================================================
# Dockerfile para um modelo scikit-learn servido com FastAPI
# ============================================================
# Etapa 1: Imagem base
FROM python:3.11-slim
# Etapa 2: Metadados da imagem
LABEL maintainer="mlops-team@exemple.com"
LABEL version="1.0.0"
LABEL description="Wine Quality Classifier - Random Forest API"
# Etapa 3: Variáveis de ambiente do sistema
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Etapa 4: Diretório de trabalho no contêiner
WORKDIR /app
# Etapa 5: Dependências do sistema (se necessário)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Etapa 6: Copiar E instalar as dependências Python (camada em cache)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Etapa 7: Copiar o código-fonte
COPY src/ ./src/
# Etapa 8: Copiar o modelo ML
COPY models/ ./models/
# Etapa 9: Expor a porta da API
EXPOSE 8000
# Etapa 10: Usuário não-root para segurança
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
# Etapa 11: Comando de inicialização
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]3. Análise detalhada de cada instrução
3.1 FROM python:3.11-slim
FROM define a imagem base. python:3.11-slim é a versão leve da imagem Python oficial:
Para scikit-learn, slim é o bom compromisso: suficientemente pequeno, mas compatível com as extensões C do numpy/scipy.
:latestFROM python:latest pode passar de Python 3.11 para 3.12 da noite para o dia e quebrar seu código. Sempre especifique a versão exata: python:3.11-slim ou até python:3.11.8-slim.Se seu modelo usar GPU (TensorFlow, PyTorch), use:
FROM nvidia/cuda:12.1-cudnn8-runtime-ubuntu22.043.2 Variáveis de ambiente ENV
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1| Variável | Efeito | Por que ativar? |
|---|---|---|
PYTHONDONTWRITEBYTECODE=1 |
Sem arquivos .pyc |
Reduz o tamanho da imagem, evita arquivos desnecessários |
PYTHONUNBUFFERED=1 |
Logs em tempo real | Indispensável para ver os logs em docker logs imediatamente |
PIP_NO_CACHE_DIR=1 |
Sem cache pip na imagem | Reduz o tamanho final da imagem (pode economizar 100-200 MB) |
PIP_DISABLE_PIP_VERSION_CHECK=1 |
Sem verificação de atualização do pip | Acelera o build, evita avisos desnecessários |
3.3 WORKDIR /app
WORKDIR define o diretório de trabalho no contêiner. Todas as instruções seguintes (COPY, RUN, CMD) são executadas a partir desse diretório. Se o diretório não existir, o Docker o cria automaticamente.
/app como diretório de trabalho para aplicações Python. Evite trabalhar diretamente em / ou /root.Otimização de imagens Docker ML
Curso MLOps Fundamentals — Capítulo 04 — Containerizar os modelos ML
- Compreender por que o tamanho das imagens Docker ML é um desafio crítico em produção
- Dominar os multi-stage builds para separar construção e execução
- Configurar um
.dockerignoreeficaz para projetos ML - Explorar o cache de camadas Docker ao máximo
- Aplicar as melhores práticas para imagens ML de produção
1. Por que o tamanho das imagens ML é crítico
Ao contrário de uma aplicação web clássica (algumas dezenas de MB), uma imagem Docker ML pode facilmente atingir 2 a 8 GB. Esse tamanho tem consequências diretas no seu fluxo de trabalho MLOps:
🚬 Implantação lenta
Baixar uma imagem de 5 GB em um cluster Kubernetes leva de 10 a 20 minutos. Em caso de auto-scaling sob carga, isso é inaceitável.
💸 Custo de armazenamento
Armazenar 50 versões de uma imagem de 4 GB no AWS ECR = 200 GB × 0,10 US$/GB/mês = 20 US$/mês apenas para armazenamento.
🔐 Superfície de ataque
Cada ferramenta adicional (compiladores, utilitários de sistema) é uma potencial vulnerabilidade de segurança. Uma imagem mínima é mais segura.
2. Comparação: imagens pesadas vs imagens slim
| Abordagem | Imagem base | Tamanho típico | Tempo de pull (100 Mbit/s) | Vulnerabilidades |
|---|---|---|---|---|
| 🔴 Imagem pesada (ingênua) | python:3.11 + todas as ferramentas |
~2,1 GB | ~170 s | Muito numerosas |
| 🟡 Imagem slim (boa prática) | python:3.11-slim |
~700 MB | ~56 s | Moderadas |
| 🟢 Multi-stage slim | Build: python:3.11-slimRun: python:3.11-slim |
~350 MB | ~28 s | Poucas |
| 🆕 Distroless | gcr.io/distroless/python3 |
~180 MB | ~14 s | Muito poucas |
| 🟢 Alpine (com precauções) | python:3.11-alpine |
~100 MB | ~8 s | Mínimas |
Alpine usa
musl libc em vez de glibc. Numpy, scikit-learn, pandas e PyTorch são compilados para glibc. O uso de Alpine força a compilação a partir das fontes, o que leva 30 a 60 minutos e pode falhar. Reserve Alpine para microsserviços sem dependências C. Para ML, prefira python:3.11-slim.3. O .dockerignore: sua primeira linha de defesa
Antes mesmo do multi-stage build, o .dockerignore é a otimização mais simples e imediata. Ele impede que o Docker envie arquivos desnecessários (ou até perigosos) para o daemon de build.
Quando você executa
docker build ., o Docker envia todo o diretório atual para o daemon Docker (chamado de «contexto de build»). Sem .dockerignore, isso inclui seu dataset de 10 GB, os notebooks Jupyter, os ambientes virtuais Python, os arquivos de configuração secretos…# .dockerignore para um projeto ML # Controle de versão .git/ .gitignore .github/ # Ambientes Python virtuais (crítico! pode ocupar centenas de MB) .venv/ venv/ env/ ENV/ __pycache__/ *.py[cod] *.pyo .pytest_cache/ .mypy_cache/ # Dados ML (nunca incluir na imagem!) data/ datasets/ *.csv *.parquet *.arrow *.feather # Notebooks Jupyter (não necessários em produção) *.ipynb .ipynb_checkpoints/ # Arquivos de configuração local .env .env.local .env.development *.env # Documentação e testes (inúteis em produção) docs/ tests/ README.md CHANGELOG.md # Artefatos de build dist/ build/ *.egg-info/ htmlcov/ .coverage # IDE e editores .vscode/ .idea/ *.swp *.swo .DS_Store Thumbs.db # Logs e arquivos temporários logs/ *.log tmp/ temp/ # Arquivos MLflow e experimentos mlruns/ mlflow.db # Modelos de teste (manter apenas o modelo de produção) models/experiments/ models/checkpoints/
Medir o impacto do .dockerignore
# Verificar o tamanho do contexto de build ANTES do .dockerignore docker build -t test-before . 2>&1 | head -5 # Sending build context to Docker daemon 4.521GB <-- sem .dockerignore! # Verificar o tamanho do contexto de build DEPOIS do .dockerignore docker build -t test-after . 2>&1 | head -5 # Sending build context to Docker daemon 12.34MB <-- com .dockerignore
4. Os multi-stage builds para ML
Um multi-stage build usa várias instruções FROM em um único Dockerfile. Cada etapa produz uma camada intermediária, e apenas a última etapa é mantida na imagem final. Isso permite:
4.1 Multi-stage build para um modelo scikit-learn
# ============================================================
# Multi-stage Dockerfile para Wine Classifier (produção)
# ============================================================
# ---- ETAPA 1: Builder ----
# Esta etapa instala tudo, inclusive as ferramentas de compilação
FROM python:3.11-slim AS builder
# Variáveis de ambiente para o build
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /build
# Instalar as ferramentas de build do sistema (NÃO estarão na imagem final)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
build-essential \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Copiar e instalar em um diretório local isolado (--prefix)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ---- ETAPA 2: Imagem de produção (final) ----
# Imagem final ultraleve: não contém gcc, build-essential, etc.
FROM python:3.11-slim AS production
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
# Copiar apenas as bibliotecas instaladas do builder
COPY --from=builder /install /usr/local
# Dependências de sistema runtime (mínimas)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Copiar o código-fonte e o modelo
COPY src/ ./src/
COPY models/ ./models/
# Segurança: usuário não-root
RUN useradd --create-home --shell /bin/bash appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]Ao eliminar
gcc, g++, build-essential e os arquivos intermediários de compilação da imagem final, você geralmente ganha entre 200 e 600 MB dependendo das dependências C/C++ utilizadas.4.2 Multi-stage build com etapa de teste
# ============================================================ # Multi-stage Dockerfile com etapa de teste integrada # ============================================================ FROM python:3.11-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # ---- Etapa de teste ---- FROM base AS test COPY requirements-dev.txt . RUN pip install --no-cache-dir -r requirements-dev.txt COPY src/ ./src/ COPY models/ ./models/ COPY tests/ ./tests/ RUN pytest tests/ -v --tb=short # ---- Etapa de produção ---- FROM base AS production COPY src/ ./src/ COPY models/ ./models/ RUN useradd --create-home appuser && chown -R appuser /app USER appuser EXPOSE 8000 CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]
Este artigo cobre os trechos mais úteis — o curso completo MLOps Fundamentals (13 capítulos, 72 lições, exercícios corrigidos e projeto final) leva você até o fim.
./acessar-o-curso-completo curso gratuito: Dominar Claude CodeFAQ
Quanto tempo para aprender MLOps Fundamentals?
Precisa de pré-requisitos?
Por onde começar concretamente?
📬 Quer receber este tipo de guia toda semana? Inscreva-se gratuitamente — código real, zero enrolação.