Compare commits
2 Commits
1d152c0dce
...
8de9b5c0cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 8de9b5c0cb | |||
| 9f58b9afa5 |
17
.dockerignore
Normal file
17
.dockerignore
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
.gitea
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
venv
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.vanna
|
||||||
|
vanna
|
||||||
|
chroma_db
|
||||||
|
data_storage
|
||||||
|
.DS_Store
|
||||||
|
*.md
|
||||||
|
docs
|
||||||
|
test_*.py
|
||||||
|
node_modules
|
||||||
20
.env.example
Normal file
20
.env.example
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# OpenAI
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
OPENAI_MODEL=gpt-5
|
||||||
|
OPENAI_TEMPERATURE=1.0
|
||||||
|
|
||||||
|
# ClickHouse Cloud (database `gold`, usuário `wren_ia`)
|
||||||
|
CLICKHOUSE_HOST=
|
||||||
|
CLICKHOUSE_PORT=8443
|
||||||
|
CLICKHOUSE_DATABASE=gold
|
||||||
|
CLICKHOUSE_USER=wren_ia
|
||||||
|
CLICKHOUSE_PASSWORD=
|
||||||
|
CLICKHOUSE_SECURE=true
|
||||||
|
|
||||||
|
# CORS (separar por vírgula)
|
||||||
|
VANNA_CORS_ORIGINS=https://lab.clubpetro.com,https://homologation.clubpetro.com
|
||||||
|
|
||||||
|
# RLS defaults (apenas pra `python ask.py` na CLI; servidor web extrai de query string)
|
||||||
|
RLS_PROGRAM_ID=
|
||||||
|
RLS_STORE_ID=
|
||||||
|
RLS_USER_ID=
|
||||||
60
.gitea/workflows/cd.yml
Normal file
60
.gitea/workflows/cd.yml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
name: CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_BASE: ${{ secrets.AR_LOCATION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT }}/${{ secrets.AR_REPO }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ hashFiles('Dockerfile') != '' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Auth GCP
|
||||||
|
uses: google-github-actions/auth@v2
|
||||||
|
with:
|
||||||
|
credentials_json: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|
||||||
|
- name: Setup gcloud
|
||||||
|
uses: google-github-actions/setup-gcloud@v2
|
||||||
|
with:
|
||||||
|
project_id: ${{ secrets.GCP_PROJECT }}
|
||||||
|
|
||||||
|
- name: Configure Docker auth
|
||||||
|
run: gcloud auth configure-docker ${{ secrets.AR_LOCATION }}-docker.pkg.dev --quiet
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: |
|
||||||
|
IMG="${IMAGE_BASE}/${{ gitea.event.repository.name }}:lab-${{ gitea.run_number }}"
|
||||||
|
docker build --platform=linux/amd64 -t "$IMG" .
|
||||||
|
echo "IMG=$IMG" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Push image (apenas em push pra master/main)
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: docker push "$IMG"
|
||||||
|
|
||||||
|
- name: Deploy hml2 (apenas em push pra master/main)
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
gcloud container clusters get-credentials ${{ secrets.GKE_CLUSTER }} --region ${{ secrets.GKE_REGION }} --project ${{ secrets.GCP_PROJECT }}
|
||||||
|
NS=${{ secrets.K8S_NAMESPACE }}
|
||||||
|
|
||||||
|
# 1) Aplica manifests (idempotente — cria PVC/Service/Ingress/Deployment se faltarem)
|
||||||
|
if [ -d k8s ]; then
|
||||||
|
kubectl apply -n "$NS" -f k8s/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) Atualiza image
|
||||||
|
DEPLOYMENT="${{ gitea.event.repository.name }}-deployment"
|
||||||
|
if kubectl get deployment "$DEPLOYMENT" -n "$NS" >/dev/null 2>&1; then
|
||||||
|
CONTAINER=$(kubectl get deployment "$DEPLOYMENT" -n "$NS" -o jsonpath='{.spec.template.spec.containers[0].name}')
|
||||||
|
kubectl set image deployment/"$DEPLOYMENT" -n "$NS" "$CONTAINER=$IMG"
|
||||||
|
kubectl rollout status deployment/"$DEPLOYMENT" -n "$NS" --timeout=300s
|
||||||
|
else
|
||||||
|
echo "Deployment $DEPLOYMENT não existe no ns $NS — pulei set image (provavelmente é o 1º deploy e o kubectl apply acabou de criar)"
|
||||||
|
fi
|
||||||
29
AI-HISTORY.md
Normal file
29
AI-HISTORY.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Histórico de mudanças com IA
|
||||||
|
|
||||||
|
Registro das alterações feitas neste repositório com apoio de IA (Claude), incluindo tempo aproximado. Ordem cronológica — **mais recente no topo**.
|
||||||
|
|
||||||
|
## Formato de entrada
|
||||||
|
|
||||||
|
```
|
||||||
|
### YYYY-MM-DD HH:MM — Título curto
|
||||||
|
|
||||||
|
- **Tempo:** ~X min
|
||||||
|
- **Prompt:** resumo do pedido
|
||||||
|
- **Mudanças:** o que foi feito
|
||||||
|
- **Artefatos:** branch, PR #, commit hash
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2026-05-05 ~17:30 — Bootstrap de deploy no hml2
|
||||||
|
|
||||||
|
- **Tempo:** ~30 min
|
||||||
|
- **Prompt:** "fazer pull do repo vanna-clubpetro e fazer o deploy"
|
||||||
|
- **Mudanças:**
|
||||||
|
- Criado `Dockerfile` multi-stage: stage 1 (Node 18) clona vanna-ai/vanna upstream pinned em `365d061` e builda o webcomponent (~7.5MB); stage 2 (Python 3.11-slim) instala vanna editable + `requirements.txt` + código do app. CMD com `--workers 1` (constraint do ChromaDB SQLite).
|
||||||
|
- Criado `requirements.txt` (clickhouse-connect, chromadb, openai, fastapi, uvicorn, pandas, plotly, pydantic, python-dotenv).
|
||||||
|
- Criado `.env.example` baseado em `docs/deploy.md`.
|
||||||
|
- Criado `.dockerignore`.
|
||||||
|
- Criado `k8s/`: `deployment.yaml` (1 réplica, strategy Recreate, PVC montado em `/app/chroma_db`, `/app/data_storage` e `~/.cache/chroma`, probes TCP), `service.yaml` (ClusterIP 80→8765), `ingress.yaml` (`lab.clubpetro.com/api/vanna/*` com timeouts altos pra SSE/WebSocket), `pvc.yaml` (5Gi standard-rwo).
|
||||||
|
- Criado `.gitea/workflows/cd.yml` seguindo template do lab — PR só valida build, merge faz `kubectl apply -f k8s/` (idempotente) + `kubectl set image` na tag `lab-<run_number>`.
|
||||||
|
- **Artefatos:** branch `chore/initial-deploy`, PR a abrir em `clubpetro-lab/vanna-clubpetro`. Pendente: criar Secret K8s `vanna-clubpetro-secret` com `OPENAI_API_KEY` + `CLICKHOUSE_*` antes do pod subir.
|
||||||
60
Dockerfile
Normal file
60
Dockerfile
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# syntax=docker/dockerfile:1.6
|
||||||
|
# Multi-stage:
|
||||||
|
# 1) Clona vanna-ai/vanna upstream (commit pinned em 365d061) e builda o webcomponent (~7.5MB)
|
||||||
|
# 2) Imagem Python 3.11 com vanna editable + requirements + código do app
|
||||||
|
ARG VANNA_UPSTREAM_COMMIT=365d0617c1a4567ffee1b19b40c27feb4206bfcf
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Stage 1 — webcomponent (Node)
|
||||||
|
# ============================================================================
|
||||||
|
FROM node:18-bookworm-slim AS webcomponent
|
||||||
|
ARG VANNA_UPSTREAM_COMMIT
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
WORKDIR /vanna
|
||||||
|
RUN git init -q \
|
||||||
|
&& git remote add origin https://github.com/vanna-ai/vanna.git \
|
||||||
|
&& git fetch --depth 50 origin "$VANNA_UPSTREAM_COMMIT" \
|
||||||
|
&& git checkout FETCH_HEAD
|
||||||
|
WORKDIR /vanna/frontends/webcomponent
|
||||||
|
RUN npm install --no-audit --no-fund --loglevel=error \
|
||||||
|
&& npm run build \
|
||||||
|
&& ls -lh dist/vanna-components.js
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Stage 2 — runtime Python
|
||||||
|
# ============================================================================
|
||||||
|
FROM python:3.11-slim-bookworm
|
||||||
|
ARG VANNA_UPSTREAM_COMMIT
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PIP_NO_CACHE_DIR=1 \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
git ca-certificates curl build-essential \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copia o vanna upstream + bundle já buildado do stage 1
|
||||||
|
COPY --from=webcomponent /vanna /app/vanna
|
||||||
|
|
||||||
|
# Instala vanna editable
|
||||||
|
RUN pip install -e ./vanna
|
||||||
|
|
||||||
|
# Instala deps do app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Código do app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# data dirs
|
||||||
|
RUN mkdir -p /app/chroma_db /app/data_storage
|
||||||
|
|
||||||
|
EXPOSE 8765
|
||||||
|
|
||||||
|
# `--workers 1` é OBRIGATÓRIO — múltiplos workers corrompem o SQLite do Chroma
|
||||||
|
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8765", "--workers", "1"]
|
||||||
65
k8s/deployment.yaml
Normal file
65
k8s/deployment.yaml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: vanna-clubpetro-deployment
|
||||||
|
labels:
|
||||||
|
app: vanna-clubpetro
|
||||||
|
spec:
|
||||||
|
replicas: 1 # ChromaDB SQLite-based — múltiplas réplicas corrompem o vector store
|
||||||
|
strategy:
|
||||||
|
type: Recreate # com PVC ReadWriteOnce não dá pra ter 2 pods montando ao mesmo tempo
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: vanna-clubpetro
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: vanna-clubpetro
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: vanna-clubpetro
|
||||||
|
image: us-central1-docker.pkg.dev/corepetro/clubpetro-lab/vanna-clubpetro:lab-latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8765
|
||||||
|
env:
|
||||||
|
- name: VANNA_CORS_ORIGINS
|
||||||
|
value: "https://lab.clubpetro.com,https://homologation.clubpetro.com"
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: vanna-clubpetro-secret
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "200m"
|
||||||
|
memory: "1Gi"
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: "2Gi"
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8765
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8765
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /app/chroma_db
|
||||||
|
subPath: chroma_db
|
||||||
|
- name: data
|
||||||
|
mountPath: /app/data_storage
|
||||||
|
subPath: data_storage
|
||||||
|
- name: data
|
||||||
|
mountPath: /root/.cache/chroma
|
||||||
|
subPath: chroma-onnx-cache
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: vanna-clubpetro-data
|
||||||
32
k8s/ingress.yaml
Normal file
32
k8s/ingress.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: vanna-clubpetro
|
||||||
|
labels:
|
||||||
|
app: vanna-clubpetro
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||||
|
# SSE/WebSocket precisam de timeout grande pra streaming não ser cortado
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: lab.clubpetro.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /api/vanna(/|$)(.*)
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: vanna-clubpetro
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- lab.clubpetro.com
|
||||||
|
secretName: lab-tls
|
||||||
13
k8s/pvc.yaml
Normal file
13
k8s/pvc.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: vanna-clubpetro-data
|
||||||
|
labels:
|
||||||
|
app: vanna-clubpetro
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
storageClassName: standard-rwo
|
||||||
15
k8s/service.yaml
Normal file
15
k8s/service.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: vanna-clubpetro
|
||||||
|
labels:
|
||||||
|
app: vanna-clubpetro
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: vanna-clubpetro
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8765
|
||||||
|
protocol: TCP
|
||||||
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Runtime deps. O `vanna` editable é instalado via `pip install -e ./vanna` no Dockerfile.
|
||||||
|
clickhouse-connect>=0.7,<1.0
|
||||||
|
chromadb>=0.4,<1.0
|
||||||
|
openai>=1.0
|
||||||
|
python-dotenv>=1.0
|
||||||
|
fastapi>=0.110
|
||||||
|
uvicorn[standard]>=0.27
|
||||||
|
pandas>=2.0
|
||||||
|
plotly>=5.18
|
||||||
|
pydantic>=2.5
|
||||||
Loading…
Reference in New Issue
Block a user