每次发布都要手动打包、上传、重启服务。 出了bug,回滚也是手动操作。 这样的流程,效率低,还容易出错。CI/CD就是来解决这个问题的。
什么是CI/CD?
CI:持续集成(Continuous Integration)
开发者频繁地把代码合并到主分支。
每次合并都自动构建、测试。
及早发现问题,避免集成地狱。
CD:持续交付/部署(Continuous Delivery/Deployment)
持续交付:代码随时可以发布到生产环境。
持续部署:代码自动发布到生产环境。
区别在于最后一步是手动还是自动。

为什么需要CI/CD?
提高效率
自动化代替手动操作。
原来要半小时的部署,现在几分钟搞定。
减少错误
手动操作容易出错。
自动化流程标准化,减少人为失误。
快速反馈
代码提交后,几分钟就知道有没有问题。
不用等到集成时才发现bug。
频繁发布
有了CI/CD,可以一天发布多次。
快速迭代,快速响应用户需求。
CI/CD工具
市面上有很多工具。
Jenkins
老牌工具,功能强大,插件丰富。
但配置复杂,界面老旧。
GitLab CI
和GitLab集成,配置简单。
用YAML文件定义流水线。
GitHub Actions
GitHub自带的CI/CD。
和GitHub无缝集成,免费额度够用。
其他
-
CircleCI:云端CI/CD
-
Travis CI:开源项目免费
-
Azure DevOps:微软的解决方案
本文以GitHub Actions为例。
GitHub Actions实战
基本概念
-
Workflow:工作流,一个自动化流程
-
Job:任务,一个workflow包含多个job
-
Step:步骤,一个job包含多个step
-
Action:动作,可复用的步骤
第一个Workflow
在项目根目录创建.github/workflows/ci.yml:
name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build run: npm run build
这个workflow做了什么?
-
当代码push到main分支或创建PR时触发
-
在Ubuntu环境运行
-
检出代码
-
安装Node.js 18
-
安装依赖
-
运行测试
-
构建项目
提交这个文件,GitHub就会自动运行。
添加代码检查
- name: Lint run: npm run lint - name: Type check run: npm run type-check
在测试前加上代码检查和类型检查。
确保代码质量。
缓存依赖
每次都安装依赖很慢。
可以缓存node_modules:
- name: Cache dependencies uses: actions/cache@v3 with: path: ~/.npm key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node-
第二次运行会快很多。
自动部署
测试通过后,自动部署到服务器。
部署到服务器
deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - name: Deploy to server uses: appleboy/ssh-action@master with: host: {{ secrets.SERVER_HOST }} username: {{ secrets.SERVER_USER }} key: ${{ secrets.SSH_KEY }} script: | cd /var/www/myapp git pull npm install npm run build pm2 restart myapp
通过SSH连接服务器,执行部署命令。
敏感信息(服务器地址、密钥)存在GitHub Secrets里。
部署到Docker
docker: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build Docker image run: docker build -t myapp:{{ github.sha }} . - name: Push to registry run: \| echo {{ secrets.DOCKER_PASSWORD }} | docker login -u {{ secrets.DOCKER_USERNAME }} --password-stdin docker push myapp:{{ github.sha }} - name: Deploy run: | ssh {{ secrets.SERVER_USER }}@{{ secrets.SERVER_HOST }} \ "docker pull myapp:{{ github.sha }} \&\& \\ docker stop myapp \|\| true \&\& \\ docker rm myapp \|\| true \&\& \\ docker run -d --name myapp -p 3000:3000 myapp:{{ github.sha }}"
构建Docker镜像,推送到仓库,然后在服务器上运行。
🚀 部署流程
构建 → 测试 → 打包 → 推送 → 部署
全自动化
多环境部署
通常有多个环境:开发、测试、生产。
环境分支策略
-
dev分支 → 开发环境
-
staging分支 → 测试环境
-
main分支 → 生产环境
on: push: branches: - dev - staging - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set environment run: | if [ "{{ github.ref }}" == "refs/heads/main" \]; then echo "ENV=production" \>\> GITHUB_ENV elif [ "{{ github.ref }}" == "refs/heads/staging" \]; then echo "ENV=staging" \>\> GITHUB_ENV else echo "ENV=development" >> GITHUB_ENV fi - name: Deploy run: \| echo "Deploying to {{ env.ENV }}" # 根据环境执行不同的部署命令
使用环境变量
- name: Deploy to production if: env.ENV == 'production' run: | ssh {{ secrets.PROD_SERVER }} "cd /var/www/app \&\& ./deploy.sh" - name: Deploy to staging if: env.ENV == 'staging' run: \| ssh {{ secrets.STAGING_SERVER }} "cd /var/www/app && ./deploy.sh"
测试覆盖率
CI/CD不只是部署,还要保证质量。
- name: Test with coverage run: npm test -- --coverage - name: Upload coverage uses: codecov/codecov-action@v3 with: files: ./coverage/lcov.info
生成测试覆盖率报告,上传到Codecov。
可以在PR里看到覆盖率变化。
通知和监控
Slack通知
- name: Notify Slack if: always() uses: 8398a7/action-slack@v3 with: status: {{ job.status }} text: 'Deployment {{ job.status }}' webhook_url: ${{ secrets.SLACK_WEBHOOK }}
部署成功或失败,发送Slack通知。
邮件通知
- name: Send email if: failure() uses: dawidd6/action-send-mail@v3 with: server_address: smtp.gmail.com server_port: 465 username: {{ secrets.EMAIL_USERNAME }} password: {{ secrets.EMAIL_PASSWORD }} subject: 'CI/CD Failed' body: 'Build failed on ${{ github.ref }}' to: team@example.com
回滚策略
部署出问题了,要能快速回滚。
保留历史版本
- name: Backup current version run: | ssh {{ secrets.SERVER }} \\ "cp -r /var/www/app /var/www/app.backup.(date +%Y%m%d%H%M%S)" - name: Deploy new version run: | # 部署新版本 - name: Health check run: | sleep 10 curl -f http://myapp.com/health || exit 1
部署前备份,部署后健康检查。
如果健康检查失败,自动回滚。
蓝绿部署
同时运行两个版本。
新版本测试通过后,切换流量。
- name: Deploy to green run: | docker run -d --name app-green -p 3001:3000 myapp:new - name: Health check run: curl -f http://localhost:3001/health - name: Switch traffic run: | # 更新Nginx配置,把流量切到3001端口 # 停止旧版本
最佳实践
快速失败
把最容易失败的步骤放前面。
比如代码检查、单元测试。
这样可以更快发现问题,节省时间。
并行执行
多个job可以并行运行。
jobs: lint: runs-on: ubuntu-latest steps: - run: npm run lint test: runs-on: ubuntu-latest steps: - run: npm test build: needs: [lint, test] runs-on: ubuntu-latest steps: - run: npm run build
lint和test并行,都通过后再build。
保持流水线简单
不要把所有逻辑都写在workflow里。
复杂的逻辑写成脚本,workflow只负责调用。
- name: Deploy run: ./scripts/deploy.sh ${{ env.ENV }}
版本控制
每次部署打上tag。
- name: Create tag run: | git tag v$(date +%Y%m%d%H%M%S) git push --tags
方便追踪和回滚。
安全考虑
不要在代码里写密钥
用GitHub Secrets存储敏感信息。
在workflow里用${{ secrets.XXX }}引用。
限制权限
workflow的token权限要最小化。
permissions: contents: read deployments: write
审核部署
生产环境部署前,要人工审核。
deploy: runs-on: ubuntu-latest environment: name: production url: https://myapp.com steps: - name: Deploy run: ./deploy.sh
在GitHub设置里配置environment,需要审核才能部署。
总结
CI/CD是现代软件开发的标配。
它不只是工具,更是一种文化。
记住几个关键点:
-
自动化一切可以自动化的
-
快速反馈,及早发现问题
-
保持流水线简单清晰
-
做好测试和质量检查
-
有回滚策略
-
注意安全
最后,CI/CD不是一蹴而就的。
从简单开始,逐步完善。
先实现基本的构建和测试,再加部署,再加监控和通知。
一步一步来,不要贪多。