BodySense CI/CD 完整流程
BodySense 项目的完整 CI/CD 流程:代码提交、自动测试、构建镜像、自动部署、回滚策略。
#type / concept
#status / growing
#tech / ops
#resource / github-actions
[!info] related notes
- 知识地图: BodySense 项目 MOC
- CI: GitHub Actions CI
- 发布: release-please
- 部署: Watchtower 自动部署
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 |
| fix | Bug 修复 | patch |
| docs | 文档更新 | - |
| style | 代码格式 | - |
| refactor | 重构 | - |
| perf | 性能优化 | patch |
| test | 测试 | - |
| chore | 构建/工具 | - |
| ci | CI 配置 | - |
示例
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:
- 本地开发:Conventional Commits 规范
- PR 创建:触发 CI(lint、test、build)
- Code Review:团队审查代码
- 合并到 main:触发 release-please
- Release PR:自动版本 bump + CHANGELOG
- 合并 Release PR:创建 GitHub Release
- Tag Push:触发生产构建
- 推送镜像:推送到 ACR
- Watchtower:检测更新,滚动重启
- 健康检查:验证部署成功
Q: 怎么回滚部署?
A:
- 修改镜像 tag:改为上一个好的版本
- 重启容器:docker-compose up -d
- 验证:健康检查 + 监控
Q: 怎么保证部署安全?
A:
- 自动测试:CI 必须通过
- Code Review:至少一人审查
- 安全扫描:依赖漏洞扫描
- 健康检查:部署后自动验证
- 回滚策略:快速回滚能力
常见错误
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