*图片示意:一张CI/CD流水线示意图,从代码提交到构建、测试、部署的自动化流程*
为什么你需要CI/CD?——”在我机器上能跑”的终结者
1
0px rgba(0,0,0,.08);”
loading=”lazy” width=”800″ height=”500″>
我刚工作时,遇到过最崩溃的场景:周五下午5点,急着上线一个小功能。本地测试通过,push到服务器,重启服务,然后——页面500了。排查2小时,发现是线上环境少装了一个包。那一刻,我真想把”在我机器上能跑”这句话印在T恤上。
CI/CD(持续集成/持续交付)就是来终结这种故事的。它的核心就一句话:每次代码变更,自动帮你跑测试、构建、部署,确保代码在任何环境都能跑起来。
我用的经验是:新手别一开始就追求全量CI/CD,先解决”手动部署”这个最大痛点,再逐步加入测试、代码检查等环节。
第一步:选工具——别纠结,GitHub Actions就够了
市面上的CI/CD工具多如牛毛:Jenkins、GitLab CI、CircleCI、Travis CI…踩过坑后,我建议新手无脑选GitHub Actions。原因有三:
如果你用GitLab,GitLab CI也行,但配置逻辑类似。我以GitHub Actions为例,你掌握后迁移成本极低。
第二步:写一个最简单的CI流水线——从0到1只需10分钟
假设你有一个Node.js + Express项目,放在GitHub上。我们先实现:每次push代码,自动跑单元测试。
2.1 准备工作
确保你的项目根目录有package.json和测试脚本。我用的是Jest,你也可以用Mocha、Vitest等。
“json`
// package.json 部分内容
{
"scripts": {
"test": "jest"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
2.2 创建第一个Workflow文件
在项目根目录创建.github/workflows/ci.yml:
`yaml
name: CI Pipeline
触发条件:push到main或master分支
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
要执行的任务
jobs:
test:
runs-on: ubuntu-latest # 运行环境:最新的Ubuntu
steps:
- uses: actions/checkout@v3 # 获取代码
- name: 设置Node.js环境
uses: actions/setup-node@v3
with:
node-version: '18' # 指定Node版本
cache: 'npm' # 缓存依赖,加速后续运行
- name: 安装依赖
run: npm ci # 比npm install更快,且锁定版本
- name: 运行测试
run: npm test # 跑单元测试
- name: 检查测试结果
run: echo "测试完成!状态:${{ job.status }}"
`
踩过的坑:第一次配时,我用了npm install而不是npm ci。结果本地装的是^1.0.0版本,CI装的是1.0.1,导致测试失败。npm ci会严格按package-lock.json安装,杜绝这种问题。
push这个文件到GitHub,你会在仓库的Actions标签页看到流水线正在运行:
*图片示意:GitHub Actions界面,显示一个正在运行的workflow,包含steps日志*
第三步:加入Docker构建——让环境不再是个谜
光跑测试不够,我们还要确保构建产物在任何环境都能跑。Docker就是最佳选择——把应用和依赖打包成镜像,部署到哪都一样。
3.1 项目根目录加Dockerfile
`dockerfile
使用官方Node.js镜像作为基础
FROM node:18-alpine
设置工作目录
WORKDIR /app
复制package.json和package-lock.json
COPY package*.json ./
安装依赖
RUN npm ci --only=production
复制源代码
COPY . .
暴露端口
EXPOSE 3000
启动命令
CMD ["node", "server.js"]
`
3.2 更新CI文件,加入Docker构建和推送
`yaml
name: CI/CD Pipeline
on:
push:
branches: [ main, master ]
jobs:
test-and-build:
runs-on: ubuntu-latest
# 定义环境变量
env:
IMAGE_NAME: my-app
REGISTRY: ghcr.io # GitHub Container Registry
steps:
- uses: actions/checkout@v3
- name: 设置Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安装依赖并测试
run: |
npm ci
npm test
- name: 登录到GitHub容器注册表
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} # GitHub自动生成的token
- name: 构建并推送Docker镜像
uses: docker/build-push-action@v4
with:
context: .
push: true # 推送到注册表
tags: |
${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
`
关键点说明:
- ${{ github.sha }}
是当前commit的哈希值,用它作为镜像标签可以精确定位每个版本 - secrets.GITHUB_TOKEN
是GitHub自动生成的,不需要手动配置,安全又方便
第四步:自动部署到服务器——最爽的一步
现在测试通过、镜像也构建好了,就差最后一步:部署到服务器。我用的是最简单的方案:通过SSH远程执行命令。
4.1 配置服务器秘钥
- SSH_PRIVATE_KEY:私钥内容SSH_HOST
- :服务器IPSSH_USER
- :登录用户名
4.2 在CI文件末尾加入部署步骤
`yaml
- name: 部署到服务器
uses: appleboy/ssh-action@v0.1.5 # 社区流行的SSH Action
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# 拉取最新镜像
docker pull ghcr.io/${{ github.repository }}/my-app:latest
# 停止旧容器
docker stop my-app || true
docker rm my-app || true
# 启动新容器
docker run -d \
--name my-app \
--restart unless-stopped \
-p 3000:3000 \
ghcr.io/${{ github.repository }}/my-app:latest
# 清理旧镜像(保留最近5个)
docker image prune -a --filter "until=24h" -f
`
建议:第一次部署时,先在服务器手动跑一遍这些命令,确认没问题再配置到CI里。我吃过亏:SSH密钥权限不对,导致部署失败,排查了半小时才发现是chmod 600没做。
第五步:监控与回滚——给自己留条后路
CI/CD跑起来后,你可能会遇到一个问题:自动部署的版本出bug了,怎么办?
5.1 加入健康检查
在部署步骤后,加一个健康检查:
`yaml`
- name: 健康检查
run: |
sleep 10 # 等待容器启动
curl -f http://${{ secrets.SSH_HOST }}:3000/health || exit 1
如果健康检查失败,自动回滚:
`yaml`
- name: 回滚(如果健康检查失败)
if: failure()
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# 获取上一个版本的镜像标签
PREVIOUS_TAG=$(docker images --format "{{.Tag}}" | grep -v latest | sort | tail -2 | head -1)
docker run -d --name my-app-rollback -p 3000:3000 ghcr.io/${{ github.repository }}/my-app:$PREVIOUS_TAG
5.2 监控部署状态
在CI文件的最后,发送通知到企业微信或Slack:
`yaml`
- name: 发送部署通知
if: always()
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "部署${{ job.status == 'success' && '成功' || '失败' }}:${{ github.repository }}@${{ github.sha }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
*图片示意:企业微信或Slack收到部署通知的截图,显示成功或失败的卡片消息*
性能数据:CI/CD到底能省多少时间?
我自己的项目经验:
- 手动部署:平均每次10-15分钟(包括SSH连接、拉代码、装依赖、重启、验证)
- CI/CD部署:平均2-3分钟(自动完成全部流程)
- 部署失误率:从手动时的约15%降到接近0%
按每天部署3次算,一年能省下约200小时——差不多是5周的工作时间。
常见问题与避坑指南
Q1:CI/CD跑得很慢怎么办?
- 启用缓存:Docker构建时用–cache-from
,Node依赖用actions/cache - 并行化:将测试拆分成多个job并行执行
- 按需触发:只在特定分支或目录变更时触发
Q2:测试通过了,部署还是跪了?
这是最坑爹的情况。建议:
- 在Dockerfile里用COPY . .
前先COPY package*.json ./,利用Docker缓存层 - 部署前在CI环境运行一个"冒烟测试":curl localhost:3000
Q3:多环境怎么管理?
我用的是分支策略:
- dev
分支:自动部署到测试环境 - staging
分支:手动触发部署到预发布环境 - main
分支:自动部署到生产环境(但经过code review)
`yaml`
on:
push:
branches:
- dev
- main
workflow_dispatch: # 手动触发
branches:
- staging
结语:从今天开始,告别手动部署
CI/CD不是锦上添花,而是现代开发者的必备技能。它让你的代码从"在我机器上能跑"变成"在任何地方都能跑"。
立刻行动:
当你第一次看到代码push后几分钟,应用已经自动更新到线上时,你会回来感谢自己的。