ci: add GitHub Actions CI/CD pipeline
- Add complete CI/CD workflow with test, build, and deploy stages - Add Dockerfile with health check and non-root user - Add comprehensive documentation and setup guides - Add automated scripts for secrets setup and local verification - Configure Harbor image registry integration - Configure automatic deployment to ljwx-deploy repository Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# GitHub Actions CI/CD 文档索引
|
||||
|
||||
## 📚 文档列表
|
||||
|
||||
### 🚀 快速开始
|
||||
- **[QUICKSTART.md](QUICKSTART.md)** - 快速参考指南
|
||||
- 本地验证
|
||||
- 触发 CI/CD
|
||||
- 常用命令
|
||||
- 故障排查
|
||||
|
||||
### 🔧 设置指南
|
||||
- **[SECRETS_SETUP_GUIDE.md](SECRETS_SETUP_GUIDE.md)** - Secrets 设置详细指南
|
||||
- 图文步骤
|
||||
- 交互式脚本
|
||||
- 验证方法
|
||||
- 常见问题
|
||||
|
||||
- **[SETUP.md](SETUP.md)** - 完整设置指南
|
||||
- 前置条件
|
||||
- Secrets 配置
|
||||
- 验证步骤
|
||||
|
||||
### 📖 架构文档
|
||||
- **[README.md](README.md)** - CI/CD 架构文档
|
||||
- K8s 集群架构
|
||||
- CI/CD 完整流程
|
||||
- ljwx-deploy 仓库结构
|
||||
- Deployment 配置
|
||||
- 监控和可观测性
|
||||
|
||||
### 🛠️ 脚本工具
|
||||
- **[../../scripts/setup-github-secrets.sh](../../scripts/setup-github-secrets.sh)** - Secrets 设置脚本
|
||||
- 交互式设置
|
||||
- 自动验证
|
||||
- 测试连接
|
||||
|
||||
- **[../../scripts/verify-ci.sh](../../scripts/verify-ci.sh)** - 本地 CI 验证脚本
|
||||
- Linter 检查
|
||||
- 运行测试
|
||||
- Docker 构建
|
||||
- 镜像测试
|
||||
|
||||
---
|
||||
|
||||
## 🎯 使用流程
|
||||
|
||||
### 首次设置
|
||||
|
||||
```bash
|
||||
# 1. 设置 GitHub Secrets
|
||||
./scripts/setup-github-secrets.sh
|
||||
|
||||
# 2. 本地验证
|
||||
./scripts/verify-ci.sh
|
||||
|
||||
# 3. 提交代码
|
||||
git add .
|
||||
git commit -m "ci: add CI/CD pipeline"
|
||||
git push origin main
|
||||
|
||||
# 4. 查看运行状态
|
||||
gh run watch
|
||||
```
|
||||
|
||||
### 日常开发
|
||||
|
||||
```bash
|
||||
# 1. 本地验证
|
||||
./scripts/verify-ci.sh --skip-docker
|
||||
|
||||
# 2. 提交代码
|
||||
git add .
|
||||
git commit -m "feat: add new feature"
|
||||
git push origin main
|
||||
|
||||
# 3. 查看运行状态
|
||||
gh run list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 检查清单
|
||||
|
||||
### 首次设置
|
||||
- [ ] 阅读 [SECRETS_SETUP_GUIDE.md](SECRETS_SETUP_GUIDE.md)
|
||||
- [ ] 运行 `./scripts/setup-github-secrets.sh`
|
||||
- [ ] 验证 secrets: `gh secret list`
|
||||
- [ ] 运行 `./scripts/verify-ci.sh`
|
||||
- [ ] 推送代码触发 CI/CD
|
||||
- [ ] 检查 GitHub Actions 运行
|
||||
- [ ] 验证 Harbor 镜像推送
|
||||
- [ ] 检查 ljwx-deploy PR
|
||||
|
||||
### 日常开发
|
||||
- [ ] 本地运行测试: `cd backend && pytest -v`
|
||||
- [ ] 本地运行 linter: `cd backend && ruff check .`
|
||||
- [ ] 运行本地验证: `./scripts/verify-ci.sh`
|
||||
- [ ] 提交代码
|
||||
- [ ] 检查 GitHub Actions
|
||||
- [ ] 合并 PR 后验证部署
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关链接
|
||||
|
||||
### GitHub
|
||||
- **仓库**: https://github.com/BrunoGao/ljwx-shiti
|
||||
- **Actions**: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
- **Settings**: https://github.com/BrunoGao/ljwx-shiti/settings
|
||||
|
||||
### 部署
|
||||
- **ljwx-deploy**: https://github.com/BrunoGao/ljwx-deploy
|
||||
- **Harbor**: https://harbor.omniverseai.net
|
||||
- **ArgoCD**: http://argocd.omniverseai.net
|
||||
|
||||
### 文档
|
||||
- **项目文档**: [../../docs/](../../docs/)
|
||||
- **API 文档**: [../../docs/API.md](../../docs/API.md)
|
||||
- **架构文档**: [../../docs/architecture.md](../../docs/architecture.md)
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-02-26
|
||||
@@ -0,0 +1,219 @@
|
||||
# CI/CD 快速参考
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 本地验证(推荐先执行)
|
||||
|
||||
```bash
|
||||
# 完整验证(包括测试和 Docker 构建)
|
||||
./scripts/verify-ci.sh
|
||||
|
||||
# 只验证 Docker 构建
|
||||
./scripts/verify-ci.sh --skip-tests
|
||||
|
||||
# 只运行测试
|
||||
./scripts/verify-ci.sh --skip-docker
|
||||
```
|
||||
|
||||
### 2. 配置 GitHub Secrets
|
||||
|
||||
访问: https://github.com/BrunoGao/ljwx-shiti/settings/secrets/actions
|
||||
|
||||
添加以下 Secrets:
|
||||
- `HARBOR_USERNAME`: Harbor 用户名
|
||||
- `HARBOR_PASSWORD`: Harbor 密码
|
||||
- `DEPLOY_REPO_TOKEN`: GitHub Personal Access Token
|
||||
|
||||
详细步骤见: [SETUP.md](.github/workflows/SETUP.md)
|
||||
|
||||
### 3. 触发 CI/CD
|
||||
|
||||
```bash
|
||||
# 提交代码
|
||||
git add .
|
||||
git commit -m "feat: add new feature"
|
||||
git push origin main
|
||||
|
||||
# 查看运行状态
|
||||
open https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Workflow 流程
|
||||
|
||||
```
|
||||
Push to main/master
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ 1. Test │ 运行测试、linter、coverage
|
||||
│ (20 min) │ ✅ 通过才继续
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ 2. Build │ 构建 Docker 镜像
|
||||
│ (30 min) │ 推送到 Harbor
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ 3. Deploy │ 更新 ljwx-deploy
|
||||
│ (10 min) │ 创建 PR
|
||||
└────────┬────────┘
|
||||
↓
|
||||
ArgoCD 自动部署
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常用命令
|
||||
|
||||
### 本地开发
|
||||
|
||||
```bash
|
||||
# 启动本地环境
|
||||
docker-compose up -d
|
||||
|
||||
# 运行测试
|
||||
cd backend && pytest -v
|
||||
|
||||
# 运行 linter
|
||||
cd backend && ruff check .
|
||||
|
||||
# 格式化代码
|
||||
cd backend && ruff format .
|
||||
```
|
||||
|
||||
### Docker 操作
|
||||
|
||||
```bash
|
||||
# 构建镜像
|
||||
docker build -t ljwx-shiti:dev -f backend/Dockerfile .
|
||||
|
||||
# 运行容器
|
||||
docker run -p 8000:8000 ljwx-shiti:dev
|
||||
|
||||
# 查看日志
|
||||
docker logs -f <container-id>
|
||||
|
||||
# 进入容器
|
||||
docker exec -it <container-id> /bin/bash
|
||||
```
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```bash
|
||||
# 查看 workflow 状态
|
||||
gh run list --workflow=ci.yml
|
||||
|
||||
# 查看最新运行的日志
|
||||
gh run view --log
|
||||
|
||||
# 手动触发 workflow
|
||||
gh workflow run ci.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 故障排查
|
||||
|
||||
### 测试失败
|
||||
|
||||
```bash
|
||||
# 本地运行测试
|
||||
cd backend
|
||||
pytest -v --tb=short
|
||||
|
||||
# 查看覆盖率
|
||||
pytest --cov=app --cov-report=html
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
### Docker 构建失败
|
||||
|
||||
```bash
|
||||
# 查看构建日志
|
||||
docker build -t ljwx-shiti:debug -f backend/Dockerfile . --progress=plain
|
||||
|
||||
# 检查依赖
|
||||
cd backend
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### Harbor 推送失败
|
||||
|
||||
```bash
|
||||
# 测试 Harbor 登录
|
||||
docker login harbor.omniverseai.net
|
||||
|
||||
# 手动推送测试
|
||||
docker tag ljwx-shiti:dev harbor.omniverseai.net/ljwx/ljwx-shiti:test
|
||||
docker push harbor.omniverseai.net/ljwx/ljwx-shiti:test
|
||||
```
|
||||
|
||||
### Deploy PR 创建失败
|
||||
|
||||
```bash
|
||||
# 检查 token 权限
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://api.github.com/repos/BrunoGao/ljwx-deploy
|
||||
|
||||
# 手动克隆测试
|
||||
git clone https://x-access-token:YOUR_TOKEN@github.com/BrunoGao/ljwx-deploy.git
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 监控
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
- **Actions 页面**: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
- **Workflow 文件**: `.github/workflows/ci.yml`
|
||||
- **Status Badge**:
|
||||
```markdown
|
||||
[](https://github.com/BrunoGao/ljwx-shiti/actions/workflows/ci.yml)
|
||||
```
|
||||
|
||||
### Harbor
|
||||
|
||||
- **Harbor UI**: https://harbor.omniverseai.net
|
||||
- **项目**: ljwx
|
||||
- **镜像**: ljwx-shiti
|
||||
|
||||
### ArgoCD
|
||||
|
||||
- **ArgoCD UI**: http://argocd.omniverseai.net
|
||||
- **Application**: ljwx-shiti-dev / ljwx-shiti-prod
|
||||
|
||||
---
|
||||
|
||||
## 📚 文档
|
||||
|
||||
- **架构文档**: [.github/workflows/README.md](.github/workflows/README.md)
|
||||
- **设置指南**: [.github/workflows/SETUP.md](.github/workflows/SETUP.md)
|
||||
- **项目文档**: [docs/](../docs/)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 检查清单
|
||||
|
||||
### 首次设置
|
||||
|
||||
- [ ] 配置 GitHub Secrets (HARBOR_USERNAME, HARBOR_PASSWORD, DEPLOY_REPO_TOKEN)
|
||||
- [ ] 运行本地验证 (`./scripts/verify-ci.sh`)
|
||||
- [ ] 推送代码触发 CI/CD
|
||||
- [ ] 验证 GitHub Actions 运行成功
|
||||
- [ ] 检查 Harbor 镜像推送
|
||||
- [ ] 验证 ljwx-deploy PR 创建
|
||||
|
||||
### 日常开发
|
||||
|
||||
- [ ] 本地运行测试通过
|
||||
- [ ] 本地运行 linter 通过
|
||||
- [ ] 提交代码前运行 `./scripts/verify-ci.sh`
|
||||
- [ ] 推送代码后检查 GitHub Actions
|
||||
- [ ] 合并 PR 后验证部署
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-02-26
|
||||
@@ -0,0 +1,712 @@
|
||||
# GitHub Actions CI/CD 架构文档
|
||||
|
||||
## 概览
|
||||
|
||||
本文档描述了 ljwx-shiti (智能错题本) 项目的 CI/CD 流程和 K8s 部署架构。
|
||||
|
||||
**关键组件**:
|
||||
- **代码仓库**: GitHub (git@github.com:BrunoGao/ljwx-shiti.git)
|
||||
- **部署仓库**: GitHub (git@github.com:BrunoGao/ljwx-deploy.git)
|
||||
- **镜像仓库**: Harbor (https://harbor.omniverseai.net)
|
||||
- **K8s 集群**: 本地 K8s (基础设施在 infra namespace)
|
||||
- **GitOps 工具**: ArgoCD
|
||||
|
||||
---
|
||||
|
||||
## 1. K8s 集群架构
|
||||
|
||||
### 1.1 Infra Namespace 基础设施
|
||||
|
||||
所有共享的基础设施服务都部署在 `infra` namespace 中,应用通过跨 namespace 服务发现访问。
|
||||
|
||||
#### PostgreSQL 服务
|
||||
```yaml
|
||||
# 内部访问 (应用使用)
|
||||
postgres-infra-postgresql.infra.svc.cluster.local:5432
|
||||
ClusterIP: 192.168.194.246
|
||||
|
||||
# Headless Service (StatefulSet 使用)
|
||||
postgres-infra-postgresql-hl.infra.svc.cluster.local
|
||||
|
||||
# 外部访问 (开发调试)
|
||||
postgres-external (NodePort: 32432)
|
||||
|
||||
# 部署方式
|
||||
StatefulSet: postgres-infra-postgresql-0 (1 副本)
|
||||
```
|
||||
|
||||
#### Redis 服务
|
||||
```yaml
|
||||
# 主节点访问 (应用使用)
|
||||
redis-infra-master.infra.svc.cluster.local:6379
|
||||
ClusterIP: 192.168.194.187
|
||||
|
||||
# Headless Service
|
||||
redis-infra-headless.infra.svc.cluster.local
|
||||
|
||||
# 外部访问 (开发调试)
|
||||
redis-external (NodePort: 32379)
|
||||
|
||||
# 部署方式
|
||||
StatefulSet: redis-infra-master-0 (1 副本)
|
||||
```
|
||||
|
||||
#### MySQL 服务
|
||||
```yaml
|
||||
# 内部访问 (应用使用)
|
||||
mysql-infra.infra.svc.cluster.local:3306
|
||||
Headless Service
|
||||
|
||||
# 外部访问 (开发调试)
|
||||
mysql-external (NodePort: 32306)
|
||||
|
||||
# 部署方式
|
||||
StatefulSet: mysql-infra-0 (1 副本)
|
||||
```
|
||||
|
||||
#### 其他基础设施
|
||||
- **MinIO**: 对象存储 (S3 兼容)
|
||||
- **ClickHouse**: 分析数据库
|
||||
- **Redpanda**: Kafka 兼容消息队列
|
||||
- **Ingress Nginx**: 入口控制器
|
||||
|
||||
### 1.2 跨 Namespace 服务访问
|
||||
|
||||
**DNS 格式**: `<service-name>.<namespace>.svc.cluster.local`
|
||||
|
||||
**应用配置示例**:
|
||||
```yaml
|
||||
# Deployment 环境变量
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
value: postgresql+asyncpg://user:pass@postgres-infra-postgresql.infra.svc.cluster.local:5432/wrongbook
|
||||
- name: REDIS_URL
|
||||
value: redis://redis-infra-master.infra.svc.cluster.local:6379/0
|
||||
```
|
||||
|
||||
**InitContainer 等待依赖**:
|
||||
```yaml
|
||||
initContainers:
|
||||
- name: wait-for-postgres
|
||||
image: busybox:1.35
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
until nc -z postgres-infra-postgresql.infra.svc.cluster.local 5432; do
|
||||
echo "Waiting for PostgreSQL..."
|
||||
sleep 2
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. CI/CD 完整流程
|
||||
|
||||
### 2.1 流程图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. 开发者推送代码 │
|
||||
│ git push origin main │
|
||||
└────────────────┬────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 2. GitHub Actions 触发 │
|
||||
│ Workflow: .github/workflows/ci.yml │
|
||||
│ - Lint & Test (pytest, ruff) │
|
||||
│ - Build Docker Image │
|
||||
│ - Push to Harbor │
|
||||
│ Image: harbor.omniverseai.net/ljwx/ljwx-shiti:${SHA} │
|
||||
└────────────────┬────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 3. 自动更新部署仓库 │
|
||||
│ - Clone ljwx-deploy │
|
||||
│ - Update kustomization.yaml │
|
||||
│ Path: apps/ljwx-shiti/overlays/prod/kustomization.yaml│
|
||||
│ - Create PR to main branch │
|
||||
└────────────────┬────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 4. PR 合并到 ljwx-deploy/main │
|
||||
│ (手动审核或自动合并) │
|
||||
└────────────────┬────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 5. ArgoCD 自动同步 (30秒内) │
|
||||
│ - 检测 Git 变更 │
|
||||
│ - 应用 Kustomize 配置 │
|
||||
│ - 滚动更新 Deployment │
|
||||
│ - 健康检查 (liveness/readiness/startup probes) │
|
||||
└────────────────┬────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 6. 应用运行在 K8s 集群 │
|
||||
│ - 连接 infra namespace 的 PostgreSQL/Redis │
|
||||
│ - 通过 Ingress 暴露服务 │
|
||||
│ - Prometheus 监控 (ServiceMonitor) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 GitHub Actions Workflow 配置
|
||||
|
||||
#### 必需的 GitHub Secrets
|
||||
|
||||
在 GitHub 仓库设置中配置以下 Secrets:
|
||||
|
||||
```bash
|
||||
# Harbor 镜像仓库凭据
|
||||
HARBOR_USERNAME=admin
|
||||
HARBOR_PASSWORD=<harbor-password>
|
||||
|
||||
# 部署仓库访问 Token
|
||||
DEPLOY_REPO_TOKEN=<github-personal-access-token>
|
||||
# Token 权限: repo, write:repository
|
||||
```
|
||||
|
||||
#### Workflow 文件结构
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci.yml
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
|
||||
env:
|
||||
HARBOR_HOST: harbor.omniverseai.net
|
||||
HARBOR_PROJECT: ljwx
|
||||
IMAGE_NAME: ljwx-shiti
|
||||
|
||||
jobs:
|
||||
test:
|
||||
# 运行测试
|
||||
|
||||
build-and-push:
|
||||
# 构建并推送镜像
|
||||
|
||||
update-deploy:
|
||||
# 更新部署配置
|
||||
```
|
||||
|
||||
### 2.3 镜像构建策略
|
||||
|
||||
#### Backend (Python FastAPI)
|
||||
|
||||
```dockerfile
|
||||
# backend/Dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
postgresql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 安装 Python 依赖
|
||||
COPY backend/requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制应用代码
|
||||
COPY backend/ .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
#### 构建命令
|
||||
|
||||
```bash
|
||||
# 单架构构建
|
||||
docker build -t harbor.omniverseai.net/ljwx/ljwx-shiti:${GITHUB_SHA} \
|
||||
-f backend/Dockerfile .
|
||||
docker push harbor.omniverseai.net/ljwx/ljwx-shiti:${GITHUB_SHA}
|
||||
|
||||
# 多架构构建 (可选)
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
-t harbor.omniverseai.net/ljwx/ljwx-shiti:${GITHUB_SHA} \
|
||||
-f backend/Dockerfile .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. ljwx-deploy 仓库结构
|
||||
|
||||
### 3.1 目录结构
|
||||
|
||||
```
|
||||
ljwx-deploy/
|
||||
├── apps/
|
||||
│ └── ljwx-shiti/
|
||||
│ ├── base/ # 基础配置
|
||||
│ │ ├── namespace.yaml # Namespace 定义
|
||||
│ │ ├── configmap.yaml # 非敏感配置
|
||||
│ │ ├── deployment.yaml # Deployment 配置
|
||||
│ │ ├── service.yaml # Service 配置
|
||||
│ │ ├── ingress.yaml # Ingress 配置
|
||||
│ │ ├── servicemonitor.yaml # Prometheus 监控
|
||||
│ │ └── kustomization.yaml # Kustomize 配置
|
||||
│ └── overlays/
|
||||
│ ├── dev/ # 开发环境
|
||||
│ │ └── kustomization.yaml
|
||||
│ ├── staging/ # 预发布环境
|
||||
│ │ └── kustomization.yaml
|
||||
│ └── prod/ # 生产环境
|
||||
│ └── kustomization.yaml
|
||||
├── argocd-apps/
|
||||
│ ├── 80-ljwx-shiti-dev.yaml # ArgoCD Application (dev)
|
||||
│ ├── 81-ljwx-shiti-staging.yaml # ArgoCD Application (staging)
|
||||
│ └── 82-ljwx-shiti-prod.yaml # ArgoCD Application (prod)
|
||||
└── cluster/
|
||||
└── namespace-ljwx-shiti.yaml # 集群级别 Namespace 配置
|
||||
```
|
||||
|
||||
### 3.2 Kustomize 配置
|
||||
|
||||
#### base/kustomization.yaml
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: ljwx-shiti
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- servicemonitor.yaml
|
||||
|
||||
commonLabels:
|
||||
app: ljwx-shiti
|
||||
managed-by: kustomize
|
||||
|
||||
# 镜像配置 (由 CI 自动更新)
|
||||
images:
|
||||
- name: harbor.omniverseai.net/ljwx/ljwx-shiti
|
||||
newTag: latest # CI 会更新为具体的 commit SHA
|
||||
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
```
|
||||
|
||||
#### overlays/prod/kustomization.yaml
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: ljwx-shiti
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
# 生产环境特定配置
|
||||
patchesStrategicMerge:
|
||||
- |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-shiti
|
||||
spec:
|
||||
replicas: 3 # 生产环境 3 副本
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: ljwx-shiti
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
|
||||
# 镜像标签 (由 CI 自动更新)
|
||||
images:
|
||||
- name: harbor.omniverseai.net/ljwx/ljwx-shiti
|
||||
newTag: abc123def # CI 更新为最新的 commit SHA
|
||||
```
|
||||
|
||||
### 3.3 Deployment 配置示例
|
||||
|
||||
```yaml
|
||||
# apps/ljwx-shiti/base/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-shiti
|
||||
namespace: ljwx-shiti
|
||||
labels:
|
||||
app: ljwx-shiti
|
||||
component: backend
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-shiti
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-shiti
|
||||
component: backend
|
||||
spec:
|
||||
# 等待 PostgreSQL 就绪
|
||||
initContainers:
|
||||
- name: wait-for-postgres
|
||||
image: busybox:1.35
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
until nc -z postgres-infra-postgresql.infra.svc.cluster.local 5432; do
|
||||
echo "Waiting for PostgreSQL..."
|
||||
sleep 2
|
||||
done
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
containers:
|
||||
- name: ljwx-shiti
|
||||
image: harbor.omniverseai.net/ljwx/ljwx-shiti:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
protocol: TCP
|
||||
|
||||
# 环境变量 - 从 ConfigMap
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-shiti-config
|
||||
|
||||
# 环境变量 - 从 Secret (敏感信息)
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-shiti-secret
|
||||
key: DATABASE_URL
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-shiti-secret
|
||||
key: REDIS_URL
|
||||
- name: ANTHROPIC_AUTH_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-shiti-secret
|
||||
key: ANTHROPIC_AUTH_TOKEN
|
||||
- name: JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-shiti-secret
|
||||
key: JWT_SECRET
|
||||
|
||||
# 资源配置
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
|
||||
# 健康检查
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 60
|
||||
```
|
||||
|
||||
### 3.4 ArgoCD Application 配置
|
||||
|
||||
```yaml
|
||||
# argocd-apps/80-ljwx-shiti-dev.yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-shiti-dev
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-shiti/overlays/dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-shiti
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true # 自动删除不在 Git 中的资源
|
||||
selfHeal: true # 自动修复手动修改
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Secret 管理
|
||||
|
||||
### 4.1 创建 Secret
|
||||
|
||||
```bash
|
||||
# 在 K8s 集群中创建 Secret (不提交到 Git)
|
||||
kubectl create secret generic ljwx-shiti-secret \
|
||||
--namespace=ljwx-shiti \
|
||||
--from-literal=DATABASE_URL='postgresql+asyncpg://wrongbook:password@postgres-infra-postgresql.infra.svc.cluster.local:5432/wrongbook' \
|
||||
--from-literal=REDIS_URL='redis://redis-infra-master.infra.svc.cluster.local:6379/0' \
|
||||
--from-literal=ANTHROPIC_AUTH_TOKEN='sk-ant-xxx' \
|
||||
--from-literal=JWT_SECRET='your-secret-key' \
|
||||
--from-literal=MINIO_ENDPOINT='minio.infra.svc.cluster.local:9000' \
|
||||
--from-literal=MINIO_ACCESS_KEY='minioadmin' \
|
||||
--from-literal=MINIO_SECRET_KEY='minioadmin'
|
||||
```
|
||||
|
||||
### 4.2 Secret 最佳实践
|
||||
|
||||
- ❌ **不要**: 将 Secret 提交到 Git 仓库
|
||||
- ✅ **推荐**: 使用 External Secrets Operator (未来迁移)
|
||||
- ✅ **推荐**: 使用 Sealed Secrets
|
||||
- ✅ **当前**: 手动在集群中创建 Secret
|
||||
|
||||
---
|
||||
|
||||
## 5. 监控和可观测性
|
||||
|
||||
### 5.1 Prometheus 监控
|
||||
|
||||
```yaml
|
||||
# apps/ljwx-shiti/base/servicemonitor.yaml
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: ljwx-shiti
|
||||
namespace: ljwx-shiti
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-shiti
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
```
|
||||
|
||||
### 5.2 日志收集
|
||||
|
||||
```yaml
|
||||
# Deployment annotations
|
||||
annotations:
|
||||
fluentbit.io/parser: json
|
||||
```
|
||||
|
||||
### 5.3 追踪 (OpenTelemetry)
|
||||
|
||||
```yaml
|
||||
# ConfigMap 环境变量
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.tracing.svc.cluster.local:4318"
|
||||
OTEL_SERVICE_NAME: "ljwx-shiti"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 常用命令
|
||||
|
||||
### 6.1 本地开发
|
||||
|
||||
```bash
|
||||
# 启动本地环境
|
||||
docker-compose up -d
|
||||
|
||||
# 运行测试
|
||||
cd backend && pytest -v
|
||||
|
||||
# 运行 linter
|
||||
cd backend && ruff check .
|
||||
|
||||
# 构建镜像
|
||||
docker build -t ljwx-shiti:dev -f backend/Dockerfile .
|
||||
```
|
||||
|
||||
### 6.2 K8s 操作
|
||||
|
||||
```bash
|
||||
# 查看应用状态
|
||||
kubectl get pods -n ljwx-shiti
|
||||
kubectl get svc -n ljwx-shiti
|
||||
kubectl get ingress -n ljwx-shiti
|
||||
|
||||
# 查看日志
|
||||
kubectl logs -n ljwx-shiti -l app=ljwx-shiti -f
|
||||
|
||||
# 查看 Pod 详情
|
||||
kubectl describe pod -n ljwx-shiti <pod-name>
|
||||
|
||||
# 进入容器
|
||||
kubectl exec -it -n ljwx-shiti <pod-name> -- /bin/bash
|
||||
|
||||
# 查看 Secret
|
||||
kubectl get secret -n ljwx-shiti ljwx-shiti-secret -o yaml
|
||||
|
||||
# 测试数据库连接
|
||||
kubectl exec -it -n ljwx-shiti <pod-name> -- \
|
||||
psql postgresql://wrongbook:password@postgres-infra-postgresql.infra.svc.cluster.local:5432/wrongbook
|
||||
```
|
||||
|
||||
### 6.3 ArgoCD 操作
|
||||
|
||||
```bash
|
||||
# 查看 Application 状态
|
||||
kubectl get application -n argocd ljwx-shiti-dev
|
||||
|
||||
# 手动触发同步
|
||||
kubectl patch application ljwx-shiti-dev -n argocd \
|
||||
--type merge \
|
||||
-p '{"operation":{"initiatedBy":{"username":"admin"},"sync":{"revision":"HEAD"}}}'
|
||||
|
||||
# 查看同步历史
|
||||
kubectl describe application -n argocd ljwx-shiti-dev
|
||||
```
|
||||
|
||||
### 6.4 Harbor 操作
|
||||
|
||||
```bash
|
||||
# 登录 Harbor
|
||||
docker login harbor.omniverseai.net
|
||||
|
||||
# 查看镜像
|
||||
docker pull harbor.omniverseai.net/ljwx/ljwx-shiti:latest
|
||||
|
||||
# 查看镜像详情
|
||||
docker buildx imagetools inspect harbor.omniverseai.net/ljwx/ljwx-shiti:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 故障排查
|
||||
|
||||
### 7.1 Pod 无法启动
|
||||
|
||||
```bash
|
||||
# 查看 Pod 事件
|
||||
kubectl describe pod -n ljwx-shiti <pod-name>
|
||||
|
||||
# 查看 init container 日志
|
||||
kubectl logs -n ljwx-shiti <pod-name> -c wait-for-postgres
|
||||
|
||||
# 测试数据库连接
|
||||
kubectl exec -n ljwx-shiti <pod-name> -- \
|
||||
nc -zv postgres-infra-postgresql.infra.svc.cluster.local 5432
|
||||
```
|
||||
|
||||
### 7.2 镜像拉取失败
|
||||
|
||||
```bash
|
||||
# 检查 imagePullSecrets
|
||||
kubectl get secret -n ljwx-shiti regcred
|
||||
|
||||
# 手动拉取镜像测试
|
||||
docker pull harbor.omniverseai.net/ljwx/ljwx-shiti:latest
|
||||
```
|
||||
|
||||
### 7.3 健康检查失败
|
||||
|
||||
```bash
|
||||
# 查看健康检查端点
|
||||
kubectl exec -n ljwx-shiti <pod-name> -- \
|
||||
curl -v http://localhost:8000/health
|
||||
|
||||
# 查看应用日志
|
||||
kubectl logs -n ljwx-shiti <pod-name> --tail=100
|
||||
```
|
||||
|
||||
### 7.4 ArgoCD 同步失败
|
||||
|
||||
```bash
|
||||
# 查看 Application 状态
|
||||
kubectl get application -n argocd ljwx-shiti-dev -o yaml
|
||||
|
||||
# 查看 ArgoCD 日志
|
||||
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 参考资料
|
||||
|
||||
### 8.1 相关仓库
|
||||
|
||||
- **应用仓库**: git@github.com:BrunoGao/ljwx-shiti.git
|
||||
- **部署仓库**: git@github.com:BrunoGao/ljwx-deploy.git
|
||||
- **Harbor**: https://harbor.omniverseai.net/harbor/projects/ljwx
|
||||
|
||||
### 8.2 文档
|
||||
|
||||
- **项目文档**: `/docs/`
|
||||
- **API 文档**: `/docs/API.md`
|
||||
- **架构文档**: `/docs/architecture.md`
|
||||
- **部署文档**: `/docs/DEPLOYMENT.md`
|
||||
|
||||
### 8.3 工具
|
||||
|
||||
- **Kustomize**: https://kustomize.io/
|
||||
- **ArgoCD**: https://argo-cd.readthedocs.io/
|
||||
- **Harbor**: https://goharbor.io/
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-02-26
|
||||
**维护者**: DevOps Team
|
||||
@@ -0,0 +1,392 @@
|
||||
# GitHub Secrets 设置详细指南
|
||||
|
||||
本文档提供 GitHub Actions CI/CD 所需 Secrets 的详细设置步骤。
|
||||
|
||||
## 📋 前置条件
|
||||
|
||||
- GitHub 账号已登录
|
||||
- 对 `BrunoGao/ljwx-shiti` 仓库有管理员权限
|
||||
- 已安装 GitHub CLI (`gh`)
|
||||
|
||||
## 🚀 快速设置(推荐)
|
||||
|
||||
### 方式 1: 使用交互式脚本
|
||||
|
||||
```bash
|
||||
# 运行设置脚本
|
||||
./scripts/setup-github-secrets.sh
|
||||
|
||||
# 按照提示操作:
|
||||
# 1. 选择 "4. 一键设置所有 secrets"
|
||||
# 2. 输入 Harbor 用户名和密码
|
||||
# 3. 输入 GitHub Personal Access Token
|
||||
# 4. 完成!
|
||||
```
|
||||
|
||||
### 方式 2: 使用 GitHub CLI 命令
|
||||
|
||||
```bash
|
||||
# 设置 Harbor 用户名
|
||||
echo "admin" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 设置 Harbor 密码(会提示输入)
|
||||
gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 设置 Deploy Token(会提示输入)
|
||||
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 详细步骤
|
||||
|
||||
### 步骤 1: 访问 Secrets 设置页面
|
||||
|
||||
#### 方式 A: 通过浏览器
|
||||
|
||||
1. 打开仓库: https://github.com/BrunoGao/ljwx-shiti
|
||||
2. 点击 **Settings** (设置)
|
||||
3. 在左侧菜单中,点击 **Secrets and variables** → **Actions**
|
||||
4. 点击 **New repository secret** (新建仓库密钥)
|
||||
|
||||
#### 方式 B: 直接访问
|
||||
|
||||
访问: https://github.com/BrunoGao/ljwx-shiti/settings/secrets/actions
|
||||
|
||||
---
|
||||
|
||||
### 步骤 2: 设置 HARBOR_USERNAME
|
||||
|
||||
#### 2.1 创建 Secret
|
||||
|
||||
- **Name**: `HARBOR_USERNAME`
|
||||
- **Secret**: `admin` (或你的 Harbor 用户名)
|
||||
|
||||
#### 2.2 点击 "Add secret"
|
||||
|
||||

|
||||
|
||||
#### 2.3 验证
|
||||
|
||||
```bash
|
||||
# 使用 gh CLI 验证
|
||||
gh secret list --repo BrunoGao/ljwx-shiti | grep HARBOR_USERNAME
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤 3: 设置 HARBOR_PASSWORD
|
||||
|
||||
#### 3.1 获取 Harbor 密码
|
||||
|
||||
联系 DevOps 团队或从 Harbor 管理界面获取密码。
|
||||
|
||||
#### 3.2 创建 Secret
|
||||
|
||||
- **Name**: `HARBOR_PASSWORD`
|
||||
- **Secret**: `<your-harbor-password>`
|
||||
|
||||
#### 3.3 测试 Harbor 登录
|
||||
|
||||
```bash
|
||||
# 测试 Harbor 登录
|
||||
docker login harbor.omniverseai.net
|
||||
# 输入用户名: admin
|
||||
# 输入密码: <your-harbor-password>
|
||||
|
||||
# 如果成功,会显示: Login Succeeded
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤 4: 创建 GitHub Personal Access Token
|
||||
|
||||
#### 4.1 访问 Token 创建页面
|
||||
|
||||
1. 访问: https://github.com/settings/tokens
|
||||
2. 点击 **Generate new token** → **Generate new token (classic)**
|
||||
|
||||
#### 4.2 配置 Token
|
||||
|
||||
**Token 名称**:
|
||||
```
|
||||
ljwx-shiti-deploy-access
|
||||
```
|
||||
|
||||
**Token 描述** (可选):
|
||||
```
|
||||
CI/CD access to ljwx-deploy repository
|
||||
```
|
||||
|
||||
**过期时间**:
|
||||
- 推荐: **90 days** (90 天)
|
||||
- 或选择: **No expiration** (永不过期,需定期审计)
|
||||
|
||||
**权限选择** (Scopes):
|
||||
|
||||
必需权限:
|
||||
- ✅ `repo` - Full control of private repositories
|
||||
- ✅ `repo:status` - Access commit status
|
||||
- ✅ `repo_deployment` - Access deployment status
|
||||
- ✅ `public_repo` - Access public repositories
|
||||
- ✅ `repo:invite` - Access repository invitations
|
||||
- ✅ `security_events` - Read and write security events
|
||||
|
||||
- ✅ `workflow` - Update GitHub Action workflows
|
||||
|
||||
可选权限:
|
||||
- ⬜ `write:packages` - Upload packages to GitHub Package Registry
|
||||
- ⬜ `read:packages` - Download packages from GitHub Package Registry
|
||||
|
||||
#### 4.3 生成 Token
|
||||
|
||||
1. 点击 **Generate token** (生成 token)
|
||||
2. **重要**: 立即复制生成的 token(只显示一次!)
|
||||
3. Token 格式: `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
||||
|
||||
#### 4.4 保存 Token
|
||||
|
||||
```bash
|
||||
# 临时保存到环境变量(可选)
|
||||
export DEPLOY_REPO_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
# 或保存到密码管理器(推荐)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤 5: 设置 DEPLOY_REPO_TOKEN
|
||||
|
||||
#### 5.1 创建 Secret
|
||||
|
||||
- **Name**: `DEPLOY_REPO_TOKEN`
|
||||
- **Secret**: `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` (你的 token)
|
||||
|
||||
#### 5.2 验证 Token 权限
|
||||
|
||||
```bash
|
||||
# 测试 token 是否有权限访问 ljwx-deploy
|
||||
curl -H "Authorization: token ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
|
||||
https://api.github.com/repos/BrunoGao/ljwx-deploy
|
||||
|
||||
# 应该返回仓库信息,包含 "name": "ljwx-deploy"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证配置
|
||||
|
||||
### 方式 1: 使用脚本验证
|
||||
|
||||
```bash
|
||||
./scripts/setup-github-secrets.sh
|
||||
|
||||
# 选择 "5. 验证 secrets 配置"
|
||||
```
|
||||
|
||||
### 方式 2: 使用 gh CLI 验证
|
||||
|
||||
```bash
|
||||
# 列出所有 secrets
|
||||
gh secret list --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 应该看到:
|
||||
# HARBOR_USERNAME Updated 2026-02-26
|
||||
# HARBOR_PASSWORD Updated 2026-02-26
|
||||
# DEPLOY_REPO_TOKEN Updated 2026-02-26
|
||||
```
|
||||
|
||||
### 方式 3: 通过浏览器验证
|
||||
|
||||
访问: https://github.com/BrunoGao/ljwx-shiti/settings/secrets/actions
|
||||
|
||||
应该看到 3 个 secrets:
|
||||
- ✅ HARBOR_USERNAME
|
||||
- ✅ HARBOR_PASSWORD
|
||||
- ✅ DEPLOY_REPO_TOKEN
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试 CI/CD
|
||||
|
||||
### 1. 提交代码触发 CI/CD
|
||||
|
||||
```bash
|
||||
# 创建测试分支
|
||||
git checkout -b test/ci-setup
|
||||
|
||||
# 修改文件触发 CI
|
||||
echo "# CI/CD Test" >> README.md
|
||||
|
||||
# 提交并推送
|
||||
git add README.md
|
||||
git commit -m "test: verify CI/CD setup"
|
||||
git push origin test/ci-setup
|
||||
```
|
||||
|
||||
### 2. 查看 GitHub Actions 运行
|
||||
|
||||
#### 方式 A: 浏览器
|
||||
|
||||
访问: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
|
||||
#### 方式 B: GitHub CLI
|
||||
|
||||
```bash
|
||||
# 查看最近的运行
|
||||
gh run list --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 实时查看运行状态
|
||||
gh run watch --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 查看运行日志
|
||||
gh run view --log --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
### 3. 验证各个阶段
|
||||
|
||||
#### ✅ 阶段 1: Test (约 20 分钟)
|
||||
- Lint 检查
|
||||
- 单元测试
|
||||
- 代码覆盖率
|
||||
|
||||
#### ✅ 阶段 2: Build and Push (约 30 分钟)
|
||||
- Docker 镜像构建
|
||||
- 推送到 Harbor
|
||||
- 镜像标签: `harbor.omniverseai.net/ljwx/ljwx-shiti:<commit-sha>`
|
||||
|
||||
#### ✅ 阶段 3: Update Deploy (约 10 分钟)
|
||||
- 克隆 ljwx-deploy 仓库
|
||||
- 更新 kustomization.yaml
|
||||
- 创建 PR
|
||||
|
||||
---
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### 问题 1: Harbor 登录失败
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
Error: Error response from daemon: Get "https://harbor.omniverseai.net/v2/": unauthorized
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 `HARBOR_USERNAME` 和 `HARBOR_PASSWORD` 是否正确
|
||||
2. 在本地测试登录:
|
||||
```bash
|
||||
docker login harbor.omniverseai.net
|
||||
```
|
||||
3. 确认账号有 `ljwx` 项目的推送权限
|
||||
|
||||
### 问题 2: Deploy Repo 访问失败
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
remote: Repository not found.
|
||||
fatal: repository 'https://github.com/BrunoGao/ljwx-deploy.git/' not found
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 `DEPLOY_REPO_TOKEN` 是否有效
|
||||
2. 确认 token 有 `repo` 和 `workflow` 权限
|
||||
3. 测试 token:
|
||||
```bash
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://api.github.com/repos/BrunoGao/ljwx-deploy
|
||||
```
|
||||
|
||||
### 问题 3: Secret 无法读取
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
Error: Secret HARBOR_USERNAME not found
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. 确认 secret 名称拼写正确(区分大小写)
|
||||
2. 检查 secret 是否在正确的仓库中
|
||||
3. 重新设置 secret:
|
||||
```bash
|
||||
gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
### 问题 4: Token 权限不足
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
Error: Resource not accessible by integration
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 token 权限是否包含 `repo` 和 `workflow`
|
||||
2. 重新生成 token 并确保选择正确的权限
|
||||
3. 更新 secret:
|
||||
```bash
|
||||
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 安全最佳实践
|
||||
|
||||
### 1. Secret 管理
|
||||
|
||||
- ❌ **不要**: 在代码中硬编码 secrets
|
||||
- ❌ **不要**: 在日志中打印 secrets
|
||||
- ❌ **不要**: 将 secrets 提交到 Git
|
||||
- ✅ **推荐**: 使用 GitHub Secrets 存储敏感信息
|
||||
- ✅ **推荐**: 定期轮换 tokens 和密码(建议 90 天)
|
||||
|
||||
### 2. Token 权限最小化
|
||||
|
||||
- 只授予必需的权限
|
||||
- 为不同用途创建不同的 tokens
|
||||
- 定期审计 token 使用情况
|
||||
|
||||
### 3. 审计日志
|
||||
|
||||
定期检查:
|
||||
- GitHub Actions 运行日志
|
||||
- Harbor 镜像推送记录
|
||||
- ljwx-deploy 仓库的 commit 历史
|
||||
|
||||
### 4. Token 过期处理
|
||||
|
||||
当 token 过期时:
|
||||
1. 生成新的 token
|
||||
2. 更新 `DEPLOY_REPO_TOKEN` secret
|
||||
3. 验证 CI/CD 运行正常
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
### GitHub 文档
|
||||
- [GitHub Secrets 文档](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
|
||||
- [Personal Access Tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
|
||||
- [GitHub Actions 文档](https://docs.github.com/en/actions)
|
||||
|
||||
### 项目文档
|
||||
- [CI/CD 架构文档](.github/workflows/README.md)
|
||||
- [快速开始指南](.github/workflows/QUICKSTART.md)
|
||||
- [本地验证脚本](../scripts/verify-ci.sh)
|
||||
|
||||
### 工具
|
||||
- [GitHub CLI](https://cli.github.com/)
|
||||
- [Harbor 文档](https://goharbor.io/docs/)
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
如果遇到问题:
|
||||
1. 查看 [常见问题](#-常见问题) 部分
|
||||
2. 运行验证脚本: `./scripts/setup-github-secrets.sh`
|
||||
3. 查看 GitHub Actions 日志
|
||||
4. 联系 DevOps 团队
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-02-26
|
||||
**维护者**: DevOps Team
|
||||
@@ -0,0 +1,230 @@
|
||||
# GitHub Actions 设置指南
|
||||
|
||||
本文档描述如何配置 GitHub Actions CI/CD 流程所需的 Secrets 和权限。
|
||||
|
||||
## 1. 必需的 GitHub Secrets
|
||||
|
||||
在 GitHub 仓库设置中配置以下 Secrets:
|
||||
|
||||
### 1.1 访问 Secrets 设置
|
||||
|
||||
1. 打开仓库: https://github.com/BrunoGao/ljwx-shiti
|
||||
2. 点击 **Settings** → **Secrets and variables** → **Actions**
|
||||
3. 点击 **New repository secret**
|
||||
|
||||
### 1.2 配置 Secrets
|
||||
|
||||
#### HARBOR_USERNAME
|
||||
- **名称**: `HARBOR_USERNAME`
|
||||
- **值**: Harbor 仓库用户名(通常是 `admin`)
|
||||
- **用途**: 登录 Harbor 镜像仓库
|
||||
|
||||
#### HARBOR_PASSWORD
|
||||
- **名称**: `HARBOR_PASSWORD`
|
||||
- **值**: Harbor 仓库密码
|
||||
- **用途**: 登录 Harbor 镜像仓库
|
||||
- **获取方式**: 联系 DevOps 团队或从 Harbor 管理界面获取
|
||||
|
||||
#### DEPLOY_REPO_TOKEN
|
||||
- **名称**: `DEPLOY_REPO_TOKEN`
|
||||
- **值**: GitHub Personal Access Token (PAT)
|
||||
- **用途**: 访问和更新 ljwx-deploy 仓库
|
||||
- **权限要求**:
|
||||
- `repo` (完整仓库访问)
|
||||
- `workflow` (更新 workflow 文件)
|
||||
|
||||
**创建 PAT 步骤**:
|
||||
1. 访问: https://github.com/settings/tokens
|
||||
2. 点击 **Generate new token** → **Generate new token (classic)**
|
||||
3. 设置名称: `ljwx-shiti-deploy-access`
|
||||
4. 选择权限:
|
||||
- ✅ `repo` (Full control of private repositories)
|
||||
- ✅ `workflow` (Update GitHub Action workflows)
|
||||
5. 点击 **Generate token**
|
||||
6. 复制生成的 token(只显示一次!)
|
||||
7. 将 token 添加到 ljwx-shiti 仓库的 Secrets
|
||||
|
||||
## 2. 验证 Secrets 配置
|
||||
|
||||
配置完成后,可以通过以下方式验证:
|
||||
|
||||
### 2.1 检查 Secrets 列表
|
||||
|
||||
在 **Settings** → **Secrets and variables** → **Actions** 页面,应该看到:
|
||||
|
||||
```
|
||||
✅ HARBOR_USERNAME
|
||||
✅ HARBOR_PASSWORD
|
||||
✅ DEPLOY_REPO_TOKEN
|
||||
```
|
||||
|
||||
### 2.2 触发测试构建
|
||||
|
||||
```bash
|
||||
# 创建测试分支
|
||||
git checkout -b test/ci-setup
|
||||
|
||||
# 修改任意文件触发 CI
|
||||
echo "# CI Test" >> README.md
|
||||
|
||||
# 提交并推送
|
||||
git add README.md
|
||||
git commit -m "test: verify CI/CD setup"
|
||||
git push origin test/ci-setup
|
||||
```
|
||||
|
||||
然后访问: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
|
||||
查看 workflow 运行状态。
|
||||
|
||||
## 3. 常见问题排查
|
||||
|
||||
### 3.1 Harbor 登录失败
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
Error: Error response from daemon: Get "https://harbor.omniverseai.net/v2/": unauthorized
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 `HARBOR_USERNAME` 和 `HARBOR_PASSWORD` 是否正确
|
||||
2. 在本地测试 Harbor 登录:
|
||||
```bash
|
||||
docker login harbor.omniverseai.net
|
||||
# 输入用户名和密码
|
||||
```
|
||||
3. 确认 Harbor 账号有 `ljwx` 项目的推送权限
|
||||
|
||||
### 3.2 Deploy Repo 访问失败
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
remote: Repository not found.
|
||||
fatal: repository 'https://github.com/BrunoGao/ljwx-deploy.git/' not found
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 `DEPLOY_REPO_TOKEN` 是否有效
|
||||
2. 确认 token 有 `repo` 权限
|
||||
3. 测试 token 权限:
|
||||
```bash
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://api.github.com/repos/BrunoGao/ljwx-deploy
|
||||
```
|
||||
4. 如果 token 过期,重新生成并更新 Secret
|
||||
|
||||
### 3.3 PR 创建失败
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
gh: command not found
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
这不应该发生,因为 GitHub Actions runner 预装了 `gh` CLI。如果遇到此问题:
|
||||
1. 检查 workflow 文件中的 `GH_TOKEN` 环境变量设置
|
||||
2. 确认使用的是 `ubuntu-latest` runner
|
||||
|
||||
### 3.4 测试失败
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
FAILED tests/test_xxx.py::test_xxx - AssertionError
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. 在本地运行测试确认通过:
|
||||
```bash
|
||||
cd backend
|
||||
pytest -v
|
||||
```
|
||||
2. 检查测试依赖的环境变量是否正确
|
||||
3. 查看 GitHub Actions 日志获取详细错误信息
|
||||
|
||||
## 4. Workflow 触发条件
|
||||
|
||||
### 4.1 自动触发
|
||||
|
||||
Workflow 会在以下情况自动触发:
|
||||
|
||||
- **Push 到 main/master/develop 分支**: 运行完整 CI/CD(测试 + 构建 + 部署)
|
||||
- **Pull Request 到 main/master/develop**: 只运行测试
|
||||
|
||||
### 4.2 手动触发
|
||||
|
||||
可以在 GitHub Actions 页面手动触发 workflow:
|
||||
|
||||
1. 访问: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
2. 选择 **CI/CD Pipeline** workflow
|
||||
3. 点击 **Run workflow**
|
||||
4. 选择分支并点击 **Run workflow**
|
||||
|
||||
## 5. 监控和通知
|
||||
|
||||
### 5.1 查看 Workflow 状态
|
||||
|
||||
访问: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
|
||||
可以看到:
|
||||
- ✅ 成功的 workflow 运行
|
||||
- ❌ 失败的 workflow 运行
|
||||
- 🟡 进行中的 workflow 运行
|
||||
|
||||
### 5.2 配置通知
|
||||
|
||||
在 **Settings** → **Notifications** 中配置:
|
||||
- Email 通知
|
||||
- Slack 通知(需要配置 Slack App)
|
||||
|
||||
### 5.3 Status Badge
|
||||
|
||||
在 README.md 中添加 status badge:
|
||||
|
||||
```markdown
|
||||
[](https://github.com/BrunoGao/ljwx-shiti/actions/workflows/ci.yml)
|
||||
```
|
||||
|
||||
## 6. 安全最佳实践
|
||||
|
||||
### 6.1 Secrets 管理
|
||||
|
||||
- ❌ **不要**: 在代码中硬编码 secrets
|
||||
- ❌ **不要**: 在日志中打印 secrets
|
||||
- ✅ **推荐**: 使用 GitHub Secrets 存储敏感信息
|
||||
- ✅ **推荐**: 定期轮换 tokens 和密码
|
||||
|
||||
### 6.2 Token 权限最小化
|
||||
|
||||
- 只授予必需的权限
|
||||
- 使用 fine-grained tokens(如果可用)
|
||||
- 为不同用途创建不同的 tokens
|
||||
|
||||
### 6.3 审计日志
|
||||
|
||||
定期检查:
|
||||
- GitHub Actions 运行日志
|
||||
- Harbor 镜像推送记录
|
||||
- ljwx-deploy 仓库的 commit 历史
|
||||
|
||||
## 7. 下一步
|
||||
|
||||
配置完成后:
|
||||
|
||||
1. ✅ 提交代码触发 CI/CD
|
||||
2. ✅ 查看 GitHub Actions 运行状态
|
||||
3. ✅ 验证镜像推送到 Harbor
|
||||
4. ✅ 检查 ljwx-deploy 仓库的 PR
|
||||
5. ✅ 合并 PR 触发 ArgoCD 部署
|
||||
|
||||
## 8. 参考资料
|
||||
|
||||
- **GitHub Actions 文档**: https://docs.github.com/en/actions
|
||||
- **GitHub Secrets 文档**: https://docs.github.com/en/actions/security-guides/encrypted-secrets
|
||||
- **Harbor 文档**: https://goharbor.io/docs/
|
||||
- **Workflow 配置**: `.github/workflows/ci.yml`
|
||||
- **架构文档**: `.github/workflows/README.md`
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-02-26
|
||||
**维护者**: DevOps Team
|
||||
@@ -0,0 +1,105 @@
|
||||
name: Backend Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, develop]
|
||||
pull_request:
|
||||
branches: [main, master, develop]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: wrongbook
|
||||
POSTGRES_PASSWORD: wrongbook
|
||||
POSTGRES_DB: wrongbook_test
|
||||
ports:
|
||||
- 5433:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6380:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: "pip"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd backend
|
||||
pip install --upgrade pip
|
||||
pip install -e ".[dev]"
|
||||
|
||||
- name: Run linter
|
||||
run: |
|
||||
cd backend
|
||||
ruff check .
|
||||
|
||||
- name: Run type checker (optional)
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd backend
|
||||
pip install mypy
|
||||
mypy app --ignore-missing-imports
|
||||
|
||||
- name: Run migrations
|
||||
env:
|
||||
DATABASE_URL: postgresql://wrongbook:wrongbook@localhost:5433/wrongbook_test
|
||||
run: |
|
||||
cd backend
|
||||
alembic upgrade head
|
||||
|
||||
- name: Run tests with coverage
|
||||
env:
|
||||
TEST_DATABASE_URL: postgresql+asyncpg://wrongbook:wrongbook@localhost:5433/wrongbook_test
|
||||
REDIS_URL: redis://localhost:6380/0
|
||||
ANTHROPIC_AUTH_TOKEN: sk-ant-test-placeholder
|
||||
ANTHROPIC_BASE_URL: https://api.anthropic.com
|
||||
ANTHROPIC_MODEL: claude-sonnet-4-6
|
||||
TESTING: "true"
|
||||
JWT_SECRET: test-secret-key
|
||||
run: |
|
||||
cd backend
|
||||
pytest --cov=app --cov-report=xml --cov-report=term --cov-fail-under=70 -v
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: backend/coverage.xml
|
||||
flags: backend
|
||||
name: backend-coverage
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Generate coverage badge
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
cd backend
|
||||
pip install coverage-badge
|
||||
coverage-badge -o coverage.svg -f
|
||||
|
||||
- name: Test summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Tests completed" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,311 @@
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, develop]
|
||||
pull_request:
|
||||
branches: [main, master, develop]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
HARBOR_HOST: harbor.omniverseai.net
|
||||
HARBOR_PROJECT: ljwx
|
||||
IMAGE_NAME: ljwx-shiti
|
||||
|
||||
jobs:
|
||||
# ── Job 1: Lint and Test ──────────────────────────────────────────────────
|
||||
test:
|
||||
name: Test Backend
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: wrongbook
|
||||
POSTGRES_PASSWORD: wrongbook
|
||||
POSTGRES_DB: wrongbook_test
|
||||
ports:
|
||||
- 5433:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6380:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: "pip"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd backend
|
||||
pip install --upgrade pip
|
||||
pip install -e ".[dev]"
|
||||
|
||||
- name: Run linter
|
||||
run: |
|
||||
cd backend
|
||||
ruff check .
|
||||
|
||||
- name: Run type checker (optional)
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd backend
|
||||
pip install mypy
|
||||
mypy app --ignore-missing-imports
|
||||
|
||||
- name: Run migrations
|
||||
env:
|
||||
DATABASE_URL: postgresql://wrongbook:wrongbook@localhost:5433/wrongbook_test
|
||||
run: |
|
||||
cd backend
|
||||
alembic upgrade head
|
||||
|
||||
- name: Run tests with coverage
|
||||
env:
|
||||
TEST_DATABASE_URL: postgresql+asyncpg://wrongbook:wrongbook@localhost:5433/wrongbook_test
|
||||
REDIS_URL: redis://localhost:6380/0
|
||||
ANTHROPIC_AUTH_TOKEN: sk-ant-test-placeholder
|
||||
ANTHROPIC_BASE_URL: https://api.anthropic.com
|
||||
ANTHROPIC_MODEL: claude-sonnet-4-6
|
||||
TESTING: "true"
|
||||
JWT_SECRET: test-secret-key
|
||||
run: |
|
||||
cd backend
|
||||
pytest --cov=app --cov-report=xml --cov-report=term --cov-fail-under=70 -v
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: backend/coverage.xml
|
||||
flags: backend
|
||||
name: backend-coverage
|
||||
fail_ci_if_error: false
|
||||
|
||||
# ── Job 2: Build and Push Docker Image ────────────────────────────────────
|
||||
build-and-push:
|
||||
name: Build and Push Image
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
image_name: ${{ steps.meta.outputs.image_name }}
|
||||
image_ref: ${{ steps.meta.outputs.image_ref }}
|
||||
image_tag: ${{ steps.meta.outputs.image_tag }}
|
||||
image_digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Resolve metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IMAGE_NAME="${HARBOR_HOST}/${HARBOR_PROJECT}/${IMAGE_NAME}"
|
||||
IMAGE_TAG="${GITHUB_SHA:0:7}"
|
||||
{
|
||||
echo "image_name=${IMAGE_NAME}"
|
||||
echo "image_ref=${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
echo "image_tag=${IMAGE_TAG}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.HARBOR_HOST }}
|
||||
username: ${{ secrets.HARBOR_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_PASSWORD }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
file: backend/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.meta.outputs.image_ref }}
|
||||
${{ steps.meta.outputs.image_name }}:latest
|
||||
cache-from: type=gha,scope=backend
|
||||
cache-to: type=gha,scope=backend,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
# ── Job 3: Update Deploy Repository ───────────────────────────────────────
|
||||
update-deploy:
|
||||
name: Update Deploy Repo
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-push
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install PyYAML
|
||||
shell: bash
|
||||
run: python3 -m pip install --quiet pyyaml
|
||||
|
||||
- name: Resolve deploy config
|
||||
id: cfg
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
{
|
||||
echo "deploy_repo=BrunoGao/ljwx-deploy"
|
||||
echo "overlay_file=apps/ljwx-shiti/overlays/prod/kustomization.yaml"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clone deploy repo
|
||||
env:
|
||||
DEPLOY_REPO_TOKEN: ${{ secrets.DEPLOY_REPO_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${DEPLOY_REPO_TOKEN:-}" ]; then
|
||||
echo "DEPLOY_REPO_TOKEN is required" && exit 1
|
||||
fi
|
||||
git clone \
|
||||
"https://x-access-token:${DEPLOY_REPO_TOKEN}@github.com/${{ steps.cfg.outputs.deploy_repo }}.git" \
|
||||
deploy-repo
|
||||
|
||||
- name: Update overlay kustomization
|
||||
env:
|
||||
OVERLAY_FILE: ${{ steps.cfg.outputs.overlay_file }}
|
||||
IMAGE_NAME: ${{ needs.build-and-push.outputs.image_name }}
|
||||
IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
|
||||
overlay = Path("deploy-repo") / os.environ["OVERLAY_FILE"]
|
||||
|
||||
# 如果文件不存在,创建基础结构
|
||||
if not overlay.exists():
|
||||
overlay.parent.mkdir(parents=True, exist_ok=True)
|
||||
data = {
|
||||
"apiVersion": "kustomize.config.k8s.io/v1beta1",
|
||||
"kind": "Kustomization",
|
||||
"namespace": "ljwx-shiti",
|
||||
"resources": ["../../base"],
|
||||
"images": []
|
||||
}
|
||||
else:
|
||||
data = yaml.safe_load(overlay.read_text()) or {}
|
||||
|
||||
# 确保 images 字段存在
|
||||
if "images" not in data:
|
||||
data["images"] = []
|
||||
|
||||
# 更新或添加镜像配置
|
||||
image_name = os.environ["IMAGE_NAME"]
|
||||
image_tag = os.environ["IMAGE_TAG"]
|
||||
|
||||
found = False
|
||||
for img in data["images"]:
|
||||
if img.get("name") == image_name or img.get("newName") == image_name:
|
||||
img["newTag"] = image_tag
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
data["images"].append({
|
||||
"name": image_name,
|
||||
"newTag": image_tag
|
||||
})
|
||||
|
||||
overlay.write_text(yaml.safe_dump(data, sort_keys=False))
|
||||
print(f"Updated {overlay} with image tag: {image_tag}")
|
||||
PY
|
||||
|
||||
- name: Create deploy PR
|
||||
working-directory: deploy-repo
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.DEPLOY_REPO_TOKEN }}
|
||||
GIT_SHA: ${{ github.sha }}
|
||||
IMAGE_REF: ${{ needs.build-and-push.outputs.image_ref }}
|
||||
IMAGE_DIGEST: ${{ needs.build-and-push.outputs.image_digest }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA_SHORT="${GIT_SHA:0:7}"
|
||||
BRANCH="bot/release-ljwx-shiti-${GITHUB_RUN_ID}"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -b "${BRANCH}"
|
||||
git add "${{ steps.cfg.outputs.overlay_file }}"
|
||||
|
||||
# 检查是否有变更
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "release(ljwx-shiti): ${SHA_SHORT}"
|
||||
git push origin "${BRANCH}"
|
||||
|
||||
gh pr create \
|
||||
--repo "${{ steps.cfg.outputs.deploy_repo }}" \
|
||||
--title "release(ljwx-shiti): ${SHA_SHORT}" \
|
||||
--body "Automated release PR from ${{ github.repository }}@${GIT_SHA}
|
||||
|
||||
**Image** \`${IMAGE_REF}\`
|
||||
digest: ${IMAGE_DIGEST}
|
||||
|
||||
**Changes**: ${{ github.event.head_commit.message }}
|
||||
" \
|
||||
--base main \
|
||||
--head "${BRANCH}"
|
||||
|
||||
# ── Job 4: Test Summary ───────────────────────────────────────────────────
|
||||
summary:
|
||||
name: CI/CD Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, build-and-push, update-deploy]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Generate summary
|
||||
run: |
|
||||
echo "## CI/CD Pipeline Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Test**: ${{ needs.test.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Build**: ${{ needs.build-and-push.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Deploy**: ${{ needs.update-deploy.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ needs.build-and-push.result }}" == "success" ]; then
|
||||
echo "**Image**: \`${{ needs.build-and-push.outputs.image_ref }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -0,0 +1,316 @@
|
||||
# GitHub Actions CI/CD 设置完成总结
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 1. Dockerfile 优化
|
||||
- ✅ 更新为使用 `pyproject.toml`
|
||||
- ✅ 添加健康检查
|
||||
- ✅ 使用非 root 用户运行
|
||||
- ✅ 修复系统依赖包名(libgl1)
|
||||
- 📁 文件: `backend/Dockerfile`
|
||||
|
||||
### 2. GitHub Actions Workflow
|
||||
- ✅ 创建完整的 CI/CD pipeline
|
||||
- ✅ 包含测试、构建、部署三个阶段
|
||||
- ✅ 自动推送镜像到 Harbor
|
||||
- ✅ 自动更新 ljwx-deploy 仓库
|
||||
- 📁 文件: `.github/workflows/ci.yml`
|
||||
|
||||
### 3. 文档
|
||||
- ✅ CI/CD 架构文档 (`.github/workflows/README.md`)
|
||||
- ✅ Secrets 设置详细指南 (`.github/workflows/SECRETS_SETUP_GUIDE.md`)
|
||||
- ✅ 快速开始指南 (`.github/workflows/QUICKSTART.md`)
|
||||
- ✅ 设置指南 (`.github/workflows/SETUP.md`)
|
||||
- ✅ 文档索引 (`.github/workflows/INDEX.md`)
|
||||
|
||||
### 4. 自动化脚本
|
||||
- ✅ Secrets 设置脚本 (`scripts/setup-github-secrets.sh`)
|
||||
- ✅ 本地 CI 验证脚本 (`scripts/verify-ci.sh`)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步操作
|
||||
|
||||
### 步骤 1: 设置 GitHub Secrets(必需)
|
||||
|
||||
运行交互式设置脚本:
|
||||
|
||||
```bash
|
||||
./scripts/setup-github-secrets.sh
|
||||
```
|
||||
|
||||
或手动设置(需要 3 个 secrets):
|
||||
|
||||
#### Secret 1: HARBOR_USERNAME
|
||||
```bash
|
||||
echo "admin" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
#### Secret 2: HARBOR_PASSWORD
|
||||
```bash
|
||||
# 会提示输入密码
|
||||
gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
#### Secret 3: DEPLOY_REPO_TOKEN
|
||||
|
||||
**首先创建 GitHub Personal Access Token:**
|
||||
1. 访问: https://github.com/settings/tokens
|
||||
2. 点击 "Generate new token (classic)"
|
||||
3. 名称: `ljwx-shiti-deploy-access`
|
||||
4. 权限:
|
||||
- ✅ `repo` (完整仓库访问)
|
||||
- ✅ `workflow` (更新 workflow 文件)
|
||||
5. 生成并复制 token
|
||||
|
||||
**然后设置 secret:**
|
||||
```bash
|
||||
# 会提示输入 token
|
||||
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
**验证 secrets:**
|
||||
```bash
|
||||
gh secret list --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 应该看到:
|
||||
# HARBOR_USERNAME Updated 2026-02-26
|
||||
# HARBOR_PASSWORD Updated 2026-02-26
|
||||
# DEPLOY_REPO_TOKEN Updated 2026-02-26
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤 2: 提交代码触发 CI/CD
|
||||
|
||||
```bash
|
||||
# 添加所有新文件
|
||||
git add .github/ backend/Dockerfile scripts/ docs/
|
||||
|
||||
# 提交
|
||||
git commit -m "ci: add GitHub Actions CI/CD pipeline
|
||||
|
||||
- Add Dockerfile with health check and non-root user
|
||||
- Add complete CI/CD workflow (test, build, deploy)
|
||||
- Add documentation and setup scripts
|
||||
- Add secrets setup guide"
|
||||
|
||||
# 推送到 GitHub(触发 CI/CD)
|
||||
git push origin master
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 步骤 3: 监控 CI/CD 运行
|
||||
|
||||
#### 方式 A: 浏览器
|
||||
访问: https://github.com/BrunoGao/ljwx-shiti/actions
|
||||
|
||||
#### 方式 B: GitHub CLI
|
||||
```bash
|
||||
# 查看运行列表
|
||||
gh run list --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 实时监控
|
||||
gh run watch --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 查看日志
|
||||
gh run view --log --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 CI/CD 流程说明
|
||||
|
||||
### 阶段 1: Test (约 20 分钟)
|
||||
```
|
||||
✅ Checkout code
|
||||
✅ Setup Python 3.11
|
||||
✅ Install dependencies
|
||||
✅ Run linter (ruff)
|
||||
✅ Run type checker (mypy)
|
||||
✅ Run migrations (alembic)
|
||||
✅ Run tests with coverage (pytest)
|
||||
✅ Upload coverage to Codecov
|
||||
```
|
||||
|
||||
### 阶段 2: Build and Push (约 30 分钟)
|
||||
```
|
||||
✅ Checkout code
|
||||
✅ Setup Docker Buildx
|
||||
✅ Login to Harbor
|
||||
✅ Build Docker image
|
||||
✅ Push to Harbor
|
||||
- Image: harbor.omniverseai.net/ljwx/ljwx-shiti:<commit-sha>
|
||||
- Tag: latest
|
||||
```
|
||||
|
||||
### 阶段 3: Update Deploy (约 10 分钟)
|
||||
```
|
||||
✅ Clone ljwx-deploy repository
|
||||
✅ Update kustomization.yaml
|
||||
- Path: apps/ljwx-shiti/overlays/prod/kustomization.yaml
|
||||
- Update image tag to <commit-sha>
|
||||
✅ Create Pull Request
|
||||
- Title: release(ljwx-shiti): <commit-sha>
|
||||
- Base: main
|
||||
```
|
||||
|
||||
### 阶段 4: ArgoCD 自动部署 (约 30 秒)
|
||||
```
|
||||
✅ ArgoCD 检测 Git 变更
|
||||
✅ 应用 Kustomize 配置
|
||||
✅ 滚动更新 Deployment
|
||||
✅ 健康检查通过
|
||||
✅ 应用运行在 K8s 集群
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 验证清单
|
||||
|
||||
### ✅ Secrets 配置
|
||||
```bash
|
||||
# 检查 secrets
|
||||
gh secret list --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 应该有 3 个 secrets:
|
||||
# - HARBOR_USERNAME
|
||||
# - HARBOR_PASSWORD
|
||||
# - DEPLOY_REPO_TOKEN
|
||||
```
|
||||
|
||||
### ✅ Workflow 文件
|
||||
```bash
|
||||
# 检查 workflow 文件
|
||||
cat .github/workflows/ci.yml
|
||||
|
||||
# 验证语法
|
||||
gh workflow view ci.yml --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
### ✅ Harbor 访问
|
||||
```bash
|
||||
# 测试 Harbor 登录
|
||||
docker login harbor.omniverseai.net
|
||||
# 输入用户名和密码
|
||||
|
||||
# 应该看到: Login Succeeded
|
||||
```
|
||||
|
||||
### ✅ Deploy Repo 访问
|
||||
```bash
|
||||
# 测试 token 权限
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://api.github.com/repos/BrunoGao/ljwx-deploy
|
||||
|
||||
# 应该返回仓库信息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 故障排查
|
||||
|
||||
### 问题 1: Secrets 未设置
|
||||
|
||||
**症状**: Workflow 失败,提示 "Secret not found"
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 重新设置 secrets
|
||||
./scripts/setup-github-secrets.sh
|
||||
```
|
||||
|
||||
### 问题 2: Harbor 登录失败
|
||||
|
||||
**症状**: "Error response from daemon: unauthorized"
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 1. 验证本地登录
|
||||
docker login harbor.omniverseai.net
|
||||
|
||||
# 2. 检查 secrets
|
||||
gh secret list --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 3. 重新设置 Harbor secrets
|
||||
echo "admin" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
|
||||
gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
### 问题 3: Deploy Repo 访问失败
|
||||
|
||||
**症状**: "Repository not found"
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 1. 测试 token
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://api.github.com/repos/BrunoGao/ljwx-deploy
|
||||
|
||||
# 2. 重新生成 token(确保有 repo 和 workflow 权限)
|
||||
# 3. 更新 secret
|
||||
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
|
||||
```
|
||||
|
||||
### 问题 4: 测试失败
|
||||
|
||||
**症状**: pytest 失败
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 1. 本地运行测试
|
||||
cd backend
|
||||
pytest -v
|
||||
|
||||
# 2. 检查测试日志
|
||||
gh run view --log --repo BrunoGao/ljwx-shiti
|
||||
|
||||
# 3. 修复测试后重新推送
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考文档
|
||||
|
||||
### 快速参考
|
||||
- [QUICKSTART.md](.github/workflows/QUICKSTART.md) - 快速开始
|
||||
- [INDEX.md](.github/workflows/INDEX.md) - 文档索引
|
||||
|
||||
### 详细指南
|
||||
- [SECRETS_SETUP_GUIDE.md](.github/workflows/SECRETS_SETUP_GUIDE.md) - Secrets 设置
|
||||
- [README.md](.github/workflows/README.md) - CI/CD 架构
|
||||
- [SETUP.md](.github/workflows/SETUP.md) - 完整设置
|
||||
|
||||
### 脚本
|
||||
- `scripts/setup-github-secrets.sh` - Secrets 设置脚本
|
||||
- `scripts/verify-ci.sh` - 本地验证脚本
|
||||
|
||||
---
|
||||
|
||||
## 🎯 成功标志
|
||||
|
||||
当你看到以下内容时,说明 CI/CD 设置成功:
|
||||
|
||||
1. ✅ GitHub Actions 运行成功(绿色勾)
|
||||
2. ✅ Harbor 中有新镜像: `ljwx-shiti:<commit-sha>`
|
||||
3. ✅ ljwx-deploy 仓库有新 PR
|
||||
4. ✅ ArgoCD 自动部署应用
|
||||
5. ✅ 应用在 K8s 集群中运行
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
如果遇到问题:
|
||||
1. 查看 [故障排查](#-故障排查) 部分
|
||||
2. 运行验证脚本: `./scripts/setup-github-secrets.sh`
|
||||
3. 查看 GitHub Actions 日志: `gh run view --log`
|
||||
4. 查看详细文档: `.github/workflows/SECRETS_SETUP_GUIDE.md`
|
||||
5. 联系 DevOps 团队
|
||||
|
||||
---
|
||||
|
||||
**祝你成功!🎉**
|
||||
|
||||
**最后更新**: 2026-02-26
|
||||
@@ -0,0 +1,38 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
postgresql-client \
|
||||
libgl1 \
|
||||
libglib2.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 复制依赖文件
|
||||
COPY backend/pyproject.toml .
|
||||
|
||||
# 安装 Python 依赖(不包含 paddle,生产环境降级到规则切分)
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir -e .
|
||||
|
||||
# 复制应用代码
|
||||
COPY backend/ .
|
||||
|
||||
# 创建非 root 用户
|
||||
RUN useradd -m -u 1000 appuser && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
USER appuser
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8000
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
|
||||
|
||||
# 启动命令
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
Executable
+292
@@ -0,0 +1,292 @@
|
||||
#!/bin/bash
|
||||
# GitHub Secrets 设置脚本
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
echo_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
echo_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
echo_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 gh CLI
|
||||
check_gh_cli() {
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo_error "GitHub CLI (gh) 未安装"
|
||||
echo_info "请访问: https://cli.github.com/ 安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! gh auth status &> /dev/null; then
|
||||
echo_error "GitHub CLI 未登录"
|
||||
echo_info "请运行: gh auth login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo_info "✅ GitHub CLI 已就绪"
|
||||
}
|
||||
|
||||
# 显示当前 secrets
|
||||
show_current_secrets() {
|
||||
echo_step "检查当前 secrets..."
|
||||
echo ""
|
||||
|
||||
local secrets=$(gh secret list --repo BrunoGao/ljwx-shiti 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$secrets" ]; then
|
||||
echo_warn "当前没有配置任何 secrets"
|
||||
else
|
||||
echo "当前已配置的 secrets:"
|
||||
echo "$secrets"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 设置 Harbor 凭据
|
||||
setup_harbor_secrets() {
|
||||
echo_step "设置 Harbor 镜像仓库凭据"
|
||||
echo ""
|
||||
|
||||
# HARBOR_USERNAME
|
||||
echo -n "请输入 Harbor 用户名 (默认: admin): "
|
||||
read -r harbor_username
|
||||
harbor_username=${harbor_username:-admin}
|
||||
|
||||
# HARBOR_PASSWORD
|
||||
echo -n "请输入 Harbor 密��: "
|
||||
read -rs harbor_password
|
||||
echo ""
|
||||
|
||||
if [ -z "$harbor_password" ]; then
|
||||
echo_error "Harbor 密码不能为空"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 测试 Harbor 登录
|
||||
echo_info "测试 Harbor 登录..."
|
||||
if echo "$harbor_password" | docker login harbor.omniverseai.net -u "$harbor_username" --password-stdin &> /dev/null; then
|
||||
echo_info "✅ Harbor 登录成功"
|
||||
docker logout harbor.omniverseai.net &> /dev/null
|
||||
else
|
||||
echo_error "❌ Harbor 登录失败,请检查用户名和密码"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 设置 secrets
|
||||
echo_info "设置 HARBOR_USERNAME..."
|
||||
echo "$harbor_username" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
|
||||
|
||||
echo_info "设置 HARBOR_PASSWORD..."
|
||||
echo "$harbor_password" | gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
|
||||
|
||||
echo_info "✅ Harbor secrets 设置完成"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 设置 Deploy Repo Token
|
||||
setup_deploy_token() {
|
||||
echo_step "设置部署仓库访问 Token"
|
||||
echo ""
|
||||
|
||||
echo "需要创建一个 GitHub Personal Access Token (PAT)"
|
||||
echo "Token 需要以下权限:"
|
||||
echo " - repo (完整仓库访问)"
|
||||
echo " - workflow (更新 workflow 文件)"
|
||||
echo ""
|
||||
echo "创建 Token 步骤:"
|
||||
echo "1. 访问: https://github.com/settings/tokens"
|
||||
echo "2. 点击 'Generate new token' → 'Generate new token (classic)'"
|
||||
echo "3. 设置名称: ljwx-shiti-deploy-access"
|
||||
echo "4. 选择权限: repo, workflow"
|
||||
echo "5. 点击 'Generate token'"
|
||||
echo "6. 复制生成的 token"
|
||||
echo ""
|
||||
|
||||
echo -n "是否已经创建了 Token? (y/n): "
|
||||
read -r has_token
|
||||
|
||||
if [ "$has_token" != "y" ]; then
|
||||
echo_warn "请先创建 Token,然后重新运行此脚本"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -n "请输入 GitHub Personal Access Token: "
|
||||
read -rs deploy_token
|
||||
echo ""
|
||||
|
||||
if [ -z "$deploy_token" ]; then
|
||||
echo_error "Token 不能为空"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 测试 token 权限
|
||||
echo_info "测试 Token 权限..."
|
||||
if curl -s -H "Authorization: token $deploy_token" \
|
||||
https://api.github.com/repos/BrunoGao/ljwx-deploy | grep -q '"name"'; then
|
||||
echo_info "✅ Token 权限验证成功"
|
||||
else
|
||||
echo_error "❌ Token 权限验证失败,请检查 token 是否正确"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 设置 secret
|
||||
echo_info "设置 DEPLOY_REPO_TOKEN..."
|
||||
echo "$deploy_token" | gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
|
||||
|
||||
echo_info "✅ Deploy token 设置完成"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 验证所有 secrets
|
||||
verify_secrets() {
|
||||
echo_step "验证 secrets 配置"
|
||||
echo ""
|
||||
|
||||
local required_secrets=("HARBOR_USERNAME" "HARBOR_PASSWORD" "DEPLOY_REPO_TOKEN")
|
||||
local all_ok=true
|
||||
|
||||
for secret in "${required_secrets[@]}"; do
|
||||
if gh secret list --repo BrunoGao/ljwx-shiti | grep -q "^$secret"; then
|
||||
echo_info "✅ $secret 已配置"
|
||||
else
|
||||
echo_error "❌ $secret 未配置"
|
||||
all_ok=false
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
if [ "$all_ok" = true ]; then
|
||||
echo_info "=========================================="
|
||||
echo_info "✅ 所有 secrets 配置完成!"
|
||||
echo_info "=========================================="
|
||||
return 0
|
||||
else
|
||||
echo_error "部分 secrets 未配置,请重新运行脚本"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示下一步
|
||||
show_next_steps() {
|
||||
echo ""
|
||||
echo_info "下一步操作:"
|
||||
echo ""
|
||||
echo "1. 提交代码触发 CI/CD:"
|
||||
echo " git add ."
|
||||
echo " git commit -m 'ci: add CI/CD pipeline'"
|
||||
echo " git push origin main"
|
||||
echo ""
|
||||
echo "2. 查看 GitHub Actions 运行状态:"
|
||||
echo " https://github.com/BrunoGao/ljwx-shiti/actions"
|
||||
echo ""
|
||||
echo "3. 或使用命令行查看:"
|
||||
echo " gh run list --repo BrunoGao/ljwx-shiti"
|
||||
echo " gh run watch --repo BrunoGao/ljwx-shiti"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 主菜单
|
||||
main_menu() {
|
||||
while true; do
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "GitHub Secrets 设置工具"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "1. 查看当前 secrets"
|
||||
echo "2. 设置 Harbor 凭据 (HARBOR_USERNAME, HARBOR_PASSWORD)"
|
||||
echo "3. 设置部署 Token (DEPLOY_REPO_TOKEN)"
|
||||
echo "4. 一键设置所有 secrets"
|
||||
echo "5. 验证 secrets 配置"
|
||||
echo "6. 退出"
|
||||
echo ""
|
||||
echo -n "请选择操作 (1-6): "
|
||||
read -r choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
show_current_secrets
|
||||
;;
|
||||
2)
|
||||
setup_harbor_secrets
|
||||
;;
|
||||
3)
|
||||
setup_deploy_token
|
||||
;;
|
||||
4)
|
||||
setup_harbor_secrets && setup_deploy_token && verify_secrets && show_next_steps
|
||||
;;
|
||||
5)
|
||||
verify_secrets
|
||||
;;
|
||||
6)
|
||||
echo_info "退出"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo_error "无效选择,请输入 1-6"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# 非交互模式
|
||||
non_interactive_mode() {
|
||||
echo_info "非交互模式"
|
||||
echo ""
|
||||
|
||||
# 从环境变量读取
|
||||
if [ -n "${HARBOR_USERNAME:-}" ] && [ -n "${HARBOR_PASSWORD:-}" ] && [ -n "${DEPLOY_REPO_TOKEN:-}" ]; then
|
||||
echo_info "从环境变量读取 secrets..."
|
||||
|
||||
echo "$HARBOR_USERNAME" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
|
||||
echo "$HARBOR_PASSWORD" | gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
|
||||
echo "$DEPLOY_REPO_TOKEN" | gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
|
||||
|
||||
verify_secrets
|
||||
show_next_steps
|
||||
else
|
||||
echo_error "环境变量未设置"
|
||||
echo_info "请设置以下环境变量:"
|
||||
echo " - HARBOR_USERNAME"
|
||||
echo " - HARBOR_PASSWORD"
|
||||
echo " - DEPLOY_REPO_TOKEN"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo_info "GitHub Secrets 设置脚本"
|
||||
echo ""
|
||||
|
||||
check_gh_cli
|
||||
|
||||
# 检查是否为非交互模式
|
||||
if [ "${NON_INTERACTIVE:-false}" = "true" ]; then
|
||||
non_interactive_mode
|
||||
else
|
||||
main_menu
|
||||
fi
|
||||
}
|
||||
|
||||
# 运行主函数
|
||||
main "$@"
|
||||
Executable
+298
@@ -0,0 +1,298 @@
|
||||
#!/bin/bash
|
||||
# 本地 CI/CD 验证脚本
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
echo_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
echo_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查必需的工具
|
||||
check_requirements() {
|
||||
echo_info "检查必需的工具..."
|
||||
|
||||
local missing_tools=()
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
missing_tools+=("docker")
|
||||
fi
|
||||
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
missing_tools+=("docker-compose")
|
||||
fi
|
||||
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
missing_tools+=("python3")
|
||||
fi
|
||||
|
||||
if [ ${#missing_tools[@]} -ne 0 ]; then
|
||||
echo_error "缺少必需的工具: ${missing_tools[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo_info "✅ 所有必需工具已安装"
|
||||
}
|
||||
|
||||
# 运行 linter
|
||||
run_linter() {
|
||||
echo_info "运行 linter..."
|
||||
|
||||
cd backend
|
||||
|
||||
if ! pip list | grep -q ruff; then
|
||||
echo_info "安装 ruff..."
|
||||
pip install ruff
|
||||
fi
|
||||
|
||||
if ruff check .; then
|
||||
echo_info "✅ Linter 检查通过"
|
||||
else
|
||||
echo_error "❌ Linter 检查失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
# 启动测试数据库
|
||||
start_test_db() {
|
||||
echo_info "启动测试数据库..."
|
||||
|
||||
# 检查是否已经运行
|
||||
if docker ps | grep -q wrongbook-postgres-test; then
|
||||
echo_warn "测试数据库已在运行"
|
||||
return
|
||||
fi
|
||||
|
||||
# 启动 PostgreSQL
|
||||
docker run -d \
|
||||
--name wrongbook-postgres-test \
|
||||
-e POSTGRES_USER=wrongbook \
|
||||
-e POSTGRES_PASSWORD=wrongbook \
|
||||
-e POSTGRES_DB=wrongbook_test \
|
||||
-p 5433:5432 \
|
||||
postgres:16-alpine
|
||||
|
||||
# 启动 Redis
|
||||
docker run -d \
|
||||
--name wrongbook-redis-test \
|
||||
-p 6380:6379 \
|
||||
redis:7-alpine
|
||||
|
||||
echo_info "等待数据库启动..."
|
||||
sleep 5
|
||||
|
||||
echo_info "✅ 测试数据库已启动"
|
||||
}
|
||||
|
||||
# 停止测试数据库
|
||||
stop_test_db() {
|
||||
echo_info "停止测试数据库..."
|
||||
|
||||
docker stop wrongbook-postgres-test wrongbook-redis-test 2>/dev/null || true
|
||||
docker rm wrongbook-postgres-test wrongbook-redis-test 2>/dev/null || true
|
||||
|
||||
echo_info "✅ 测试数据库已停止"
|
||||
}
|
||||
|
||||
# 运行测试
|
||||
run_tests() {
|
||||
echo_info "运行测试..."
|
||||
|
||||
cd backend
|
||||
|
||||
# 安装依赖
|
||||
if [ ! -d ".venv" ]; then
|
||||
echo_info "创建虚拟环境..."
|
||||
python3 -m venv .venv
|
||||
fi
|
||||
|
||||
source .venv/bin/activate
|
||||
|
||||
echo_info "安装依赖..."
|
||||
pip install -q --upgrade pip
|
||||
pip install -q -e ".[dev]"
|
||||
|
||||
# 运行迁移
|
||||
echo_info "运行数据库迁移..."
|
||||
export DATABASE_URL="postgresql://wrongbook:wrongbook@localhost:5433/wrongbook_test"
|
||||
alembic upgrade head
|
||||
|
||||
# 运行测试
|
||||
echo_info "运行测试..."
|
||||
export TEST_DATABASE_URL="postgresql+asyncpg://wrongbook:wrongbook@localhost:5433/wrongbook_test"
|
||||
export REDIS_URL="redis://localhost:6380/0"
|
||||
export ANTHROPIC_AUTH_TOKEN="sk-ant-test-placeholder"
|
||||
export ANTHROPIC_BASE_URL="https://api.anthropic.com"
|
||||
export ANTHROPIC_MODEL="claude-sonnet-4-6"
|
||||
export TESTING="true"
|
||||
export JWT_SECRET="test-secret-key"
|
||||
|
||||
if pytest -v --cov=app --cov-report=term; then
|
||||
echo_info "✅ 测试通过"
|
||||
else
|
||||
echo_error "❌ 测试失败"
|
||||
deactivate
|
||||
cd ..
|
||||
exit 1
|
||||
fi
|
||||
|
||||
deactivate
|
||||
cd ..
|
||||
}
|
||||
|
||||
# 构建 Docker 镜像
|
||||
build_docker_image() {
|
||||
echo_info "构建 Docker 镜像..."
|
||||
|
||||
local image_tag="ljwx-shiti:local-test"
|
||||
|
||||
if docker build -t "$image_tag" -f backend/Dockerfile .; then
|
||||
echo_info "✅ Docker 镜像构建成功: $image_tag"
|
||||
else
|
||||
echo_error "❌ Docker 镜像构建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 显示镜像信息
|
||||
echo_info "镜像信息:"
|
||||
docker images "$image_tag"
|
||||
}
|
||||
|
||||
# 测试 Docker 镜像
|
||||
test_docker_image() {
|
||||
echo_info "测试 Docker 镜像..."
|
||||
|
||||
local image_tag="ljwx-shiti:local-test"
|
||||
local container_name="ljwx-shiti-test"
|
||||
|
||||
# 停止并删除旧容器
|
||||
docker stop "$container_name" 2>/dev/null || true
|
||||
docker rm "$container_name" 2>/dev/null || true
|
||||
|
||||
# 启动容器
|
||||
echo_info "启动测试容器..."
|
||||
docker run -d \
|
||||
--name "$container_name" \
|
||||
-p 8000:8000 \
|
||||
-e DATABASE_URL="postgresql+asyncpg://wrongbook:wrongbook@host.docker.internal:5433/wrongbook_test" \
|
||||
-e REDIS_URL="redis://host.docker.internal:6380/0" \
|
||||
-e ANTHROPIC_AUTH_TOKEN="sk-ant-test-placeholder" \
|
||||
-e JWT_SECRET="test-secret-key" \
|
||||
"$image_tag"
|
||||
|
||||
echo_info "等待应用启动..."
|
||||
sleep 10
|
||||
|
||||
# 测试健康检查
|
||||
echo_info "测试健康检查端点..."
|
||||
if curl -f http://localhost:8000/health; then
|
||||
echo_info "✅ 健康检查通过"
|
||||
else
|
||||
echo_error "❌ 健康检查失败"
|
||||
docker logs "$container_name"
|
||||
docker stop "$container_name"
|
||||
docker rm "$container_name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 清理
|
||||
echo_info "清理测试容器..."
|
||||
docker stop "$container_name"
|
||||
docker rm "$container_name"
|
||||
|
||||
echo_info "✅ Docker 镜像测试通过"
|
||||
}
|
||||
|
||||
# 清理
|
||||
cleanup() {
|
||||
echo_info "清理资源..."
|
||||
stop_test_db
|
||||
echo_info "✅ 清理完成"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
echo_info "开始本地 CI/CD 验证..."
|
||||
echo ""
|
||||
|
||||
# 设置清理 trap
|
||||
trap cleanup EXIT
|
||||
|
||||
# 检查参数
|
||||
local skip_tests=false
|
||||
local skip_docker=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--skip-tests)
|
||||
skip_tests=true
|
||||
shift
|
||||
;;
|
||||
--skip-docker)
|
||||
skip_docker=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " --skip-tests 跳过测试"
|
||||
echo " --skip-docker 跳过 Docker 构建和测试"
|
||||
echo " --help 显示帮助信息"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo_error "未知选项: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 执行验证步骤
|
||||
check_requirements
|
||||
|
||||
if [ "$skip_tests" = false ]; then
|
||||
run_linter
|
||||
start_test_db
|
||||
run_tests
|
||||
else
|
||||
echo_warn "跳过测试"
|
||||
fi
|
||||
|
||||
if [ "$skip_docker" = false ]; then
|
||||
build_docker_image
|
||||
if [ "$skip_tests" = false ]; then
|
||||
test_docker_image
|
||||
fi
|
||||
else
|
||||
echo_warn "跳过 Docker 构建和测试"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo_info "=========================================="
|
||||
echo_info "✅ 本地 CI/CD 验证完成!"
|
||||
echo_info "=========================================="
|
||||
echo ""
|
||||
echo_info "下一步:"
|
||||
echo_info "1. 提交代码: git add . && git commit -m 'ci: add CI/CD pipeline'"
|
||||
echo_info "2. 推送到 GitHub: git push origin main"
|
||||
echo_info "3. 查看 GitHub Actions: https://github.com/BrunoGao/ljwx-shiti/actions"
|
||||
}
|
||||
|
||||
# 运行主函数
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user