chore: bootstrap deploy no hml2 (Dockerfile + k8s + workflow CD)
All checks were successful
CD / build (pull_request) Has been skipped
All checks were successful
CD / build (pull_request) Has been skipped
- Dockerfile multi-stage Node 18 (webcomponent) + Python 3.11 - vanna upstream pinned em 365d0617c1a4567ffee1b19b40c27feb4206bfcf - requirements.txt + .env.example + .dockerignore - k8s/: deployment (1 replica, PVC, Recreate), service, ingress (SSE/WS timeouts), PVC 5Gi - .gitea/workflows/cd.yml seguindo template do lab Pendência: criar Secret K8s vanna-clubpetro-secret com OPENAI_API_KEY + CLICKHOUSE_*
This commit is contained in:
parent
1d152c0dce
commit
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