BodySense CI/CD 完整流程

BodySense 项目的完整 CI/CD 流程:代码提交、自动测试、构建镜像、自动部署、回滚策略。

#type / concept #status / growing #tech / ops #resource / github-actions

[!info] related notes

BodySense CI/CD 完整流程

完整流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                           代码提交流程                                       │
└─────────────────────────────────────────────────────────────────────────────┘

开发者本地开发


┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ git commit  │ ──► │ git push    │ ──► │ PR 创建     │
│             │     │             │     │             │
│ Conventional│     │ 触发 CI     │     │ Code Review │
│ Commits     │     │             │     │             │
└─────────────┘     └─────────────┘     └─────────────┘


┌─────────────────────────────────────────────────────────────────────────────┐
│                           CI 流程 (GitHub Actions)                          │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Commitlint  │ ──► │ Lint + Test │ ──► │ Build       │
│             │     │ (并行)      │     │             │
│ 检查格式    │     │             │     │ 构建镜像    │
└─────────────┘     └─────────────┘     └─────────────┘


                                        ┌─────────────┐
                                        │ 推送镜像    │
                                        │             │
                                        │ Docker Hub  │
                                        │ / ACR       │
                                        └─────────────┘


┌─────────────────────────────────────────────────────────────────────────────┐
│                           发布流程                                           │
└─────────────────────────────────────────────────────────────────────────────┘

PR 合并到 main


┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│release-please│ ──► │ Release PR  │ ──► │ GitHub      │
│ 创建 PR     │     │ 版本 bump   │     │ Release     │
│             │     │ CHANGELOG   │     │             │
└─────────────┘     └─────────────┘     └─────────────┘


                                        ┌─────────────┐
                                        │ Tag Push    │
                                        │ 触发部署    │
                                        └─────────────┘


┌─────────────────────────────────────────────────────────────────────────────┐
│                           部署流程                                           │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ 构建生产    │ ──► │ 推送 prod   │ ──► │ Watchtower  │
│ 镜像        │     │ latest tag  │     │ 检测更新    │
│             │     │             │     │             │
└─────────────┘     └─────────────┘     └─────────────┘


                                        ┌─────────────┐
                                        │ 滚动重启    │
                                        │ 容器        │
                                        │             │
                                        └─────────────┘


                                        ┌─────────────┐
                                        │ 健康检查    │
                                        │ 验证部署    │
                                        │             │
                                        └─────────────┘

1. 代码提交规范

Conventional Commits

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

类型说明

类型说明版本 bump
feat新功能minor
fixBug 修复patch
docs文档更新-
style代码格式-
refactor重构-
perf性能优化patch
test测试-
chore构建/工具-
ciCI 配置-

示例

feat(consultation): add symptom extraction
fix(auth): fix token refresh race condition
docs(readme): update setup instructions
perf(rag): optimize vector search query

Commitlint 配置

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'ci'],
    ],
    'scope-enum': [
      2,
      'always',
      ['auth', 'consultation', 'rag', 'training', 'profile', 'deploy', 'ci'],
    ],
  },
};

2. GitHub Actions CI

完整 Workflow

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # Commitlint
  commitlint:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm install -g @commitlint/cli @commitlint/config-conventional
      - run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose

  # Frontend (web)
  web:
    runs-on: ubuntu-latest
    needs: commitlint
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Lint
        run: pnpm lint

      - name: Type check
        run: pnpm typecheck

      - name: Test
        run: pnpm test

      - name: Build
        run: pnpm build

  # Backend (api)
  api:
    runs-on: ubuntu-latest
    needs: commitlint
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: bodysense_test
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
          cache: true

      - name: Vet
        run: go vet ./...

      - name: Test
        run: go test ./... -v -race
        env:
          TEST_DATABASE_URL: postgres://test:test@localhost:5432/bodysense_test?sslmode=disable

      - name: Build
        run: go build -o server cmd/server/main.go

  # AI Service
  ai-service:
    runs-on: ubuntu-latest
    needs: commitlint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install uv
        uses: astral-sh/setup-uv@v3

      - name: Install dependencies
        run: uv sync

      - name: Lint
        run: uv run ruff check .

      - name: Test
        run: uv run pytest

  # Build Docker Images (only on main)
  build:
    runs-on: ubuntu-latest
    needs: [web, api, ai-service]
    if: github.ref == 'refs/heads/main'
    strategy:
      matrix:
        service: [web, api, ai-service]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: bodysense/${{ matrix.service }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: ./${{ matrix.service }}
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

3. release-please 配置

GitHub Action

# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [main]

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          release-type: node
          config-file: release-please-config.json
          manifest-file: .release-please-manifest.json

配置文件

// release-please-config.json
{
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
  "packages": {
    ".": {
      "release-type": "node",
      "bump-minor-pre-major": true,
      "bump-patch-for-minor-pre-major": true,
      "changelog-path": "CHANGELOG.md",
      "changelog-sections": [
        { "type": "feat", "section": "Features" },
        { "type": "fix", "section": "Bug Fixes" },
        { "type": "perf", "section": "Performance" },
        { "type": "docs", "section": "Documentation", "hidden": true },
        { "type": "chore", "section": "Miscellaneous", "hidden": true }
      ]
    }
  }
}
// .release-please-manifest.json
{
  ".": "0.1.0"
}

4. 生产部署

Docker 构建

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    tags:
      - 'v*'

jobs:
  build-prod:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service: [web, api, ai-service]
    steps:
      - uses: actions/checkout@v4

      - name: Extract version
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

      - name: Login to ACR
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.ACR_REGISTRY }}
          username: ${{ secrets.ACR_USERNAME }}
          password: ${{ secrets.ACR_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: ./${{ matrix.service }}
          push: true
          tags: |
            ${{ secrets.ACR_REGISTRY }}/bodysense-${{ matrix.service }}:${{ steps.version.outputs.VERSION }}
            ${{ secrets.ACR_REGISTRY }}/bodysense-${{ matrix.service }}:prod-latest

镜像标签策略

bodysense-api:v0.3.0          # 版本 tag(不可变,用于回滚)
bodysense-api:prod-latest     # 最新 tag(Watchtower 监控)
bodysense-api:abc123          # commit hash(调试用)

5. Watchtower 自动部署

配置

# docker-compose.prod.yml
version: '3.8'

services:
  api:
    image: bodysense-api:prod-latest
    labels:
      com.centurylinklabs.watchtower.enable: 'true'
    restart: unless-stopped

  ai-service:
    image: bodysense-ai-service:prod-latest
    labels:
      com.centurylinklabs.watchtower.enable: 'true'
    restart: unless-stopped

  web:
    image: bodysense-web:prod-latest
    labels:
      com.centurylinklabs.watchtower.enable: 'true'
    restart: unless-stopped

  watchtower:
    image: containrrr/watchtower
    environment:
      WATCHTOWER_CLEANUP: 'true'
      WATCHTOWER_POLL_INTERVAL: '300'
      WATCHTOWER_LABEL_ENABLE: 'true'
      WATCHTOWER_ROLLING_RESTART: 'true'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /root/.docker/config.json:/config.json:ro

部署流程

1. GitHub Release 创建
2. 构建生产镜像
3. 推送 prod-latest tag
4. Watchtower 检测到更新(5 分钟内)
5. 拉取新镜像
6. 滚动重启容器
7. 健康检查验证

6. 回滚策略

手动回滚

# 1. 查看历史版本
git tag -l 'v*'

# 2. 修改 docker-compose.prod.yml
image: bodysense-api:v0.2.0  # 改为上一个好的版本

# 3. 重启容器
docker-compose -f docker-compose.prod.yml up -d api

# 4. 验证
curl https://api.example.com/health

自动回滚(健康检查失败)

# docker-compose.prod.yml
services:
  api:
    image: bodysense-api:prod-latest
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:8080/health']
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
    restart: unless-stopped

如果健康检查失败 3 次,容器会自动重启。如果持续失败,需要手动介入。

7. 环境管理

环境变量

# .env.production
DATABASE_URL=postgres://user:pass@db:5432/bodysense
REDIS_URL=redis://redis:6379
JWT_SECRET=production-secret
LLM_API_KEY=sk-xxx
LLM_BASE_URL=https://api.openai.com/v1

Secrets 管理

# GitHub Secrets
DOCKERHUB_USERNAME
DOCKERHUB_TOKEN
ACR_REGISTRY
ACR_USERNAME
ACR_PASSWORD
PRODUCTION_SSH_KEY

8. 监控部署

部署通知

# .github/workflows/deploy.yml
- name: Notify deployment
  if: success()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "✅ Deployed ${{ matrix.service }} v${{ steps.version.outputs.VERSION }}"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

- name: Notify failure
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "❌ Failed to deploy ${{ matrix.service }}"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

部署验证

# scripts/verify-deploy.sh
#!/bin/bash

set -e

echo "Verifying deployment..."

# 等待服务启动
sleep 30

# 健康检查
response=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health)
if [ "$response" != "200" ]; then
  echo "❌ Health check failed: $response"
  exit 1
fi

echo "✅ Deployment verified"

9. Monorepo CI 优化

只运行受影响的服务

# .github/workflows/ci.yml
- name: Check affected
  id: affected
  run: |
    if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q "^apps/web/"; then
      echo "web=true" >> $GITHUB_OUTPUT
    fi
    if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q "^apps/api/"; then
      echo "api=true" >> $GITHUB_OUTPUT
    fi

- name: Build web
  if: steps.affected.outputs.web == 'true'
  run: pnpm build:web

- name: Build api
  if: steps.affected.outputs.api == 'true'
  run: go build -o server cmd/server/main.go

缓存优化

# pnpm 缓存
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'pnpm'

# Go 缓存
- uses: actions/setup-go@v5
  with:
    go-version: '1.22'
    cache: true

# Docker 缓存
- uses: docker/build-push-action@v5
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max

10. 安全扫描

依赖扫描

# .github/workflows/security.yml
name: Security

on:
  schedule:
    - cron: '0 0 * * 1'  # 每周一

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'table'
          exit-code: '1'
          severity: 'CRITICAL,HIGH'

镜像扫描

- name: Scan Docker image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'bodysense/api:${{ github.sha }}'
    format: 'sarif'
    output: 'trivy-results.sarif'

常见面试问题

Q: 从代码提交到上线的完整流程是什么?

A:

  1. 本地开发:Conventional Commits 规范
  2. PR 创建:触发 CI(lint、test、build)
  3. Code Review:团队审查代码
  4. 合并到 main:触发 release-please
  5. Release PR:自动版本 bump + CHANGELOG
  6. 合并 Release PR:创建 GitHub Release
  7. Tag Push:触发生产构建
  8. 推送镜像:推送到 ACR
  9. Watchtower:检测更新,滚动重启
  10. 健康检查:验证部署成功

Q: 怎么回滚部署?

A:

  1. 修改镜像 tag:改为上一个好的版本
  2. 重启容器:docker-compose up -d
  3. 验证:健康检查 + 监控

Q: 怎么保证部署安全?

A:

  1. 自动测试:CI 必须通过
  2. Code Review:至少一人审查
  3. 安全扫描:依赖漏洞扫描
  4. 健康检查:部署后自动验证
  5. 回滚策略:快速回滚能力

常见错误

CI 不测试

# ❌ 只构建不测试
jobs:
  build:
    steps:
      - run: pnpm build

# ✅ 测试 + 构建
jobs:
  test:
    steps:
      - run: pnpm test
  build:
    needs: test
    steps:
      - run: pnpm build

不缓存依赖

# ❌ 每次都重新安装依赖
- run: pnpm install

# ✅ 使用缓存
- uses: actions/setup-node@v4
  with:
    cache: 'pnpm'
- run: pnpm install --frozen-lockfile

部署不验证

# ❌ 部署后不验证
- run: docker-compose up -d

# ✅ 部署后验证
- run: docker-compose up -d
- run: sleep 30
- run: curl -f https://api.example.com/health
创建于 2026/6/25 更新于 2026/6/25