CI/CD 流水线设计实战
CI/CD(持续集成/持续部署)是现代软件交付的核心实践。通过自动化构建、测试、部署流程,可以快速、可靠地将代码变更交付到生产环境。本文详解 Jenkins、GitHub Actions、GitLab CI 等主流 CI/CD 工具的配置与最佳实践。
一、CI/CD 基础概念
1.1 核心概念
graph LR
Git[代码提交<br/>Git] --> Build[构建<br/>Maven]
Build --> Test[测试<br/>JUnit]
Test --> Deploy[部署<br/>K8s]
Deploy --> Monitor[监控<br/>Prometheus]
| 概念 | 说明 | 目标 |
|---|---|---|
| CI(持续集成) | 频繁合并代码到主干,自动构建测试 | 尽早发现问题 |
| CD(持续交付) | 代码随时可部署到生产环境 | 快速交付价值 |
| CD(持续部署) | 代码自动部署到生产环境 | 完全自动化 |
1.2 流水线设计原则
mindmap
root((优秀流水线特征))
快速反馈
构建时间<10 分钟
测试时间<5 分钟
可靠性
流水线成功率>95%
可重复性
任何环境都能<br/>复现相同结果
可视化
清晰展示每个<br/>阶段状态
容错性
失败后能<br/>快速恢复
二、Jenkins 流水线
2.1 Jenkins 安装
Docker 安装:
# 启动 Jenkins
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins:lts-jdk11
# 获取初始密码
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# 访问 http://localhost:8080 完成安装
插件安装:
必需插件:
├── Pipeline(流水线)
├── Pipeline: Stage View(阶段视图)
├── Git Plugin(Git 集成)
├── Maven Integration(Maven 集成)
├── Docker Plugin(Docker 集成)
├── Kubernetes Plugin(K8s 集成)
└── Blue Ocean(现代化 UI)
2.2 基础流水线
Jenkinsfile(声明式语法):
pipeline {
agent any
tools {
maven 'Maven 3.8' // 需在 Jenkins 配置
jdk 'JDK 11' // 需在 Jenkins 配置
}
environment {
DOCKER_IMAGE = "myapp"
DOCKER_REGISTRY = "registry.example.com"
}
stages {
stage('Checkout') {
steps {
checkout scm
echo "代码版本:${GIT_COMMIT}"
}
}
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
post {
success {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
publishHTML(target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/site/jacoco',
reportFiles: 'index.html',
reportName: '代码覆盖率报告'
])
}
}
}
stage('Docker Build') {
steps {
script {
docker.build("${DOCKER_IMAGE}:${BUILD_ID}")
}
}
}
stage('Push') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
docker.image("${DOCKER_IMAGE}:${BUILD_ID}").push()
docker.image("${DOCKER_IMAGE}:${BUILD_ID}").push('latest')
}
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh '''
kubectl set image deployment/myapp myapp=${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${BUILD_ID}
kubectl rollout status deployment/myapp
'''
}
}
}
post {
always {
cleanWs()
echo '流水线完成'
}
success {
echo '构建成功!'
}
failure {
echo '构建失败!请检查日志'
// 发送通知
// mail to: 'team@example.com', subject: "构建失败:${env.JOB_NAME}", body: "请查看:${env.BUILD_URL}"
}
}
}
2.3 并行阶段
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
parallel {
stage('单元测试') {
steps {
sh 'mvn test -Dtest=*Test'
}
}
stage('集成测试') {
steps {
sh 'mvn test -Dtest=*IT'
}
}
stage('代码质量') {
steps {
sh 'mvn sonar:sonar'
}
}
}
}
stage('Deploy') {
steps {
echo '部署到环境'
}
}
}
}
2.4 多环境部署
pipeline {
agent any
environment {
DOCKER_IMAGE = "myapp"
}
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
script {
docker.build("${DOCKER_IMAGE}:${BUILD_ID}")
}
}
}
stage('Deploy to Dev') {
when {
branch 'develop'
}
steps {
deployToEnvironment('dev')
}
}
stage('Deploy to Test') {
when {
branch 'test'
}
steps {
deployToEnvironment('test')
}
}
stage('Deploy to Prod') {
when {
branch 'main'
}
steps {
input message: '确认部署到生产环境?', ok: '确认部署'
deployToEnvironment('prod')
}
}
}
post {
always {
cleanWs()
}
}
}
// 自定义函数
def deployToEnvironment(String env) {
echo "部署到 ${env} 环境"
sh """
kubectl --context=${env} set image deployment/myapp myapp=${DOCKER_IMAGE}:${BUILD_ID}
kubectl --context=${env} rollout status deployment/myapp
"""
}
2.5 共享库
目录结构:
jenkins-shared-library/
├── vars/
│ ├── commonPipeline.groovy
│ ├── deployToK8s.groovy
│ └── sendNotification.groovy
├── src/
│ └── com/
│ └── example/
│ └── JenkinsUtils.groovy
└── resources/
└── config.yaml
共享库代码:
// vars/commonPipeline.groovy
def call(Map config = [:]) {
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh config.buildCommand ?: 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh config.testCommand ?: 'mvn test'
}
}
stage('Push') {
steps {
script {
docker.withRegistry(config.registry ?: 'https://registry.example.com', config.credentials ?: 'docker-credentials') {
docker.image("${config.image}:${BUILD_ID}").push()
}
}
}
}
}
}
}
使用共享库:
@Library('jenkins-shared-library') _
commonPipeline(
image: 'myapp',
registry: 'https://registry.example.com',
credentials: 'docker-credentials',
buildCommand: 'mvn clean package -DskipTests',
testCommand: 'mvn test'
)
三、GitHub Actions
3.1 基础配置
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn clean package -DskipTests
- name: Run tests
run: mvn test
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: target/surefire-reports/
3.2 Docker 构建与推送
# .github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [main, develop]
tags: ['v*']
pull_request:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: myapp
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha,prefix=sha-
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=myapp:buildcache
cache-to: type=registry,ref=myapp:buildcache,mode=max
3.3 多环境部署
# .github/workflows/deploy.yml
name: Deploy to Kubernetes
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'dev'
type: choice
options:
- dev
- test
- prod
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'dev' }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.26.0'
- name: Configure kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/myapp myapp=myapp:${{ github.sha }}
kubectl rollout status deployment/myapp
- name: Verify deployment
run: |
kubectl get pods -l app=myapp
kubectl get svc myapp-service
3.4 矩阵构建
# .github/workflows/matrix-build.yml
name: Matrix Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [11, 17, 21]
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: maven
- name: Build and test
run: mvn clean verify
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ matrix.java-version }}-${{ matrix.os }}
path: target/*.jar
3.5 自定义 Action
# action.yml
name: 'Maven Build'
description: '执行 Maven 构建'
inputs:
java-version:
description: 'Java 版本'
required: true
default: '11'
maven-args:
description: 'Maven 参数'
required: false
default: 'clean package'
outputs:
artifact-path:
description: '构建产物路径'
value: ${{ steps.build.outputs.artifact }}
runs:
using: "composite"
steps:
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: ${{ inputs.java-version }}
distribution: 'temurin'
cache: maven
- name: Build
id: build
shell: bash
run: |
mvn ${{ inputs.maven-args }}
echo "artifact=target/*.jar" >> $GITHUB_OUTPUT
# 使用自定义 Action
- name: Build application
uses: ./.github/actions/maven-build
with:
java-version: '11'
maven-args: 'clean package -DskipTests'
四、GitLab CI
4.1 基础配置
# .gitlab-ci.yml
stages:
- build
- test
- package
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .m2/repository/
- target/
build:
stage: build
image: maven:3.8-openjdk-11
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 week
test:
stage: test
image: maven:3.8-openjdk-11
script:
- mvn test
artifacts:
reports:
junit: target/surefire-reports/*.xml
paths:
- target/site/jacoco/
expire_in: 1 week
docker-build:
stage: package
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
only:
- main
- develop
deploy-dev:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp myapp=$DOCKER_IMAGE
- kubectl rollout status deployment/myapp
environment:
name: development
url: https://dev.example.com
only:
- develop
deploy-prod:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp myapp=$DOCKER_IMAGE
- kubectl rollout status deployment/myapp
environment:
name: production
url: https://www.example.com
when: manual
only:
- main
4.2 多环境配置
# .gitlab-ci.yml
stages:
- build
- test
- deploy
include:
- local: '/.gitlab/build.yml'
- local: '/.gitlab/test.yml'
- local: '/.gitlab/deploy.yml'
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
# .gitlab/deploy.yml
deploy:dev:
stage: deploy
script:
- echo "部署到开发环境"
environment:
name: development
url: https://dev.example.com
on_stop: stop:dev
auto_stop_in: 1 week
only:
- develop
deploy:test:
stage: deploy
script:
- echo "部署到测试环境"
environment:
name: testing
url: https://test.example.com
only:
- test
deploy:prod:
stage: deploy
script:
- echo "部署到生产环境"
environment:
name: production
url: https://www.example.com
when: manual
only:
- main
stop:dev:
stage: deploy
script:
- echo "停止开发环境"
environment:
name: development
action: stop
when: manual
only:
- develop
五、最佳实践
5.1 流水线优化
问题:构建时间长
解决方案:
# 1. 缓存依赖
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .m2/repository/
# 2. 并行执行
test:
parallel:
matrix:
- TEST_GROUP: [unit, integration, e2e]
# 3. 增量构建
build:
script:
- mvn clean package -DskipTests -T 1C # 多线程构建
# 4. 只构建变更部分
build:
rules:
- changes:
- src/**/*
- pom.xml
5.2 安全实践
# 1. 使用 Secret 管理敏感信息
# GitHub Secrets / GitLab Variables / Jenkins Credentials
# 2. 最小权限原则
permissions:
contents: read
packages: write
# 3. 依赖扫描
dependency-check:
script:
- mvn org.owasp:dependency-check-maven:check
# 4. 镜像扫描
scan:
script:
- docker scan $DOCKER_IMAGE
# 5. 签名验证
sign:
script:
- cosign sign $DOCKER_IMAGE
5.3 监控与告警
// Jenkins 监控
post {
always {
script {
// 发送构建指标到 Prometheus
def duration = currentBuild.duration
def status = currentBuild.result ?: 'SUCCESS'
// 发送到监控系统
httpRequest url: "http://prometheus-pushgateway:9091/metrics",
httpMode: 'POST',
contentType: 'TEXT',
requestBody: "build_duration{job='${JOB_NAME}'} ${duration}\nbuild_status{job='${JOB_NAME}',status='${status}'} 1"
}
}
failure {
// 发送告警通知
slackSend channel: '#alerts',
color: 'danger',
message: "构建失败:${JOB_NAME} #${BUILD_NUMBER}\n${BUILD_URL}"
}
}
5.4 流水线即代码
mindmap
root((流水线即代码项目结构))
.github/workflows
ci.yml 持续集成
cd.yml 持续部署
security.yml 安全扫描
Jenkinsfile Jenkins 流水线
.gitlab-ci.yml GitLab CI
.drone.yml Drone CI
六、总结
6.1 工具选择
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| GitHub 项目 | GitHub Actions | 原生集成、免费额度 |
| GitLab 项目 | GitLab CI | 原生集成、功能完整 |
| 企业自建 | Jenkins | 灵活、插件丰富 |
| 云原生 | Tekton/Argo | Kubernetes 原生 |
6.2 核心要点
- 快速反馈:构建时间控制在 10 分钟内
- 可靠性:流水线成功率 > 95%
- 安全性:Secret 管理、依赖扫描、镜像签名
- 可维护性:流水线即代码、共享库、模板化
- 可观测性:日志、指标、告警完善
CI/CD 不是一蹴而就的,持续改进才能发挥最大价值。