fix: cleanup k8s configs, add deploy.sh, remove hardcoded secrets

- Add one-click deploy script (k8s/deploy.sh) for test + prod
- Add startupProbe to backend (60x10s = 10min grace for ML model loading)
- Add configmap.yaml and secret.yaml (envsubst from backend/.env)
- Fix image URLs: add registry.plfai.cn prefix
- Fix frontend: API base URL relative (/api/v1), login-required mode
- Remove hardcoded localhost references from frontend
- Clean broken kustomize overlays
- Add nodeAffinity for backend→k8smaster, frontend→plf-cvm/k8smaster
- Remove dead ReplicaSets and stale pods during deploy
This commit is contained in:
2026-06-16 14:15:31 +08:00
parent 23f07dd3a7
commit 1d12cb574e
15 changed files with 230 additions and 60 deletions
Executable
+182
View File
@@ -0,0 +1,182 @@
#!/bin/bash
# GraphRAG Studio — 一键部署脚本 (test + prod 双环境)
# 用法: bash k8s/deploy.sh
# 前提: k3s 集群已运行, docker 可用, registry.plfai.cn 可推送
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
REGISTRY="registry.plfai.cn"
BACKEND_IMAGE="${REGISTRY}/graphrag-backend"
FRONTEND_IMAGE="${REGISTRY}/graphrag-frontend"
KUBECTL="k3s kubectl"
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}[$(date +%H:%M:%S)]${NC} $1"; }
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
err() { echo -e "${RED}[ERR]${NC} $1"; exit 1; }
# ──────────────── 加载密钥 ────────────────
if [ -f "$PROJECT_DIR/backend/.env" ]; then
set -a; source "$PROJECT_DIR/backend/.env"; set +a
else
err "请先创建 backend/.env(参考 backend/.env.example"
fi
if [ -z "$DEEPSEEK_API_KEY" ] || [ "$DEEPSEEK_API_KEY" = "sk-your-key-here" ]; then
err "请在 backend/.env 中填入 DEEPSEEK_API_KEY"
fi
# ──────────────── 1. 构建镜像 ────────────────
log "Building backend image..."
cd "$PROJECT_DIR"
docker build --network host -f backend/Dockerfile -t "${BACKEND_IMAGE}:latest" . || err "backend build"
docker tag "${BACKEND_IMAGE}:latest" "${BACKEND_IMAGE}:v1"
ok "Backend built"
log "Building frontend image..."
docker build --network host -f frontend/Dockerfile -t "${FRONTEND_IMAGE}:latest" . || err "frontend build"
docker tag "${FRONTEND_IMAGE}:latest" "${FRONTEND_IMAGE}:v1"
ok "Frontend built"
# ──────────────── 2. 推送镜像 ────────────────
log "Pushing images to registry..."
docker push "${BACKEND_IMAGE}:latest" "${BACKEND_IMAGE}:v1" >/dev/null 2>&1
docker push "${FRONTEND_IMAGE}:latest" "${FRONTEND_IMAGE}:v1" >/dev/null 2>&1
ok "Images pushed"
# ──────────────── 3. 导入 containerd ────────────────
log "Importing into containerd..."
docker save "${BACKEND_IMAGE}:v1" | k3s ctr images import - >/dev/null 2>&1
docker save "${FRONTEND_IMAGE}:v1" | k3s ctr images import - >/dev/null 2>&1
ok "Containerd loaded"
# ──────────────── 4. 部署 Kubernetes ────────────────
deploy_env() {
local NS="$1" label="$2"
log "Deploying ${label} (ns: ${NS})..."
# 创建 namespace
$KUBECTL create namespace "$NS" --dry-run=client -o yaml | $KUBECTL apply -f - 2>/dev/null
# apply ConfigMap
$KUBECTL apply -n "$NS" -f "$SCRIPT_DIR/base/configmap.yaml" 2>/dev/null
# envsubst 注入密钥 → apply Secret
envsubst < "$SCRIPT_DIR/base/secret.yaml" | $KUBECTL apply -n "$NS" -f - 2>/dev/null
# apply 其余 base 资源
for f in "$SCRIPT_DIR/base"/*.yaml; do
case "$(basename "$f")" in
kustomization.yaml|configmap.yaml|secret.yaml) continue ;;
*) $KUBECTL apply -n "$NS" -f "$f" 2>/dev/null ;;
esac
done
# 修正镜像 tag
$KUBECTL set image deployment/backend "backend=${BACKEND_IMAGE}:v1" -n "$NS" 2>/dev/null
$KUBECTL set image deployment/frontend "frontend=${FRONTEND_IMAGE}:v1" -n "$NS" 2>/dev/null
# 修正 backend command(镜像 CMD 可能为 sleep
$KUBECTL patch deployment backend -n "$NS" --type json -p '[
{"op":"add","path":"/spec/template/spec/containers/0/command","value":["/opt/venv/bin/python","-m","uvicorn","main:app","--host","0.0.0.0","--port","8000"]}
]' 2>/dev/null
# 节点亲和性
$KUBECTL patch deployment backend -n "$NS" --type merge -p \
'{"spec":{"template":{"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"In","values":["k8smaster"]}]}]}}}}}}}' 2>/dev/null
$KUBECTL patch deployment frontend -n "$NS" --type merge -p \
'{"spec":{"template":{"spec":{"affinity":{"nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"In","values":["plf-cvm-worker","k8smaster"]}]}]}}}}}}}' 2>/dev/null
ok "${label} deployed"
}
deploy_env "graphrag-test" "TEST"
deploy_env "graphrag-prod" "PROD"
# ──────────────── 5. 等待就绪 ────────────────
log "Waiting for pods..."
for ns in graphrag-test graphrag-prod; do
$KUBECTL wait --for=condition=ready pod -l component=backend -n "$ns" --timeout=360s 2>/dev/null && ok "${ns} backend ready" || log " ${ns} backend still starting"
$KUBECTL wait --for=condition=ready pod -l component=frontend -n "$ns" --timeout=60s 2>/dev/null && ok "${ns} frontend ready" || log " ${ns} frontend still starting"
done
# ──────────────── 6. 配置 nginx ────────────────
log "Setting up nginx..."
setup_nginx() {
local domain="$1" frontend_ip="$2" backend_ip="$3"
cat > "/etc/nginx/conf.d/${domain}.conf" << NGINX
server {
listen 443 ssl;
server_name ${domain};
ssl_certificate /etc/letsencrypt/live/plfai.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/plfai.cn/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location /api/ {
proxy_pass http://${backend_ip}:8000;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 120s;
proxy_buffering off;
}
location / {
proxy_pass http://${frontend_ip}:80;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
server {
listen 80;
server_name ${domain};
return 301 https://\$server_name\$request_uri;
}
NGINX
ok "nginx: ${domain}"
}
TEST_FE=$($KUBECTL get svc frontend -n graphrag-test -o jsonpath='{.spec.clusterIP}')
TEST_BE=$($KUBECTL get svc backend -n graphrag-test -o jsonpath='{.spec.clusterIP}')
PROD_FE=$($KUBECTL get svc frontend -n graphrag-prod -o jsonpath='{.spec.clusterIP}')
PROD_BE=$($KUBECTL get svc backend -n graphrag-prod -o jsonpath='{.spec.clusterIP}')
setup_nginx "test-graphrag.plfai.cn" "$TEST_FE" "$TEST_BE"
setup_nginx "test-graphrag-backend.plfai.cn" "$TEST_BE" "$TEST_BE"
setup_nginx "graphrag.plfai.cn" "$PROD_FE" "$PROD_BE"
setup_nginx "graphrag-backend.plfai.cn" "$PROD_BE" "$PROD_BE"
nginx -t >/dev/null 2>&1 && nginx -s reload >/dev/null 2>&1
ok "nginx reloaded"
# ──────────────── 7. 验证 ────────────────
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} GraphRAG Studio 部署完成${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo "Test: https://test-graphrag.plfai.cn"
echo "Prod: https://graphrag.plfai.cn"
echo ""
for url in \
"https://test-graphrag.plfai.cn" \
"https://graphrag.plfai.cn" \
"https://test-graphrag-backend.plfai.cn/api/v1/health" \
"https://graphrag-backend.plfai.cn/api/v1/health"; do
code=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 5 "$url" 2>/dev/null || echo "ERR")
echo " $url → HTTP $code"
done
echo ""
echo -e "${GREEN}Done.${NC}"