前端转 AI 第 28 天:CI/CD - GitHub Actions 自动化部署
昨日回顾
Day 27 把项目 Docker 化了,docker compose up -d就能跑。
但还有个问题——每次改代码都要手动做这些事:
# 每次提交后手动跑pytest# 跑测试dockerbuild-tai-assistant.# 构建镜像dockerpush registry/ai-api# 推镜像sshserver# 登服务器dockercompose pull# 拉镜像dockercompose up-d# 重启服务一次部署 10 分钟,还容易忘步骤。
今天上CI/CD——把这一串全自动化。
Day 28 目标:写出生产级 GitHub Actions 工作流,从 push 到部署全自动。
一、CI / CD 是什么
前端同学对这个词可能有点陌生,但用过 Vercel / Netlify 的一定懂:
git push之后,Vercel 自动构建 + 部署,1 分钟后线上就是新版本。
这就是 CI/CD。
1. 两个概念
| 缩写 | 全称 | 含义 |
|---|---|---|
| CI | Continuous Integration | 持续集成:每次提交自动跑测试 |
| CD | Continuous Delivery / Deployment | 持续交付/部署:测试通过后自动部署 |
实际流程:
开发者 push 代码 ↓ CI:自动跑测试 + lint ↓(通过) CD:自动构建镜像 + 部署 ↓ 线上更新2. 为什么必须上 CI/CD
手动部署的问题:
| 问题 | 后果 |
|---|---|
| 忘了跑测试 | 上线才发现有 bug |
| 忘了步骤 | 部署失败,服务中断 |
| 不同人操作不同 | 环境不一致 |
| 无法回滚 | 出问题只能手动恢复 |
CI/CD 解决:
- 标准化:流程写在代码里,谁跑都一样
- 快速反馈:push 后几分钟知道有没有问题
- 可追溯:每次部署有记录
- 可回滚:出问题回退到上一个版本
前端类比:Vercel / Netlify 的"自动部署"就是 CI/CD。GitHub Actions 是"更通用的版本"——不只能部署前端,也能部署后端、跑测试、发通知、做任何事。
3. 主流 CI/CD 工具
| 工具 | 特点 | 推荐度 |
|---|---|---|
| GitHub Actions | 和 GitHub 深度集成,免费额度多 | ⭐⭐⭐⭐⭐ |
| GitLab CI | GitLab 用户首选 | ⭐⭐⭐⭐ |
| Jenkins | 老牌,自建 | ⭐⭐⭐ |
| CircleCI | 独立服务,强大 | ⭐⭐⭐ |
| Travis CI | 老牌,逐渐淡出 | ⭐⭐ |
我们用 GitHub Actions——开源项目免费,个人项目 2000 分钟/月免费。
二、GitHub Actions 核心概念
4 个关键词:
| 概念 | 含义 | 前端类比 |
|---|---|---|
| Workflow | 一个完整的工作流(YAML 文件) | npm run ci一条命令 |
| Job | 工作流里的一个任务 | npm run test/npm run build |
| Step | Job 里的一步 | 一条 shell 命令 |
| Action | 可复用的步骤(社区提供) | npm 包 |
关系:
Workflow (.github/workflows/ci.yml) ├── Job 1: test │ ├── Step 1: checkout │ ├── Step 2: setup-python │ └── Step 3: pytest └── Job 2: build (依赖 Job 1) ├── Step 1: checkout ├── Step 2: docker build └── Step 3: docker push所有 Workflow 放在.github/workflows/目录下。
三、第一个 GitHub Actions
1. 最简单的 Workflow
创建.github/workflows/hello.yml:
name:Helloon:push:branches:[main]jobs:hello:runs-on:ubuntu-lateststeps:-name:打招呼run:echo "Hello,GitHub Actions!"提交后 push,GitHub 就会自动跑。
打开 GitHub 仓库 → Actions 标签,能看到运行记录。
2. 逐行拆解
name:Hello# Workflow 名字(显示在 UI)on:# 触发条件push:branches:[main]# push 到 main 时触发jobs:# 所有 Jobhello:# Job ID(唯一标识)runs-on:ubuntu-latest# 运行环境(Ubuntu 最新版)steps:# 步骤列表-name:打招呼# 步骤名run:echo "Hello"# 执行命令3. 常用触发条件
on:# push 时触发push:branches:[main,develop]paths:# 只在某些文件变化时触发-"src/**"-"pyproject.toml"# PR 时触发pull_request:branches:[main]# 手动触发(Actions 页面点按钮)workflow_dispatch:# 定时触发(cron 语法,UTC 时间)schedule:-cron:"0 2 * * *"# 每天凌晨 2 点# 打 tag 时触发push:tags:-"v*"# v1.0.0 / v1.2.34. 常用 Action
GitHub 官方和社区提供了大量预构建的 Action,直接用:
| Action | 作用 |
|---|---|
actions/checkout@v4 | 拉取代码 |
actions/setup-python@v5 | 安装 Python |
actions/setup-node@v4 | 安装 Node |
actions/cache@v4 | 缓存依赖 |
actions/upload-artifact@v4 | 上传构建产物 |
docker/build-push-action@v5 | 构建并推送 Docker 镜像 |
用法:
steps:-uses:actions/checkout@v4# 用别人做好的 Action-uses:actions/setup-python@v5with:# 传参数python-version:"3.11"usesvsrun:
uses:调用现成的 Actionrun:执行 shell 命令
四、给项目加 CI
核心:每次 push 自动跑测试。
1. 创建.github/workflows/ci.yml
name:CIon:push:branches:[main,develop]pull_request:branches:[main]jobs:test:name:测试runs-on:ubuntu-lateststrategy:matrix:python-version:["3.11","3.12"]steps:-name:拉取代码uses:actions/checkout@v4-name:安装 Python ${{matrix.python-version}}uses:actions/setup-python@v5with:python-version:${{matrix.python-version}}-name:缓存 pip 依赖uses:actions/cache@v4with:path:~/.cache/pipkey:${{runner.os}}-pip-${{hashFiles('pyproject.toml')}}restore-keys:|${{ runner.os }}-pip--name:安装依赖run:|python -m pip install --upgrade pip pip install -e ".[dev]"-name:代码风格检查run:ruff check src/ tests/-name:类型检查run:mypy src/||true# 先允许失败-name:运行测试run:pytest--cov=src/ai_assistant--cov-report=xml-name:上传覆盖率uses:codecov/codecov-action@v4if:matrix.python-version == '3.11'with:file:./coverage.xmlfail_ci_if_error:false2. 关键点拆解
1.strategy.matrix:多版本测试
strategy:matrix:python-version:["3.11","3.12"]同时测两个 Python 版本,保证兼容性。
执行时:
- Job 1: Python 3.11
- Job 2: Python 3.12
- 两个并行跑,都通过才算成功
前端类比:类似 Jest 测 Node 16 / 18 / 20 多版本。
2. 缓存:加速 10 倍
-uses:actions/cache@v4with:path:~/.cache/pipkey:${{runner.os}}-pip-${{hashFiles('pyproject.toml')}}第一次跑:装依赖 30 秒
第二次跑(缓存命中):3 秒
hashFiles('pyproject.toml')是关键——依赖文件变了缓存就失效,自动重新装。
这就是"精准缓存"——既不浪费也不出错。
3.ruff/mypy:代码质量
-name:代码风格检查run:ruff check src/ tests/ruff是 Python 的 ESLint——检查代码风格、潜在 bug。速度极快(Rust 写的)。
加上pyproject.toml配置:
[tool.ruff] line-length = 100 target-version = "py311" [tool.ruff.lint] select = ["E", "F", "I", "N", "W", "B"] ignore = ["E501"] # 行长度交给 formattermypy是 Python 的类型检查器——对应前端的tsc:
[tool.mypy] python_version = "3.11" strict = false ignore_missing_imports = true4. 覆盖率:Codecov 集成
-uses:codecov/codecov-action@v4with:file:./coverage.xml上传覆盖率到 Codecov,README 可以显示覆盖率徽章:
[](https://codecov.io/gh/yourname/repo)面试官看到项目有覆盖率徽章,会加分不少。
3. 运行效果
push 代码后,GitHub Actions 页面显示:
✓ test (3.11) 1m 23s ✓ test (3.12) 1m 25s失败时:
✗ test (3.11) 1m 15s └─ 运行测试 ❌ FAILED tests/api/test_chat.py::TestChat::test_chat_creates_new_session点进去能看到完整日志。PR 页面也会显示检查通过与否。
五、CD:自动构建和推送 Docker 镜像
CI 通过后,构建 Docker 镜像推送到镜像仓库。
1. 用什么镜像仓库
| 仓库 | 优点 | 免费额度 |
|---|---|---|
| GitHub Container Registry (ghcr.io) | 和 GitHub 集成,权限好管 | 无限公开 / 500MB 私有 |
| Docker Hub | 老牌,知名度高 | 1 个私有仓库 |
| 阿里云 ACR | 国内快 | 有限免费 |
推荐 GitHub Container Registry——和仓库绑定,不用额外配置。
2. 创建.github/workflows/docker.yml
name:Build and Push Dockeron:push:branches:[main]tags:["v*"]env:REGISTRY:ghcr.ioIMAGE_NAME:${{github.repository}}jobs:build:runs-on:ubuntu-latestpermissions:contents:readpackages:write# 推送镜像需要steps:-name:拉取代码uses:actions/checkout@v4-name:设置 Docker Buildxuses:docker/setup-buildx-action@v3-name:登录到镜像仓库uses:docker/login-action@v3with:registry:${{env.REGISTRY}}username:${{github.actor}}password:${{secrets.GITHUB_TOKEN}}# 自动提供-name:生成镜像标签和元数据id:metauses:docker/metadata-action@v5with:images:${{env.REGISTRY}}/${{env.IMAGE_NAME}}tags:|type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix=sha--name:构建并推送镜像uses:docker/build-push-action@v5with:context:.push:truetags:${{steps.meta.outputs.tags}}labels:${{steps.meta.outputs.labels}}cache-from:type=ghacache-to:type=gha,mode=max3. 关键点拆解
1.permissions:权限声明
permissions:contents:readpackages:writeGitHub Actions 默认权限很小,要写镜像必须显式声明packages: write。
2.secrets.GITHUB_TOKEN:自动 Token
GITHUB_TOKEN是 GitHub 自动提供的,不用手动配置。权限和permissions挂钩。
其他密钥要用secrets.XXX:
password:${{secrets.DOCKER_HUB_PASSWORD}}在仓库 Settings → Secrets and variables → Actions 里配置。
3.docker/metadata-action:标签自动生成
tags:|type=ref,event=branch # main → ghcr.io/user/repo:main type=semver,pattern={{version}} # v1.2.3 → ghcr.io/user/repo:1.2.3 type=semver,pattern={{major}}.{{minor}} # v1.2.3 → ghcr.io/user/repo:1.2 type=sha,prefix=sha- # sha-abc1234一个 commit 生成多个标签——灵活切换和回滚。
4.cache-from/to: type=gha:构建缓存
用 GitHub Actions 缓存加速 Docker 构建——第二次构建从 60 秒降到 10 秒。
4. 运行效果
push 后,镜像自动推送到ghcr.io/yourname/ai-assistant。
在 GitHub 仓库的 Packages 页面能看到:
ai-assistant ├── main ├── 1.2.3 ├── 1.2 └── sha-a3f9b2e任何服务器上:
dockerpull ghcr.io/yourname/ai-assistant:latestdockerrun-d-p8000:8000 ghcr.io/yourname/ai-assistant:latest部署变得无比简单。
六、自动部署到服务器
镜像构建好了,接下来让服务器自动拉取更新。
1. 部署方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| SSH 直连部署 | 简单 | 服务器暴露 |
| Webhook + 服务器拉取 | 无侵入 | 要配 Webhook |
| K8s + ArgoCD | 生产级 | 复杂 |
| Docker Swarm | 轻量集群 | 生态小 |
我们选 SSH 直连——最简单,适合小项目。
2. 服务器准备
1. 服务器上装 Docker:
curl-fsSLhttps://get.docker.com|shsudousermod-aGdocker$USER2. 创建docker-compose.prod.yml(Day 27 已写)。
3. 配置.env:
# /opt/ai-assistant/.envAPI_KEY=your-prod-keyPOSTGRES_PASSWORD=strong-password4. 首次手动部署:
cd/opt/ai-assistantdockercompose-fdocker-compose.prod.yml up-d确认服务能起,再上自动部署。
3. 生成 SSH 密钥
本地生成:
ssh-keygen-ted25519-C"github-actions"-f~/.ssh/github_actions把公钥放到服务器:
cat~/.ssh/github_actions.pub>>~/.ssh/authorized_keys在 GitHub Secrets 里配置:
SSH_HOST:服务器 IPSSH_USER:SSH 用户名SSH_KEY:私钥内容(cat ~/.ssh/github_actions)SSH_PORT:SSH 端口(默认 22)
4. 创建.github/workflows/deploy.yml
name:Deployon:push:tags:["v*"]# 只在大版本 tag 时部署workflow_dispatch:# 也支持手动触发inputs:environment:description:"部署环境"required:truedefault:"production"type:choiceoptions:-production-stagingjobs:deploy:name:部署到服务器runs-on:ubuntu-latestenvironment:productionsteps:-name:部署uses:appleboy/ssh-action@v1with:host:${{secrets.SSH_HOST}}username:${{secrets.SSH_USER}}key:${{secrets.SSH_KEY}}port:${{secrets.SSH_PORT||22}}script:|set -e cd /opt/ai-assistantecho "=== 拉取最新代码 ===" git pull origin main echo "=== 拉取最新镜像 ===" docker compose-f docker-compose.prod.yml pull echo "=== 重启服务 ===" docker compose-f docker-compose.prod.yml up-d echo "=== 清理旧镜像 ===" docker image prune-f echo "=== 检查健康状态 ===" sleep 5 curl-f http://localhost:8000/health/ready||exit 1 echo "=== 部署完成 ==="5. 关键点拆解
1.tags: ["v*"]:只在大版本部署
日常 push 只触发 CI,打 tag 才部署到生产——减少风险。
流程:
# 日常开发gitpush# 只跑 CI# 要发版gittag v1.0.0gitpush--tags# 触发 CI + Docker + Deploy2.workflow_dispatch:手动触发
有时候需要手动部署(比如修复小问题):
- 打开 GitHub → Actions → Deploy → Run workflow
- 选环境 → Run
3.appleboy/ssh-action
社区最常用的 SSH Action——传命令到服务器执行。
set -e让脚本遇错就停,避免"部分失败但显示成功"。
4. 部署脚本的步骤
cd/opt/ai-assistantgitpull# 更新 compose 文件dockercompose pull# 拉新镜像dockercompose up-d# 重启dockerimage prune-f# 清理旧镜像(省磁盘)curl-fhealth/ready||exit1# 健康检查,失败则退出最后一步最关键——健康检查。如果新版本起不来,GitHub Actions 显示失败,你能立刻知道。
6. 失败自动回滚(进阶)
更稳的方案:部署失败自动回滚到上一个版本。
-name:部署(带回滚)uses:appleboy/ssh-action@v1with:script:|set -e cd /opt/ai-assistant# 记录当前版本CURRENT=$(docker compose-f docker-compose.prod.yml ps--format json|jq-r '.[0].Image')# 部署新版本docker compose-f docker-compose.prod.yml pull docker compose-f docker-compose.prod.yml up-d# 健康检查sleep 10 if!curl-f http://localhost:8000/health/ready; then echo "❌ 新版本不健康,回滚到 $CURRENT" docker compose-f docker-compose.prod.yml down docker tag $CURRENT ai-assistant:rollback# ... 回滚逻辑exit 1 fi echo "✓ 部署成功"真实生产环境用 K8s + Argo Rollouts 做金丝雀发布,但今天先理解基础思路。
七、完整 CI/CD 流程
1. 完整项目结构
.github/ └── workflows/ ├── ci.yml # 测试 + lint ├── docker.yml # 构建镜像 └── deploy.yml # 部署到服务器2. 各 Workflow 触发时机
| Workflow | 触发 | 做什么 |
|---|---|---|
ci.yml | push / PR | 跑测试、lint、上传覆盖率 |
docker.yml | push main / tag | 构建镜像推 ghcr |
deploy.yml | tagv*/ 手动 | SSH 部署到服务器 |
完整流程:
开发者 push 代码 ↓ [CI] 测试通过? ├── ❌ → 通知,停止 └── ✓ → 继续 ↓ [Docker] 构建镜像推 ghcr ↓ (打 tag 时) [Docker] 重新构建带版本号的镜像 ↓ [Deploy] SSH 到服务器拉镜像重启 ↓ 健康检查通过 ↓ ✓ 上线完成3. 加一个通知
失败时通过 Slack / 钉钉 / 飞书通知:
-name:失败通知if:failure()uses:8398a7/action-slack@v3with:status:${{job.status}}webhook_url:${{secrets.SLACK_WEBHOOK}}或者用更通用的 curl:
-name:发送钉钉通知if:always()run:|STATUS="${{ job.status }}" if [ "$STATUS" = "success" ]; then ICON="✅" else ICON="❌" ficurl-X POST "${{secrets.DINGTALK_WEBHOOK}}" \-H "Content-Type:application/json" \-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$ICON 部署 $STATUS:${{github.repository}}(${{github.ref_name}})\"}}"if: always()让通知不管成功失败都发。
if: failure()只在失败时发。
八、前端视角:GitHub Actions vs Vercel
用过 Vercel 的你会觉得 GitHub Actions 有点"原始":
| 环节 | Vercel | GitHub Actions |
|---|---|---|
| 触发 | git push | git push / PR / tag / 手动 / 定时 |
| 配置 | 自动检测框架 | 手写 YAML |
| 构建 | npm run build | 自己写步骤 |
| 部署 | 自动 | 自己写 SSH / API |
| 预览 | 每个 PR 有独立预览 | 需要额外配置 |
| 环境变量 | UI 配置 | secrets |
| 日志 | 网页查看 | 网页查看 |
Vercel 是"打包好的 CI/CD",适合前端和 Next.js。
GitHub Actions 是"通用工具",什么都能做:
- 部署后端服务
- 构建 Docker 镜像
- 发布 PyPI 包(Day 20)
- 定时任务
- 自动回复 Issue
- 自动打标签
- …
Vercel 的"预览部署",GitHub Actions 也能实现——每个 PR 部署到临时 URL。
核心思想一样:代码事件 → 自动动作。你前端的"自动部署"经验直接迁移。
九、今日踩坑
secrets名字拼错→ 读出来是空字符串,不报错permissions没声明→ 推镜像时 403 权限不足actions/checkout没加→ 工作目录是空的cachekey 用了固定值→ 依赖更新了但缓存还在,装的是旧包hashFiles路径写错→ 缓存 key 永远是同一个,脏缓存- SSH 部署命令没有
set -e→ 中间步骤失败也返回成功 - 密钥硬编码在 YAML 里→ 泄露,所有密钥用
secrets runs-on用了不存在的 runner→ 比如macos-14-xlarge(要付费)- 矩阵测试里变量引用错→
${{ matrix.python-version }}记成${{ python-version }} - 失败了没通知→ 上线了才从用户那里知道
- 生产环境用
main分支触发部署→ 随便一个 push 就上线,要打 tag 才部署 - 忘了清理旧镜像→ 服务器磁盘 3 个月就满了
- 免费额度用超→ 私有仓库 2000 分钟/月,跑矩阵测试很吃时间
- 部署脚本里
cd写错路径→ 找不到项目目录 if:条件写错→ 比如if: success()但想"失败也执行"要用always()
十、今日总结
今天核心收获三件事:
- CI/CD 是"代码事件驱动的自动化":push 触发测试,tag 触发部署
- GitHub Actions 三件套:Workflow(YAML) + Job(任务) + Step(步骤)
- 矩阵测试 + 缓存 + 健康检查是三大实用技巧:多版本兼容、加速构建、保证部署成功
一个感受:
CI/CD 的核心价值不是"省事",是"可靠"。
- 手动部署 100 次,99 次成功,1 次忘了步骤就出事故
- 自动部署 1000 次,每次都按脚本执行,可复现、可追溯、可回滚
Vercel 让前端习惯了"push 即部署",GitHub Actions 让你对后端也有同样的体验。
你的项目现在实现了"全链路自动化":
- ✅ 代码 push → 自动测试
- ✅ 测试通过 → 自动构建镜像
- ✅ 打 tag → 自动部署到服务器
- ✅ 部署失败 → 自动通知
从"手动运维"到"DevOps 工程师"。
Day 28 完成度:✅
明日预告(Day 29)
- Nginx 反代 + HTTPS:
- Nginx 反向代理配置
- Let’s Encrypt 免费 SSL 证书
- HTTP → HTTPS 自动跳转
- 域名绑定 + DNS 配置
- Gzip 压缩 / 限流配置
最后
如果这篇对你有用,点个赞让我知道有人在看。
评论区互动:你之前用过 CI/CD 吗?GitHub Actions / GitLab CI / Jenkins?踩过什么坑?欢迎分享。
有问题评论区见,我们第 29 天继续。
100 天,第 29 天见。
附录:系列目录
已完成 ✅
| 阶段 | 天数 | 内容 |
|---|---|---|
| 基础 | 01-06 | Python 语法、数据结构、函数、OOP、文件、依赖管理 |
| 网络 | 07-08 | HTTP 请求、异步 asyncio |
| 工程 | 09-12 | 正则、装饰器、Pydantic、日志调试 |
| 项目 | 13-14 | 命令行 AI 助手 v1 / v2 |
| 复盘 | 15 | 阶段总结 |
| 数据 | 16-17 | SQLite 数据库、SQLAlchemy ORM |
| 测试 | 18-19 | pytest 入门 + 进阶 |
| 工具 | 20 | CLI 打包与发布 |
| 后端 | 21-28 | FastAPI + Docker + CI/CD ✅ |
进行中 🎯
| 阶段 | 天数 | 内容 |
|---|---|---|
| 后端 | 29-30 | Nginx 反代 + 部署上线 |
| 大模型 | 31-45 | LLM API + Prompt 工程 |
| RAG | 46-60 | 向量数据库 + 知识库 |
| Agent | 61-80 | LangChain / 工具调用 |
| 实战 | 81-100 | 完整 Agent 项目上线 |
100 天后见,一起从切图仔变成 Agent 开发者。