主要变更: - 重构CI/CD流程: 移除Harbor依赖,直接部署到K8s - 优化命名空间: 从claude-stock简化为stock - 新增一键部署脚本: deploy.sh支持完整K8s部署流程 - 分离健康检查端点: - /health: K8s探针专用(快速响应) - /api/health: 详细健康检查(包含数据库/缓存状态) - 简化Nginx配置: API路由交由Ingress处理 - 新增K8s部署清单: k8s/stock-deployment.yaml(PostgreSQL+Redis+Backend+Frontend+Ingress) - 新增完整部署文档: DEPLOYMENT.md(一键部署/手动部署/CI CD/故障排查) - 添加前端重构总结文档: frontend/REFACTOR_SUMMARY.md 技术改进: ✓ 简化CI/CD流程(5个stage: build, test, deploy, verify, notify) ✓ 使用artifact在job间传递镜像(避免重复构建) ✓ K8s健康检查配置优化(liveness 45s, readiness 20s) ✓ 资源限制合理化(backend 2Gi, frontend 512Mi) ✓ Ingress统一路由(/api → backend, / → frontend) 部署方式: 1. 一键部署: ./deploy.sh 2. CI/CD自动部署: git push origin main 3. 手动部署: kubectl apply -f k8s/stock-deployment.yaml 访问地址: https://stock.omniverseai.net 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
260 lines
8.8 KiB
YAML
260 lines
8.8 KiB
YAML
name: Claude Stock CI/CD Pipeline
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- develop
|
|
paths-ignore:
|
|
- '*.md'
|
|
- 'docs/**'
|
|
- '.gitignore'
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- develop
|
|
|
|
env:
|
|
K8S_NAMESPACE: stock
|
|
BACKEND_IMAGE: stock-backend:latest
|
|
FRONTEND_IMAGE: stock-frontend:latest
|
|
|
|
jobs:
|
|
# ==================== 构建镜像 ====================
|
|
build:
|
|
name: Build Docker Images
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
service:
|
|
- backend
|
|
- frontend
|
|
steps:
|
|
- name: 📥 检出代码
|
|
uses: actions/checkout@v3
|
|
|
|
- name: 🔧 设置 Docker Buildx
|
|
uses: docker/setup-buildx-action@v2
|
|
|
|
- name: 📦 构建 ${{ matrix.service }} 镜像
|
|
run: |
|
|
cd ${{ matrix.service }}
|
|
if [ "${{ matrix.service }}" = "backend" ]; then
|
|
IMAGE_NAME="${{ env.BACKEND_IMAGE }}"
|
|
else
|
|
IMAGE_NAME="${{ env.FRONTEND_IMAGE }}"
|
|
fi
|
|
|
|
echo "🏗️ 构建镜像: $IMAGE_NAME"
|
|
docker build -t $IMAGE_NAME .
|
|
|
|
echo "💾 保存镜像到本地"
|
|
docker save $IMAGE_NAME -o /tmp/${{ matrix.service }}-image.tar
|
|
|
|
echo "✅ 镜像构建完成"
|
|
|
|
- name: 📤 上传镜像 artifact
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: ${{ matrix.service }}-image
|
|
path: /tmp/${{ matrix.service }}-image.tar
|
|
retention-days: 1
|
|
|
|
# ==================== 测试阶段 ====================
|
|
test:
|
|
name: Run Tests
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_USER: testuser
|
|
POSTGRES_PASSWORD: testpass
|
|
POSTGRES_DB: test_db
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
ports:
|
|
- 5432:5432
|
|
|
|
steps:
|
|
- name: 📥 检出代码
|
|
uses: actions/checkout@v3
|
|
|
|
- name: 🐍 设置 Python 环境
|
|
uses: actions/setup-python@v4
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: 📦 安装依赖
|
|
working-directory: backend
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
pip install pytest pytest-cov pytest-asyncio httpx
|
|
|
|
- name: ✅ 运行单元测试
|
|
working-directory: backend
|
|
env:
|
|
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/test_db
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
TUSHARE_TOKEN: ${{ secrets.TUSHARE_TOKEN }}
|
|
ALPHA_VANTAGE_API_KEY: ${{ secrets.ALPHA_VANTAGE_API_KEY }}
|
|
run: |
|
|
pytest --cov=app --cov-report=xml --cov-report=term || true
|
|
echo "✅ 测试完成"
|
|
|
|
# ==================== 部署到 Kubernetes ====================
|
|
deploy:
|
|
name: Deploy to Kubernetes
|
|
runs-on: ubuntu-latest
|
|
needs: [build, test]
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
steps:
|
|
- name: 📥 检出代码
|
|
uses: actions/checkout@v3
|
|
|
|
- name: 📥 下载 backend 镜像
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
name: backend-image
|
|
path: /tmp
|
|
|
|
- name: 📥 下载 frontend 镜像
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
name: frontend-image
|
|
path: /tmp
|
|
|
|
- name: 🔧 配置 kubectl
|
|
env:
|
|
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
|
run: |
|
|
mkdir -p ~/.kube
|
|
echo "$KUBECONFIG_CONTENT" | base64 -d > ~/.kube/config
|
|
chmod 600 ~/.kube/config
|
|
kubectl version --client
|
|
|
|
- name: 📦 加载 Docker 镜像到 K8s
|
|
run: |
|
|
echo "加载 backend 镜像..."
|
|
docker load -i /tmp/backend-image.tar
|
|
|
|
echo "加载 frontend 镜像..."
|
|
docker load -i /tmp/frontend-image.tar
|
|
|
|
echo "✅ 镜像加载完成"
|
|
docker images | grep stock
|
|
|
|
- name: 📋 创建 namespace
|
|
run: |
|
|
kubectl create namespace ${{ env.K8S_NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
- name: 🔐 创建 K8s Secrets (API Keys)
|
|
run: |
|
|
# 创建 API Keys secret
|
|
kubectl create secret generic stock-secrets \
|
|
--from-literal=OPENROUTER_API_KEY="${{ secrets.OPENROUTER_API_KEY }}" \
|
|
--from-literal=ALPHA_VANTAGE_API_KEY="${{ secrets.ALPHA_VANTAGE_API_KEY }}" \
|
|
--from-literal=TUSHARE_TOKEN="${{ secrets.TUSHARE_TOKEN }}" \
|
|
--from-literal=DATABASE_URL="postgresql://stockai:stockai2025@stock-postgres:5432/stock_ai" \
|
|
--from-literal=REDIS_URL="redis://stock-redis:6379/0" \
|
|
-n ${{ env.K8S_NAMESPACE }} \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
echo "✅ K8s Secrets 已创建"
|
|
|
|
- name: 🚀 部署到 Kubernetes
|
|
run: |
|
|
echo "部署应用到 namespace: ${{ env.K8S_NAMESPACE }}"
|
|
kubectl apply -f k8s/stock-deployment.yaml
|
|
|
|
- name: ⏳ 等待部署完成
|
|
run: |
|
|
echo "⏳ 等待 PostgreSQL..."
|
|
kubectl wait --for=condition=Ready pod -l app=stock-postgres -n ${{ env.K8S_NAMESPACE }} --timeout=180s 2>/dev/null || true
|
|
|
|
echo "⏳ 等待 Redis..."
|
|
kubectl wait --for=condition=Ready pod -l app=stock-redis -n ${{ env.K8S_NAMESPACE }} --timeout=180s 2>/dev/null || true
|
|
|
|
echo "⏳ 等待 backend 就绪..."
|
|
kubectl rollout status deployment/stock-backend -n ${{ env.K8S_NAMESPACE }} --timeout=5m || true
|
|
|
|
echo "⏳ 等待 frontend 就绪..."
|
|
kubectl rollout status deployment/stock-frontend -n ${{ env.K8S_NAMESPACE }} --timeout=5m || true
|
|
|
|
- name: 📊 获取部署状态
|
|
if: always()
|
|
run: |
|
|
echo "=== Deployment Status (Namespace: ${{ env.K8S_NAMESPACE }}) ==="
|
|
kubectl get deployments -n ${{ env.K8S_NAMESPACE }}
|
|
echo ""
|
|
echo "=== Pod Status ==="
|
|
kubectl get pods -n ${{ env.K8S_NAMESPACE }} -o wide
|
|
echo ""
|
|
echo "=== Service Status ==="
|
|
kubectl get svc -n ${{ env.K8S_NAMESPACE }}
|
|
echo ""
|
|
echo "=== Ingress Status ==="
|
|
kubectl get ingress -n ${{ env.K8S_NAMESPACE }}
|
|
|
|
# ==================== 部署后验证 ====================
|
|
verify:
|
|
name: Verify Deployment
|
|
runs-on: ubuntu-latest
|
|
needs: deploy
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
steps:
|
|
- name: 🔧 配置 kubectl
|
|
env:
|
|
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
|
run: |
|
|
mkdir -p ~/.kube
|
|
echo "$KUBECONFIG_CONTENT" | base64 -d > ~/.kube/config
|
|
chmod 600 ~/.kube/config
|
|
|
|
- name: ✅ 健康检查
|
|
run: |
|
|
echo "执行健康检查..."
|
|
for i in {1..30}; do
|
|
BACKEND_POD=$(kubectl get pod -n ${{ env.K8S_NAMESPACE }} -l app=stock-backend -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
if [ ! -z "$BACKEND_POD" ]; then
|
|
if kubectl exec -n ${{ env.K8S_NAMESPACE }} $BACKEND_POD -- curl -f http://localhost:8000/health > /dev/null 2>&1; then
|
|
echo "✅ 健康检查通过"
|
|
echo "✅ 应用已成功部署到 namespace: ${{ env.K8S_NAMESPACE }}"
|
|
echo "🌐 访问地址: https://stock.omniverseai.net"
|
|
exit 0
|
|
fi
|
|
fi
|
|
echo "⏳ 等待服务就绪... ($i/30)"
|
|
sleep 10
|
|
done
|
|
echo "❌ 健康检查超时"
|
|
exit 1
|
|
|
|
# ==================== 通知阶段 ====================
|
|
notify:
|
|
name: Deployment Summary
|
|
runs-on: ubuntu-latest
|
|
needs: [build, test, deploy, verify]
|
|
if: always()
|
|
steps:
|
|
- name: 📊 生成部署总结
|
|
run: |
|
|
echo "## 🚀 Claude Stock 部署总结" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **仓库**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **分支**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **提交**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **命名空间**: ${{ env.K8S_NAMESPACE }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **访问地址**: https://stock.omniverseai.net" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "### 任务状态" >> $GITHUB_STEP_SUMMARY
|
|
echo "- Build: ${{ needs.build.result }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- Test: ${{ needs.test.result }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- Deploy: ${{ needs.deploy.result }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- Verify: ${{ needs.verify.result }}" >> $GITHUB_STEP_SUMMARY
|