Git 与 CI/CD 集成
2026/3/20大约 7 分钟
Git 与 CI/CD 集成
Git 在持续集成/持续部署中的应用
CI/CD 概述
什么是 CI/CD?
CI/CD 定义:
| 类型 | 定义 |
|---|---|
| 持续集成 (CI) | 开发者提交代码 → 自动构建 → 自动测试 → 代码质量检查 → 反馈结果 |
| 持续交付 (CD) | CI + 自动部署到测试环境 + 手动触发生产部署 |
| 持续部署 (CD) | CI + 自动部署到所有环境(包括生产环境) |
完整流程:
Git 在 CI/CD 中的作用
- 触发器:推送、合并、标签等事件触发流水线
- 版本追踪:记录每次构建对应的代码版本
- 配置存储:CI/CD 配置文件版本化管理
- 制品标记:使用 Git 标签标记发布版本
GitHub Actions
基础配置
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run build
完整的 CI/CD 流水线
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
tags: ["v*"]
pull_request:
branches: [main]
env:
NODE_VERSION: "20"
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# 代码质量检查
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- run: npm ci
- run: npm run lint
- run: npm run type-check
# 测试
test:
runs-on: ubuntu-latest
needs: lint
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm ci
- run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
# 构建
build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- run: npm ci
- run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build
path: dist/
# 构建 Docker 镜像
docker:
runs-on: ubuntu-latest
needs: build
if: github.event_name != 'pull_request'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build
path: dist/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 部署到测试环境
deploy-staging:
runs-on: ubuntu-latest
needs: docker
if: github.ref == 'refs/heads/develop'
environment:
name: staging
url: https://staging.example.com
steps:
- name: Deploy to staging
run: |
echo "Deploying to staging..."
# 部署脚本
# 部署到生产环境
deploy-production:
runs-on: ubuntu-latest
needs: docker
if: startsWith(github.ref, 'refs/tags/v')
environment:
name: production
url: https://example.com
steps:
- name: Deploy to production
run: |
echo "Deploying to production..."
# 部署脚本
# 创建 GitHub Release
release:
runs-on: ubuntu-latest
needs: deploy-production
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build
path: dist/
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: dist/*
generate_release_notes: true
PR 检查工作流
# .github/workflows/pr-check.yml
name: PR Check
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check PR title
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check commits
uses: wagoid/commitlint-github-action@v5
- name: Check for conflicts
run: |
git fetch origin main
git merge-tree $(git merge-base HEAD origin/main) HEAD origin/main
- name: Check file changes
id: changes
uses: dorny/paths-filter@v2
with:
filters: |
src:
- 'src/**'
tests:
- 'tests/**'
docs:
- 'docs/**'
- name: Require tests for src changes
if: steps.changes.outputs.src == 'true' && steps.changes.outputs.tests != 'true'
run: |
echo "::warning::Source code changed but no test changes detected"
GitLab CI/CD
基础配置
# .gitlab-ci.yml
stages:
- lint
- test
- build
- deploy
variables:
NODE_VERSION: "20"
default:
image: node:${NODE_VERSION}
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
lint:
stage: lint
script:
- npm run lint
- npm run type-check
test:
stage: test
script:
- npm test -- --coverage
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
deploy-staging:
stage: deploy
script:
- echo "Deploying to staging..."
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy-production:
stage: deploy
script:
- echo "Deploying to production..."
environment:
name: production
url: https://example.com
only:
- tags
when: manual
高级 GitLab CI 配置
# .gitlab-ci.yml
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
stages:
- lint
- test
- build
- security
- deploy
- release
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
.node-job:
image: node:20
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
before_script:
- npm ci
lint:
extends: .node-job
stage: lint
script:
- npm run lint
- npm run type-check
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH
test:unit:
extends: .node-job
stage: test
script:
- npm run test:unit -- --coverage
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
junit: junit.xml
paths:
- coverage/
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH
test:e2e:
extends: .node-job
stage: test
services:
- name: postgres:15
alias: db
- name: redis:7
alias: cache
variables:
DATABASE_URL: postgres://postgres:postgres@db:5432/test
REDIS_URL: redis://cache:6379
script:
- npm run test:e2e
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
build:app:
extends: .node-job
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
build:docker:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
deploy:staging:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache curl
- curl -X POST $DEPLOY_WEBHOOK_STAGING
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"
deploy:production:
stage: deploy
script:
- echo "Deploying to production..."
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_TAG
when: manual
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
script:
- echo "Creating release..."
release:
tag_name: $CI_COMMIT_TAG
description: "./CHANGELOG.md"
rules:
- if: $CI_COMMIT_TAG
Jenkins Pipeline
Jenkinsfile 示例
// Jenkinsfile
pipeline {
agent any
environment {
NODE_VERSION = '20'
DOCKER_REGISTRY = 'docker.io'
IMAGE_NAME = 'myapp'
}
options {
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '10'))
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install') {
steps {
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
sh 'npm ci'
}
}
}
stage('Lint') {
steps {
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
sh 'npm run lint'
}
}
}
stage('Test') {
steps {
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
sh 'npm test -- --coverage'
}
}
post {
always {
junit 'junit.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage/lcov-report',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Build') {
steps {
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
sh 'npm run build'
}
}
}
stage('Docker Build') {
when {
anyOf {
branch 'main'
branch 'develop'
buildingTag()
}
}
steps {
script {
def tag = env.TAG_NAME ?: env.BRANCH_NAME
docker.build("${IMAGE_NAME}:${tag}")
}
}
}
stage('Deploy Staging') {
when {
branch 'develop'
}
steps {
echo 'Deploying to staging...'
// 部署脚本
}
}
stage('Deploy Production') {
when {
buildingTag()
}
input {
message "Deploy to production?"
ok "Deploy"
}
steps {
echo 'Deploying to production...'
// 部署脚本
}
}
}
post {
always {
cleanWs()
}
success {
slackSend(
color: 'good',
message: "Build succeeded: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
failure {
slackSend(
color: 'danger',
message: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}
}
Git 标签与版本发布
自动化版本管理
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Generate changelog
id: changelog
uses: metcalfc/changelog-generator@v4
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
files: |
dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
语义化版本自动发布
# .github/workflows/semantic-release.yml
name: Semantic Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
// release.config.js
module.exports = {
branches: ["main"],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/git",
{
assets: ["CHANGELOG.md", "package.json"],
message: "chore(release): ${nextRelease.version} [skip ci]",
},
],
],
};
环境管理
多环境配置
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main, develop]
workflow_dispatch:
inputs:
environment:
description: "Environment to deploy"
required: true
default: "staging"
type: choice
options:
- staging
- production
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || 'staging') }}
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Configure environment
id: config
run: |
if [[ "${{ github.event.inputs.environment }}" == "production" ]] || [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "env=production" >> $GITHUB_OUTPUT
echo "url=https://example.com" >> $GITHUB_OUTPUT
else
echo "env=staging" >> $GITHUB_OUTPUT
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
fi
- name: Deploy
id: deploy
run: |
echo "Deploying to ${{ steps.config.outputs.env }}..."
echo "url=${{ steps.config.outputs.url }}" >> $GITHUB_OUTPUT
最佳实践
CI/CD 流水线设计原则
速度优化 ⚡:
- ✅ 使用缓存(依赖、构建产物)
- ✅ 并行执行独立任务
- ✅ 增量构建和测试
- ✅ 只在必要时运行完整测试
可靠性 🛡️:
- ✅ 幂等的构建和部署脚本
- ✅ 自动重试临时失败
- ✅ 健康检查和回滚机制
- ✅ 环境隔离
安全性 🔒:
- ✅ 敏感信息使用 Secrets
- ✅ 最小权限原则
- ✅ 代码和依赖安全扫描
- ✅ 签名和验证制品
可维护性 🔧:
- ✅ 配置即代码
- ✅ 模块化和可重用的流水线
- ✅ 清晰的日志和通知
- ✅ 文档化的流程
分支与环境映射
| 分支 | 环境 | 触发条件 |
|---|---|---|
feature/* | 开发环境 | PR 创建/更新 |
develop | 测试环境 | 推送到 develop |
release/* | 预发布环境 | 推送到 release 分支 |
main | 生产环境 | 推送到 main / 标签 |
v*.*.* | 生产环境 | 创建版本标签 |
PR 工作流:
- 创建 PR → 自动部署到临时环境
- 审核通过 → 合并到 develop
- develop 测试通过 → 合并到 main
- 创建版本标签 → 部署到生产