预计阅读时间:14 分钟 | 难度:进阶 ⭐⭐⭐
前两课我们实践了两种部署方式,但都有一个共同的问题:每次更新都要手动操作。
手工部署的工作流:
git push → SSH 连服务器 → git pull → npm install → npm run build → pm2 restart → 完成
这里面每一步都可能出错(忘拉代码、用了旧依赖、端口冲突……)。而且当你需要频繁更新(一天多次)时,手工操作的时间成本不可忽视。
CI/CD 的目标:把上述流程自动化。你只做 git push,剩下的交给流水线。
| 概念 | 做什么 | 交付物 |
|---|---|---|
| CI(持续集成) | 频繁合并代码到主分支 → 自动运行测试和构建 | 一个通过测试的构建产物 |
| CD(持续部署) | 将通过 CI 的产物自动部署到生产环境 | 线上可访问的服务 |
| CD(持续交付) | 产物通过 CI 后自动准备好,但需要手动确认才部署到生产 | 一个"一键部署"的待命版本 |
简单来说:CI = 自动检查代码质量,CD = 自动把代码送上线。
GitHub Actions 是目前最流行的 CI/CD 平台之一(与 GitHub 仓库原生集成,无需额外配置)。
| 术语 | 说明 | 类比 |
|---|---|---|
| Workflow | 一个完整的自动化流程,定义在 .github/workflows/*.yml | 工厂的生产线 |
| Job | Workflow 中的一个任务单元。多个 Job 可以并行或串行执行 | 生产线上的一个工位 |
| Step | Job 中的单个操作(运行命令或调用 Action) | 工位上的一个操作 |
| Action | 可复用的 GitHub Actions 模块(社区或官方提供) | 预制工具(扳手、螺丝刀) |
| Runner | 运行 Workflow 的服务器(GitHub 托管或自托管) | 工厂车间 |
在仓库中创建 .github/workflows/deploy.yml:
# .github/workflows/deploy.yml
name: Deploy to Production
# 触发条件:push 到 master/main 分支
on:
push:
branches: [main]
# 也可以手动触发
workflow_dispatch:
jobs:
# Job 1:CI — 构建和测试
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
# 保存构建产物,供后续 Job 使用
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
# Job 2:CD — 部署到服务器(等 CI 完成后再执行)
deploy:
needs: ci # 等 ci job 成功后执行
runs-on: ubuntu-latest
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy via SSH
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
source: "dist/"
target: "/var/www/myapp/"
- name: Restart application
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /var/www/myapp
docker compose pull
docker compose up -d --build
上面的 workflow 中使用了 ${{ secrets.XXX }},这些值绝对不能写在代码里。
在 GitHub 仓库页面设置:
# Settings → Secrets and variables → Actions → New repository secret
DEPLOY_HOST → 你的服务器 IP(如 123.123.123.123)
DEPLOY_USER → 登录用户名(如 ubuntu)
DEPLOY_KEY → SSH 私钥的全部内容(cat ~/.ssh/id_ed25519)
# 如果还需要其他敏感信息也放这里
DB_PASSWORD → 数据库密码
将 Docker 与 CI/CD 结合是最常见的生产模式:
# .github/workflows/docker-deploy.yml 片段
jobs:
build-and-push:
steps:
# ... checkout、test ...
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/myapp:latest
deploy:
needs: build-and-push
steps:
- name: SSH and deploy
uses: appleboy/ssh-action@v1.0.3
with:
script: |
docker pull myuser/myapp:latest
docker compose up -d --build
Docker 版本对比直接传文件的优势:服务器上不需要安装 Node.js 等运行时——Docker 镜像已经包含了所有依赖。服务器只需要 Docker 引擎。
核心思路:准备两套完全一样的环境(蓝/绿),只有一套对外服务:
Nginx 实现蓝绿切换的配置示意:
# 蓝绿切换:编辑 Nginx upstream,重载即可
upstream myapp {
# server 127.0.0.1:3001; # 蓝色(旧版,暂不启用)
server 127.0.0.1:3002; # 绿色(新版,当前活跃)
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://myapp;
}
}
自动化部署必须配套自动回滚能力:
| 部署方式 | 回滚方法 |
|---|---|
| 传统部署(文件) | 保留前 N 个版本的备份目录,重新软链接即可回滚 |
| Docker 部署 | 使用版本标签(如 myapp:v1.2.3),回滚时改 tag 重新部署 |
| 蓝绿部署 | 流量切回旧环境,一键完成 |
如果你不想自己维护 CI/CD 基础设施,也可以选择 PaaS(平台即服务)——代码推上去,平台帮你运行:
| 平台 | 特点 | 适合场景 |
|---|---|---|
| Vercel | 前端/全栈(Next.js 亲爹),自动 HTTPS 和 CDN | 前端项目、Next.js 应用 |
| Railway | 极简部署体验,支持多种语言和数据库 | 全栈项目快速部署 |
| Fly.io | 边缘部署,全球多节点 | 需要低延迟的全球应用 |
| Heroku | PaaS 老牌标杆,生态完善 | 传统 Web 应用(但免费方案已取消) |
Q1: GitHub Actions 中,如何确保 deploy job 在 ci job 成功后才执行?
Q2: 如何在 GitHub Actions 中安全使用服务器密码和密钥?
Q3: 蓝绿部署相比直接部署的最大优势是什么?
部署上线只是开始。这一课覆盖生产环境必须做的安全措施——防火墙深度配置、备份策略、监控告警、日志管理,以及灾难恢复计划。