CI/CD 持续集成与部署实战:基于 GitHub Actions 的自动化构建与发布流水线
为什么需要 CI/CD
在团队规模扩大、发布频率提高之后,手工构建、手工打包、手工登录服务器部署的方式很快就会遇到瓶颈:一次发布要敲十几条命令,稍不留神就漏了某一步;分支合并后忘了跑测试,带病代码直接上了生产;回滚只能靠人肉操作,慢且容易出错。
CI/CD(持续集成 / 持续交付 / 持续部署)把"代码提交 → 编译测试 → 打包 → 部署"这条链路全部用流水线串起来,让机器在每次提交时自动完成重复劳动。它带来的核心价值是:
- 更快的反馈:提交几分钟后就知道测试是否通过,问题在最早阶段暴露。
- 更低的发布风险:流程标准化,减少"我本地是好的"这类环境差异。
- 更频繁、更安全的发布:自动化让发布成本趋近于零,从而可以小步快跑。
本文以 GitHub Actions 为例,带你从零搭出一条可用的自动化构建与部署流水线。
CI/CD 三个概念的区别
很多人把 CI 和 CD 混为一谈,其实它们关注点不同:
- 持续集成(CI):开发者频繁地把代码合并到主干,每次合并都自动触发构建与测试,尽早发现集成问题。重点在"合代码不出事"。
- 持续交付(CD, Delivery):在 CI 的基础上,保证代码随时可以部署到生产——但通常部署动作由人点一下确认。重点在"随时可发"。
- 持续部署(CD, Deployment):进一步把"点一下"也省掉,测试通过后自动上线生产。重点在"自动就发"。
对大多数团队而言,先做到 CI + 持续交付已经收益巨大;持续部署则需要根据业务对风险的容忍度谨慎开启。
GitHub Actions 的核心模型
理解下面 5 个概念,就能读懂几乎所有 GitHub Actions 配置:
| 概念 | 含义 |
|---|---|
| **Workflow(工作流)** | 一个自动化流程,对应仓库 `.github/workflows/` 下的一个 YAML 文件 |
| **Event(事件)** | 触发工作流的动作,如 `push`、`pull_request`、`schedule` |
| **Job(作业)** | 一组按顺序执行的步骤,默认多个 Job 并行运行 |
| **Step(步骤)** | Job 中的一个具体操作,可以是一条命令或一个 Action |
| **Runner(执行器)** | 真正跑任务的机器,GitHub 提供托管 Runner,也可用自托管 Runner |
一个最小工作流文件如下:
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Hello, CI/CD!"
actions/checkout@v4 是官方 Action,作用是把仓库代码拉到 Runner 上;@v4 是版本标签,生产环境务必锁定大版本以避免意外变更。
实战一:提交即跑测试
以一个简单的 Node.js 项目为例,每次 push 时自动安装依赖并运行测试:
name: Node CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test
npm ci 会严格按照 package-lock.json 安装,比 npm install 更快且可复现,是 CI 场景的推荐写法。cache: npm 让依赖缓存生效,第二次构建能省下几十秒。
实战二:构建并推送 Docker 镜像
结合上一篇文章讲过的 Docker 镜像构建,这里把"构建镜像 → 推送到镜像仓库"自动化。以推送至 Docker Hub 为例:
name: Build Image
on:
push:
tags: ['v*']
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USER }}/myapp:${{ github.ref_name }}
注意所有敏感信息(账号、Token)都通过 secrets 注入,绝不写死在代码里。${{ github.ref_name }} 在打 v1.0.0 标签触发时,会自动取标签名作为镜像版本,做到"打标签即发布新版本"。
实战三:自动化部署到服务器
测试通过、镜像就绪后,最后一步是把它部署到服务器。最常见、最稳妥的方式是通过 SSH 登录并执行远程命令:
name: Deploy
on:
push:
tags: ['v*']
jobs:
deploy:
needs: docker # 等待镜像构建 Job 完成
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /opt/myapp
docker compose pull
docker compose up -d
needs: docker 表达了 Job 之间的依赖关系,保证"先构建、再部署"。远程脚本里用 docker compose pull 拉取最新镜像后 up -d 平滑重启,配合前文讲过的 Nginx 反向代理,可以实现几乎无感知的滚动更新。
用矩阵构建一次覆盖多环境
当项目需要验证多个语言版本或操作系统时,用 matrix 策略可以一次并行跑出所有组合,而不用复制粘贴多份配置:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test
Secrets 安全管理三原则
- 永远不要明文写密钥:所有凭据通过仓库 Settings → Secrets 配置,在 YAML 中只引用
${{ secrets.XXX }}。 - 最小权限:部署用的 SSH 密钥应限制为只能执行特定命令;镜像仓库 Token 用只读或仅写权限的细粒度令牌。
- 定期轮换:长期不变的密钥一旦泄露危害巨大,建议设置轮换周期。
常见问题与排错
- Runner 拉不到私有依赖:在私有 npm / Go module 场景下,需要在步骤中配置对应凭证或
.npmrc,不要为了简化把私有源公开。 - 缓存失效导致构建变慢:
key设计要合理,通常把依赖锁文件哈希作为缓存 key,锁文件变了缓存自然失效。 - Windows Runner 路径问题:路径分隔符和权限与 Linux 不同,跨平台矩阵构建时脚本要兼容。
- 超时:默认 Job 超时 6 小时、单步 360 分钟,长任务可在
timeout-minutes中显式调整。
小结
一条成熟的 CI/CD 流水线,本质上是把团队的最佳实践"写进代码、交给机器"。本文我们从概念区分讲起,依次实现了"提交即测试 → 打标签即构建镜像 → 推镜像即部署上线"三段式流水线,并补充了矩阵构建与 Secrets 安全要点。
把这几条串起来,你就拥有了一条从 git push 到生产环境更新的自动化高速公路。当你再也不想手动敲部署命令时,就会感谢今天搭好的这条流水线。
本文为技术教程系列之一。与之关联的可延伸主题包括:Docker 镜像多阶段构建优化、Linux 系统性能监控、Nginx 反向代理与 HTTPS 配置——它们共同构成一套现代应用的部署与运维基础。

发表评论 取消回复