Appearance
CI/CD 集成
概述
Git 与 CI/CD(持续集成/持续部署)系统紧密集成,可以自动化构建、测试和部署流程。本章介绍如何将 Git 与主流 CI/CD 平台集成。
Git 与 CI/CD
CI/CD 基本概念
- 持续集成 (CI):代码提交后自动构建和测试
- 持续部署 (CD):测试通过后自动部署到生产环境
- 持续交付 (CD):代码随时可以部署到生产环境
Git 在 CI/CD 中的作用
代码提交 → 触发 CI → 构建测试 → 部署
│ │ │ │
└── Git ───┴── Webhook ┴── 流水线 ┘常见触发方式
| 触发事件 | 说明 |
|---|---|
| push | 推送到分支时触发 |
| pull_request | 创建或更新 PR 时触发 |
| merge | 合并到主分支时触发 |
| tag | 创建标签时触发 |
| schedule | 定时触发 |
| manual | 手动触发 |
GitHub Actions
基本配置
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v3
with:
fetch-depth: 0 # 获取完整历史
- name: 设置 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 代码检查
run: npm run lint
- name: 运行测试
run: npm test -- --coverage
- name: 构建项目
run: npm run build处理子模块
yaml
# .github/workflows/ci.yml
steps:
- name: 检出代码(包含子模块)
uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.GITHUB_TOKEN }}矩阵构建
yaml
# .github/workflows/ci.yml
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [16, 18, 20]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test缓存优化
yaml
# .github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 缓存 Node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
- run: npm test部署配置
yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
tags:
- 'v*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 构建生产版本
run: |
npm ci
npm run build
- name: 部署到服务器
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/app
git pull origin main
npm install --production
npm run migrate
pm2 restart app发布到 npm
yaml
# .github/workflows/publish.yml
name: Publish to npm
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}GitLab CI
基本配置
yaml
# .gitlab-ci.yml
stages:
- lint
- test
- build
- deploy
variables:
NODE_VERSION: "18"
cache:
paths:
- node_modules/
lint:
stage: lint
image: node:${NODE_VERSION}
script:
- npm ci
- npm run lint
only:
- merge_requests
- main
test:
stage: test
image: node:${NODE_VERSION}
script:
- npm ci
- npm test -- --coverage
coverage: '/Lines\s*:\s*(\d+.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
only:
- merge_requests
- main
build:
stage: build
image: node:${NODE_VERSION}
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
only:
- main
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
script:
- ssh $SSH_USER@$SSH_HOST "cd /var/www/app && git pull && npm install --production && pm2 restart app"
only:
- main
when: manual使用 Docker
yaml
# .gitlab-ci.yml
stages:
- build
- deploy
build_image:
stage: build
image: docker:latest
services:
- docker:dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
only:
- main
deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache curl
- curl -X POST $WEBHOOK_URL
only:
- main环境部署
yaml
# .gitlab-ci.yml
stages:
- test
- deploy_staging
- deploy_production
deploy_staging:
stage: deploy_staging
environment:
name: staging
url: https://staging.example.com
script:
- ./deploy.sh staging
only:
- develop
deploy_production:
stage: deploy_production
environment:
name: production
url: https://example.com
script:
- ./deploy.sh production
only:
- main
when: manualGitLab CI 变量
yaml
# .gitlab-ci.yml
variables:
# 全局变量
APP_NAME: "my-app"
REGISTRY: "registry.example.com"
# Git 变量(自动提供)
# CI_COMMIT_SHA - 提交哈希
# CI_COMMIT_SHORT_SHA - 短提交哈希
# CI_COMMIT_REF_NAME - 分支或标签名
# CI_COMMIT_MESSAGE - 提交信息
# CI_COMMIT_AUTHOR - 提交作者
job:
script:
- echo "Building $APP_NAME"
- echo "Commit: $CI_COMMIT_SHA"
- echo "Branch: $CI_COMMIT_REF_NAME"
- echo "Author: $CI_COMMIT_AUTHOR"Jenkins 集成
Jenkinsfile 基本配置
groovy
// Jenkinsfile
pipeline {
agent any
tools {
nodejs 'NodeJS-18'
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git log -1 --pretty=format:"%h - %an, %ar : %s"'
}
}
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Test') {
steps {
sh 'npm test'
}
post {
always {
junit 'test-results/*.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh './deploy.sh'
}
}
}
post {
success {
echo 'Build succeeded!'
slackSend(color: 'good', message: "Build succeeded: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
}
failure {
echo 'Build failed!'
slackSend(color: 'danger', message: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
}
}
}多分支流水线
groovy
// Jenkinsfile
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'npm ci'
sh 'npm run build'
}
}
stage('Test') {
steps {
sh 'npm test'
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh './deploy.sh staging'
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
input {
message "Deploy to production?"
ok "Deploy"
}
steps {
sh './deploy.sh production'
}
}
}
}Git 触发器配置
groovy
// Jenkinsfile
pipeline {
agent any
triggers {
// 轮询 SCM 变化
pollSCM('H/5 * * * *')
// 或使用 GitLab/GitHub 插件的 webhook
gitlab(triggerOnPush: true, triggerOnMergeRequest: true)
}
stages {
stage('Build') {
steps {
sh 'npm run build'
}
}
}
}参数化构建
groovy
// Jenkinsfile
pipeline {
agent any
parameters {
choice(
name: 'ENVIRONMENT',
choices: ['staging', 'production'],
description: '部署环境'
)
string(
name: 'BRANCH',
defaultValue: 'main',
description: '部署分支'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: '跳过测试'
)
}
stages {
stage('Checkout') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "*/${params.BRANCH}"]],
userRemoteConfigs: scm.userRemoteConfigs
])
}
}
stage('Test') {
when {
expression { !params.SKIP_TESTS }
}
steps {
sh 'npm test'
}
}
stage('Deploy') {
steps {
sh "./deploy.sh ${params.ENVIRONMENT}"
}
}
}
}CI/CD 最佳实践
1. 分支策略
yaml
# GitHub Actions 示例
on:
push:
branches:
- main # 主分支:自动部署生产
- develop # 开发分支:自动部署测试
pull_request:
branches:
- main # PR:运行测试
types:
- opened
- synchronize2. 环境变量管理
yaml
# GitHub Actions
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # 使用 GitHub Environments
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_URL: ${{ secrets.DB_URL }}
run: ./deploy.sh3. 构建缓存
yaml
# GitHub Actions
- name: 缓存依赖
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}4. 安全最佳实践
yaml
# 不要在日志中输出敏感信息
- name: Deploy
run: |
echo "::add-mask::${{ secrets.API_KEY }}"
./deploy.sh5. 通知配置
yaml
# GitHub Actions
jobs:
notify:
runs-on: ubuntu-latest
if: always()
steps:
- name: Slack Notification
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}总结
| 平台 | 配置文件 | 特点 |
|---|---|---|
| GitHub Actions | .github/workflows/*.yml | 与 GitHub 深度集成,市场丰富 |
| GitLab CI | .gitlab-ci.yml | 内置 Docker 支持,环境管理 |
| Jenkins | Jenkinsfile | 灵活强大,插件丰富 |
选择建议:
- GitHub 项目:使用 GitHub Actions
- GitLab 项目:使用 GitLab CI
- 企业私有部署:使用 Jenkins
- 多平台支持:考虑可移植性
最佳实践:
- 使用分支策略控制部署流程
- 敏感信息使用 Secrets 管理
- 启用构建缓存提高效率
- 配置通知及时了解构建状态
