From 0c908b128c67e077b8fef635ebe87d195e4eab8b Mon Sep 17 00:00:00 2001 From: enoch Date: Thu, 7 Aug 2025 17:09:29 +0800 Subject: [PATCH] feat: add multi-platform CI/CD support for GitHub, Gitea, and GitLab --- .github/workflows/deploy.yml | 148 +++++++++------------ .gitlab-ci.yml | 157 ++++++++++++++++++++++ README.md | 59 ++++++++ docker-compose.prod.yml | 55 ++++++++ docs/ci-cd-setup.md | 251 +++++++++++++++++++++++++++++++++++ 5 files changed, 586 insertions(+), 84 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 docker-compose.prod.yml create mode 100644 docs/ci-cd-setup.md diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1043708..91bdfb2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,15 +1,15 @@ -name: Deploy to Production +name: CI/CD Pipeline on: push: - branches: [main] + branches: [main, master] tags: ['v*'] pull_request: - branches: [main] + branches: [main, master] env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: rust-user-api + REGISTRY: ${{ vars.REGISTRY || 'docker.io' }} jobs: test: @@ -21,11 +21,11 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - components: rustfmt, clippy + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source ~/.cargo/env + rustup component add rustfmt clippy + echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Cache dependencies uses: actions/cache@v3 @@ -37,16 +37,19 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Check formatting - run: cargo fmt -- --check + run: | + source ~/.cargo/env + cargo fmt -- --check - name: Run clippy - run: cargo clippy -- -D warnings + run: | + source ~/.cargo/env + cargo clippy -- -D warnings - name: Run tests - run: cargo test --verbose - - - name: Run integration tests - run: cargo test --test integration_tests --verbose + run: | + source ~/.cargo/env + cargo test --verbose security-scan: name: Security Scan @@ -58,30 +61,15 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source ~/.cargo/env - - name: Install cargo-audit - run: cargo install cargo-audit - - - name: Run security audit - run: cargo audit - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - scan-type: 'fs' - scan-ref: '.' - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Upload Trivy scan results - uses: github/codeql-action/upload-sarif@v2 - if: always() - with: - sarif_file: 'trivy-results.sarif' + - name: Install and run cargo-audit + run: | + source ~/.cargo/env + cargo install cargo-audit + cargo audit build: name: Build Docker Image @@ -90,8 +78,7 @@ jobs: if: github.event_name == 'push' outputs: - image: ${{ steps.image.outputs.image }} - digest: ${{ steps.build.outputs.digest }} + image: ${{ steps.meta.outputs.tags }} steps: - name: Checkout code @@ -100,59 +87,48 @@ jobs: - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Container Registry + - name: Login to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASS }} - - name: Extract metadata + - name: Build metadata id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha,prefix={{branch}}- + run: | + TAGS="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + TAGS="$TAGS,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$VERSION" + else + TAGS="$TAGS,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" + fi + echo "tags=$TAGS" >> $GITHUB_OUTPUT - name: Build and push Docker image - id: build uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - - - name: Output image - id: image - run: | - echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" >> $GITHUB_OUTPUT deploy-staging: name: Deploy to Staging runs-on: ubuntu-latest needs: build - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' environment: staging steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Deploy to staging run: | - echo "Deploying to staging environment..." - echo "Image: ${{ needs.build.outputs.image }}" + echo "部署到测试环境..." + echo "镜像: ${{ needs.build.outputs.image }}" # 这里添加实际的部署逻辑 - # 例如: kubectl set image deployment/rust-api rust-api=${{ needs.build.outputs.image }} deploy-production: name: Deploy to Production @@ -162,42 +138,46 @@ jobs: environment: production steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup SSH - uses: webfactory/ssh-agent@v0.7.0 - with: - ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }} + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts - name: Deploy to production run: | - ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF' + ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF' cd /opt/rust-api # 备份当前数据库 - docker-compose -f docker-compose.prod.yml exec -T rust-user-api \ - sqlite3 /app/data/production.db ".backup /app/data/backup-$(date +%Y%m%d-%H%M%S).db" + if [ -f docker-compose.prod.yml ]; then + docker-compose -f docker-compose.prod.yml exec -T rust-user-api \ + sqlite3 /app/data/production.db ".backup /app/data/backup-$(date +%Y%m%d-%H%M%S).db" || true + fi # 拉取最新镜像 docker pull ${{ needs.build.outputs.image }} # 更新docker-compose文件中的镜像标签 - sed -i 's|image: .*|image: ${{ needs.build.outputs.image }}|' docker-compose.prod.yml - - # 重新部署 - docker-compose -f docker-compose.prod.yml up -d + if [ -f docker-compose.prod.yml ]; then + sed -i 's|image: .*rust-user-api.*|image: ${{ needs.build.outputs.image }}|' docker-compose.prod.yml + docker-compose -f docker-compose.prod.yml up -d + else + # 如果没有 prod 配置文件,使用默认配置 + docker-compose down || true + docker-compose up -d + fi # 等待服务启动 sleep 30 # 健康检查 - if curl -f http://localhost/health; then + if curl -f http://localhost:8080/health || curl -f http://localhost/health; then echo "✅ 部署成功!" else echo "❌ 部署失败,开始回滚..." - docker-compose -f docker-compose.prod.yml down - # 这里可以添加回滚逻辑 + docker-compose down exit 1 fi EOF diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..7ca45d4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,157 @@ +stages: + - test + - security + - build + - deploy + +variables: + IMAGE_NAME: rust-user-api + REGISTRY: ${CI_REGISTRY:-docker.io} + +# 缓存配置 +.cargo_cache: &cargo_cache + cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - ~/.cargo/registry/ + - ~/.cargo/git/ + - target/ + +test: + stage: test + image: ubuntu:latest + <<: *cargo_cache + before_script: + - apt-get update -qq && apt-get install -y -qq curl build-essential + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - source ~/.cargo/env + - rustup component add rustfmt clippy + script: + - source ~/.cargo/env + - cargo fmt -- --check + - cargo clippy -- -D warnings + - cargo test --verbose + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_COMMIT_BRANCH == "master" + - if: $CI_COMMIT_TAG + +security-scan: + stage: security + image: ubuntu:latest + needs: [test] + before_script: + - apt-get update -qq && apt-get install -y -qq curl build-essential + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - source ~/.cargo/env + script: + - source ~/.cargo/env + - cargo install cargo-audit + - cargo audit + rules: + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_COMMIT_BRANCH == "master" + - if: $CI_COMMIT_TAG + +build: + stage: build + image: docker:latest + services: + - docker:dind + needs: [test, security-scan] + variables: + DOCKER_TLS_CERTDIR: "/certs" + before_script: + - docker login -u $REGISTRY_USER -p $REGISTRY_PASS $REGISTRY + script: + - | + if [ "$CI_COMMIT_TAG" ]; then + TAGS="$REGISTRY/$IMAGE_NAME:latest $REGISTRY/$IMAGE_NAME:$CI_COMMIT_TAG" + else + TAGS="$REGISTRY/$IMAGE_NAME:latest $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA" + fi + - docker build -t $REGISTRY/$IMAGE_NAME:latest . + - | + for tag in $TAGS; do + docker tag $REGISTRY/$IMAGE_NAME:latest $tag + docker push $tag + done + - echo "IMAGE_TAG=$REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA" > build.env + artifacts: + reports: + dotenv: build.env + rules: + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_COMMIT_BRANCH == "master" + - if: $CI_COMMIT_TAG + +deploy-staging: + stage: deploy + image: alpine:latest + needs: [build] + environment: + name: staging + before_script: + - apk add --no-cache curl + script: + - echo "部署到测试环境..." + - echo "镜像: $IMAGE_TAG" + # 这里添加实际的部署逻辑 + rules: + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_COMMIT_BRANCH == "master" + +deploy-production: + stage: deploy + image: alpine:latest + needs: [build, deploy-staging] + environment: + name: production + before_script: + - apk add --no-cache openssh-client curl + - mkdir -p ~/.ssh + - echo "$DEPLOY_KEY" > ~/.ssh/id_rsa + - chmod 600 ~/.ssh/id_rsa + - ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts + script: + - | + ssh $DEPLOY_USER@$DEPLOY_HOST << 'EOF' + cd /opt/rust-api + + # 备份当前数据库 + if [ -f docker-compose.prod.yml ]; then + docker-compose -f docker-compose.prod.yml exec -T rust-user-api \ + sqlite3 /app/data/production.db ".backup /app/data/backup-$(date +%Y%m%d-%H%M%S).db" || true + fi + + # 拉取最新镜像 + docker pull $IMAGE_TAG + + # 更新docker-compose文件中的镜像标签 + if [ -f docker-compose.prod.yml ]; then + sed -i "s|image: .*rust-user-api.*|image: $IMAGE_TAG|" docker-compose.prod.yml + docker-compose -f docker-compose.prod.yml up -d + else + docker-compose down || true + docker-compose up -d + fi + + # 等待服务启动 + sleep 30 + + # 健康检查 + if curl -f http://localhost:8080/health || curl -f http://localhost/health; then + echo "✅ 部署成功!" + else + echo "❌ 部署失败,开始回滚..." + docker-compose down + exit 1 + fi + EOF + - echo "🎉 生产环境部署成功!" + - echo "版本: $CI_COMMIT_TAG" + - echo "镜像: $IMAGE_TAG" + rules: + - if: $CI_COMMIT_TAG =~ /^v.*/ + when: manual \ No newline at end of file diff --git a/README.md b/README.md index b7d5284..3ba2f1a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ - ✅ 请求验证和错误处理 - ✅ JWT 身份认证 - ✅ 结构化日志记录 +- ✅ CI/CD 流水线(GitHub Actions、Gitea Actions、GitLab CI) +- ✅ Docker 容器化部署 +- ✅ 安全扫描和代码质量检查 - 🔄 SQLite 数据库集成(计划中) - 🔄 API 文档和测试(计划中) @@ -45,6 +48,22 @@ cargo run 服务器将在 `http://127.0.0.1:3000` 启动。 +### 4. Docker 部署(可选) + +```bash +# 构建镜像 +docker build -t rust-user-api . + +# 运行容器 +docker run -p 3000:3000 rust-user-api +``` + +或使用 Docker Compose: + +```bash +docker-compose up -d +``` + ## API 端点 ### 基础端点 @@ -116,6 +135,46 @@ cargo clippy cargo fmt ``` +## CI/CD 部署 + +本项目支持在多个平台上进行自动化部署: + +### 支持的平台 +- ✅ **GitHub Actions** - 完整支持 +- ✅ **Gitea Actions** - 兼容支持 +- ✅ **GitLab CI/CD** - 原生支持 + +### 配置文件 +- `.github/workflows/deploy.yml` - GitHub/Gitea Actions 配置 +- `.gitlab-ci.yml` - GitLab CI/CD 配置 + +### 部署流程 +1. **测试阶段** - 代码格式检查、Clippy 检查、单元测试 +2. **安全扫描** - 依赖漏洞扫描 +3. **构建阶段** - Docker 镜像构建和推送 +4. **部署阶段** - 自动部署到测试/生产环境 + +### 快速配置 +详细的配置说明请参考:[CI/CD 配置指南](docs/ci-cd-setup.md) + +## Docker 支持 + +### 本地构建 +```bash +docker build -t rust-user-api . +docker run -p 3000:3000 rust-user-api +``` + +### Docker Compose +```bash +docker-compose up -d +``` + +### 生产部署 +```bash +docker-compose -f docker-compose.prod.yml up -d +``` + ## 学习路径 这个项目按照以下阶段设计,适合渐进式学习: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..198b1e8 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,55 @@ +version: '3.8' + +services: + rust-user-api: + image: docker.io/your-username/rust-user-api:latest + container_name: rust-user-api-prod + ports: + - "8080:3000" + volumes: + - ./data:/app/data + - ./logs:/app/logs + environment: + - RUST_LOG=warn + - RUST_BACKTRACE=1 + - DATABASE_URL=/app/data/production.db + - JWT_SECRET=${JWT_SECRET:-your-super-secret-jwt-key-change-this-in-production} + - SERVER_HOST=0.0.0.0 + - SERVER_PORT=3000 + restart: always + deploy: + resources: + limits: + memory: 512M + cpus: '0.5' + reservations: + memory: 256M + cpus: '0.25' + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # 可选:添加反向代理 + nginx: + image: nginx:alpine + container_name: rust-api-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + - ./ssl:/etc/nginx/ssl:ro + depends_on: + - rust-user-api + restart: always + profiles: + - with-nginx + +volumes: + data: + driver: local + logs: + driver: local \ No newline at end of file diff --git a/docs/ci-cd-setup.md b/docs/ci-cd-setup.md new file mode 100644 index 0000000..4b95c6b --- /dev/null +++ b/docs/ci-cd-setup.md @@ -0,0 +1,251 @@ +# CI/CD 配置指南 + +本项目支持在 GitHub、Gitea 和 GitLab 三个平台上运行 CI/CD 流水线。 + +## 文件说明 + +- `.github/workflows/deploy.yml` - GitHub Actions / Gitea Actions 配置 +- `.gitlab-ci.yml` - GitLab CI/CD 配置 + +## 平台兼容性 + +### ✅ GitHub +- 完全支持所有功能 +- 使用 GitHub Actions 生态 +- 支持 GitHub Container Registry (ghcr.io) + +### ✅ Gitea +- 兼容 GitHub Actions 语法 +- 支持大部分常用 Actions +- 需要配置自定义镜像仓库 + +### ✅ GitLab +- 使用 GitLab CI/CD 语法 +- 支持 GitLab Container Registry +- 手动部署到生产环境 + +## 必需的 Secrets 配置 + +### GitHub / Gitea + +在仓库设置 → Secrets 中添加: + +``` +REGISTRY_USER # 镜像仓库用户名 +REGISTRY_PASS # 镜像仓库密码 +DEPLOY_KEY # SSH 私钥(用于部署) +DEPLOY_USER # 部署服务器用户名 +DEPLOY_HOST # 部署服务器地址 +``` + +### GitLab + +在项目设置 → CI/CD → Variables 中添加: + +``` +REGISTRY_USER # 镜像仓库用户名 +REGISTRY_PASS # 镜像仓库密码 +DEPLOY_KEY # SSH 私钥(用于部署) +DEPLOY_USER # 部署服务器用户名 +DEPLOY_HOST # 部署服务器地址 +``` + +## 可选的 Variables 配置 + +### GitHub / Gitea + +在仓库设置 → Variables 中添加: + +``` +REGISTRY # 自定义镜像仓库地址(默认: docker.io) +``` + +### GitLab + +GitLab 会自动提供 `CI_REGISTRY` 变量,指向项目的容器仓库。 + +## 镜像仓库配置 + +### Docker Hub +``` +REGISTRY_USER = your-dockerhub-username +REGISTRY_PASS = your-dockerhub-token +REGISTRY = docker.io # 可选,默认值 +``` + +### GitHub Container Registry +``` +REGISTRY_USER = your-github-username +REGISTRY_PASS = your-github-token +REGISTRY = ghcr.io +``` + +### 私有仓库 +``` +REGISTRY_USER = your-username +REGISTRY_PASS = your-password +REGISTRY = your-registry.com +``` + +## SSH 密钥配置 + +1. 在部署服务器上生成 SSH 密钥对: +```bash +ssh-keygen -t rsa -b 4096 -C "deploy@your-project" +``` + +2. 将公钥添加到服务器的 `~/.ssh/authorized_keys`: +```bash +cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys +``` + +3. 将私钥内容复制到 `DEPLOY_KEY` secret 中: +```bash +cat ~/.ssh/id_rsa +``` + +## 部署环境配置 + +### 服务器目录结构 +``` +/opt/rust-api/ +├── docker-compose.yml # 开发/默认配置 +├── docker-compose.prod.yml # 生产配置(可选) +└── data/ # 数据目录 + └── production.db # 生产数据库 +``` + +### Docker Compose 配置示例 + +**docker-compose.yml** +```yaml +version: '3.8' +services: + rust-user-api: + image: docker.io/your-username/rust-user-api:latest + ports: + - "8080:8080" + volumes: + - ./data:/app/data + environment: + - RUST_LOG=info + - DATABASE_URL=/app/data/production.db + restart: unless-stopped +``` + +**docker-compose.prod.yml** +```yaml +version: '3.8' +services: + rust-user-api: + image: docker.io/your-username/rust-user-api:latest + ports: + - "8080:8080" + volumes: + - ./data:/app/data + environment: + - RUST_LOG=warn + - DATABASE_URL=/app/data/production.db + restart: always + deploy: + resources: + limits: + memory: 512M + reservations: + memory: 256M +``` + +## 流水线触发条件 + +### 测试阶段 +- 推送到 `main` 或 `master` 分支 +- 创建 Pull Request / Merge Request + +### 构建阶段 +- 推送到 `main` 或 `master` 分支 +- 推送标签(如 `v1.0.0`) + +### 部署阶段 +- **测试环境**: 推送到 `main` 或 `master` 分支时自动部署 +- **生产环境**: 推送版本标签时部署(GitLab 需要手动触发) + +## 版本标签规范 + +使用语义化版本标签来触发生产部署: + +```bash +git tag v1.0.0 +git push origin v1.0.0 +``` + +标签格式:`v..` + +## 健康检查 + +流水线会在部署后进行健康检查,访问以下端点: +- `http://localhost:8080/health` +- `http://localhost/health` + +确保您的应用提供健康检查端点。 + +## 故障排除 + +### 常见问题 + +1. **Rust 安装失败** + - 检查网络连接 + - 确保有足够的磁盘空间 + +2. **Docker 构建失败** + - 检查 Dockerfile 语法 + - 确保所有依赖都已正确配置 + +3. **部署失败** + - 检查 SSH 密钥配置 + - 确保部署服务器可访问 + - 检查服务器磁盘空间 + +4. **镜像推送失败** + - 检查镜像仓库凭据 + - 确保有推送权限 + +### 调试技巧 + +1. 查看流水线日志 +2. 在本地测试 Docker 构建 +3. 手动测试 SSH 连接 +4. 检查服务器日志 + +## 安全建议 + +1. 定期轮换 SSH 密钥 +2. 使用最小权限原则配置部署用户 +3. 定期更新镜像仓库凭据 +4. 启用双因素认证 +5. 监控部署日志 + +## 扩展配置 + +### 添加通知 + +可以在流水线中添加通知功能,如: +- Slack 通知 +- 邮件通知 +- 钉钉通知 +- 企业微信通知 + +### 多环境部署 + +可以配置多个环境: +- 开发环境 (dev) +- 测试环境 (staging) +- 预生产环境 (pre-prod) +- 生产环境 (production) + +### 蓝绿部署 + +对于零停机部署,可以配置蓝绿部署策略。 + +### 回滚机制 + +建议配置自动回滚机制,在部署失败时自动恢复到上一个稳定版本。 \ No newline at end of file