ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

前端转 AI 100 天 Day 28:CI/CD——GitHub Actions 自动化部署

前端转 AI 100 天 Day 28:CI/CD——GitHub Actions 自动化部署

前端转 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. 两个概念

缩写全称含义
CIContinuous Integration持续集成:每次提交自动跑测试
CDContinuous 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 CIGitLab 用户首选⭐⭐⭐⭐
Jenkins老牌,自建⭐⭐⭐
CircleCI独立服务,强大⭐⭐⭐
Travis CI老牌,逐渐淡出⭐⭐

我们用 GitHub Actions——开源项目免费,个人项目 2000 分钟/月免费。


二、GitHub Actions 核心概念

4 个关键词:

概念含义前端类比
Workflow一个完整的工作流(YAML 文件)npm run ci一条命令
Job工作流里的一个任务npm run test/npm run build
StepJob 里的一步一条 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.3

4. 常用 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:调用现成的 Action
  • run:执行 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:false

2. 关键点拆解

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"] # 行长度交给 formatter

mypy是 Python 的类型检查器——对应前端的tsc:

[tool.mypy] python_version = "3.11" strict = false ignore_missing_imports = true

4. 覆盖率:Codecov 集成

-uses:codecov/codecov-action@v4with:file:./coverage.xml

上传覆盖率到 Codecov,README 可以显示覆盖率徽章:

[![codecov](https://codecov.io/gh/yourname/repo/branch/main/graph/badge.svg)](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=max

3. 关键点拆解

1.permissions:权限声明

permissions:contents:readpackages:write

GitHub 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$USER

2. 创建docker-compose.prod.yml(Day 27 已写)。

3. 配置.env:

# /opt/ai-assistant/.envAPI_KEY=your-prod-keyPOSTGRES_PASSWORD=strong-password

4. 首次手动部署:

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:服务器 IP
  • SSH_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 + Deploy

2.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.ymlpush / PR跑测试、lint、上传覆盖率
docker.ymlpush main / tag构建镜像推 ghcr
deploy.ymltagv*/ 手动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 有点"原始":

环节VercelGitHub Actions
触发git pushgit 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。

核心思想一样:代码事件 → 自动动作。你前端的"自动部署"经验直接迁移。


九、今日踩坑

  1. secrets名字拼错→ 读出来是空字符串,不报错
  2. permissions没声明→ 推镜像时 403 权限不足
  3. actions/checkout没加→ 工作目录是空的
  4. cachekey 用了固定值→ 依赖更新了但缓存还在,装的是旧包
  5. hashFiles路径写错→ 缓存 key 永远是同一个,脏缓存
  6. SSH 部署命令没有set -e→ 中间步骤失败也返回成功
  7. 密钥硬编码在 YAML 里→ 泄露,所有密钥用secrets
  8. runs-on用了不存在的 runner→ 比如macos-14-xlarge(要付费)
  9. 矩阵测试里变量引用错→${{ matrix.python-version }}记成${{ python-version }}
  10. 失败了没通知→ 上线了才从用户那里知道
  11. 生产环境用main分支触发部署→ 随便一个 push 就上线,要打 tag 才部署
  12. 忘了清理旧镜像→ 服务器磁盘 3 个月就满了
  13. 免费额度用超→ 私有仓库 2000 分钟/月,跑矩阵测试很吃时间
  14. 部署脚本里cd写错路径→ 找不到项目目录
  15. if:条件写错→ 比如if: success()但想"失败也执行"要用always()

十、今日总结

今天核心收获三件事:

  1. CI/CD 是"代码事件驱动的自动化":push 触发测试,tag 触发部署
  2. GitHub Actions 三件套:Workflow(YAML) + Job(任务) + Step(步骤)
  3. 矩阵测试 + 缓存 + 健康检查是三大实用技巧:多版本兼容、加速构建、保证部署成功

一个感受:

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-06Python 语法、数据结构、函数、OOP、文件、依赖管理
网络07-08HTTP 请求、异步 asyncio
工程09-12正则、装饰器、Pydantic、日志调试
项目13-14命令行 AI 助手 v1 / v2
复盘15阶段总结
数据16-17SQLite 数据库、SQLAlchemy ORM
测试18-19pytest 入门 + 进阶
工具20CLI 打包与发布
后端21-28FastAPI + Docker + CI/CD ✅

进行中 🎯

阶段天数内容
后端29-30Nginx 反代 + 部署上线
大模型31-45LLM API + Prompt 工程
RAG46-60向量数据库 + 知识库
Agent61-80LangChain / 工具调用
实战81-100完整 Agent 项目上线

100 天后见,一起从切图仔变成 Agent 开发者。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进