【CI/CD·Jenkins篇】Jenkins 与 Docker 集成:容器化构建与镜像推送

前言

当构建环境五花八门——有的项目用 Java 17,有的用 Go 1.21,有的需要 Node 20——在 Jenkins Agent 上预装所有环境不现实。Docker 解决了这个问题:每个构建步骤在指定版本的容器中执行,环境完全隔离且可复现。本篇从零搭建一个完整的容器化 CI 流水线。


一、Docker-in-Docker vs Docker Socket 挂载

方式一:挂载 Docker Socket(推荐)


Jenkins Agent (宿主机)
┌──────────────────────────────┐
│  Docker Daemon (宿主机)       │
│  /var/run/docker.sock         │
│       ↑ 挂载                  │
│  ┌────┴───────────────────┐  │
│  │ Pipeline 容器 (maven)    │  │
│  │  执行: docker build ...  │  │  ← 通过 socket 调用宿主机 Docker
│  │  执行: docker push ...    │  │
│  └────────────────────────┘  │
└──────────────────────────────┘

优点:简单直接,利用宿主机 Docker 引擎,无需嵌套 Docker

缺点:有安全风险——容器内可以控制宿主机的 Docker

配置方式:


# Dockerfile for Jenkins Agent
FROM maven:3.9-eclipse-temurin-17

# 安装 Docker CLI(只需要 CLI,不需要 daemon)
RUN apt-get update && apt-get install -y docker.io

USER jenkins

pipeline {
    agent {
        docker {
            image 'maven:3.9-eclipse-temurin-17'
            args '-v /var/run/docker.sock:/var/run/docker.sock -v $HOME/.m2:/root/.m2'
        }
    }
    stages {
        stage('Build') {
            steps { sh 'mvn clean package -DskipTests' }
        }
        stage('Docker Build') {
            steps {
                sh 'docker build -t myapp:${BUILD_NUMBER} .'
                sh 'docker push registry.com/myapp:${BUILD_NUMBER}'
            }
        }
    }
}

方式二:Docker-in-Docker(DinD)


Jenkins Agent (宿主机)
┌──────────────────────────────────┐
│  Pipeline 容器 (maven)            │
│  ┌────────────────────────────┐  │
│  │  DinD 容器 (docker:24-dind) │  │  ← 独立 Docker daemon
│  │  执行: docker build ...     │  │
│  └────────────────────────────┘  │
└──────────────────────────────────┘

优点:完全隔离,不影响宿主机

缺点:性能开销大,需要 --privileged 权限


pipeline {
    agent {
        docker {
            image 'maven:3.9-eclipse-temurin-17'
            args '--privileged -v /tmp:/tmp'
        }
    }
    stages {
        stage('Start Docker Service') {
            steps {
                // 启动内部 Docker daemon
                sh 'dockerd --host=tcp://0.0.0.0:2375 &'
                sh 'sleep 5'
                sh 'docker info'
            }
        }
    }
}
**踩坑提示**:Socket 挂载方式有安全风险,因为容器内的用户可以通过 Docker 命令控制宿主机。生产环境建议用 DinD 或专门的 Docker 构建节点。

二、多阶段 Dockerfile 构建

为什么用多阶段构建


传统构建:一个 Dockerfile 全包
  → 镜像包含编译器、源码、依赖 → 镜像 800MB+ → 安全风险

多阶段构建:
  → 阶段1: 编译 (maven 镜像, 含编译工具)
  → 阶段2: 运行 (jre 镜像, 只有运行时) → 镜像 150MB → 安全干净

多阶段 Dockerfile 示例


# === 阶段1: 构建 ===
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /build
# 先复制 pom.xml 利用缓存层
COPY pom.xml .
RUN mvn dependency:go-offline

# 复制源码并编译
COPY src ./src
RUN mvn clean package -DskipTests

# === 阶段2: 运行 ===
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app
# 从构建阶段复制产物
COPY --from=builder /build/target/*.jar app.jar

# 安全:用非 root 用户运行
RUN addgroup -S app && adduser -S app -G app
USER app

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar", "--server.port=8080"]

前端项目多阶段构建


# === 阶段1: 前端构建 ===
FROM node:20-alpine AS frontend-builder

WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build

# === 阶段2: Nginx 静态服务 ===
FROM nginx:1.25-alpine

COPY --from=frontend-builder /frontend/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s \
    CMD wget -qO- http://localhost/ || exit 1

三、镜像构建与推送流水线

完整 Pipeline


pipeline {
    agent { label 'docker-build' }
    
    environment {
        REGISTRY = 'registry.mycompany.com'
        IMAGE_NAME = "${REGISTRY}/myapp"
        IMAGE_TAG = "${env.BUILD_NUMBER}"
        DOFILE_PATH = 'Dockerfile'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Build App') {
            steps {
                // 在 Maven 容器中构建
                sh 'mvn clean package -DskipTests'
                stash includes: 'target/*.jar', name: 'app-jar'
            }
        }
        
        stage('Build Image') {
            steps {
                unstash 'app-jar'
                // 多阶段构建
                sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} -f ${DOCKERFILE_PATH} ."
                // 同时打一个 latest 标签
                sh "docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest"
            }
        }
        
        stage('Security Scan') {
            steps {
                // 用 Trivy 扫描镜像漏洞
                sh """
                    trivy image --exit-code 1 --severity HIGH,CRITICAL \
                        --ignorefile .trivyignore \
                        ${IMAGE_NAME}:${IMAGE_TAG}
                """
            }
        }
        
        stage('Push') {
            steps {
                withCredentials([usernamePassword(
                    credentialsId: 'docker-registry',
                    usernameVariable: 'REG_USER',
                    passwordVariable: 'REG_PASS'
                )]) {
                    sh "echo $REG_PASS | docker login $REGISTRY -u $REG_USER --password-stdin"
                    sh "docker push ${IMAGE_NAME}:${IMAGE_TAG}"
                    sh "docker push ${IMAGE_NAME}:latest"
                }
            }
        }
        
        stage('Sign Image') {
            when {
                branch 'main'
            }
            steps {
                // 用 Cosign 签名镜像
                sh """
                    cosign sign --key env://COSIGN_PRIVATE_KEY \
                        ${IMAGE_NAME}:${IMAGE_TAG}
                """
            }
        }
        
        stage('Clean') {
            steps {
                // 清理本地镜像
                sh "docker rmi ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest || true"
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
    }
}

镜像标签策略

| 标签 | 用途 | 示例 |

|------|------|------|

| BUILD_NUMBER | 每次构建唯一标识 | 42 |

| GIT_COMMIT | 关联代码版本 | a1b2c3d |

| BRANCH-COMMIT | 分支+commit | main-a1b2c3d |

| latest | 最新稳定版(仅 main 分支) | latest |

| vX.Y.Z | 正式发版 Tag | v1.2.3 |


environment {
    GIT_HASH = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
    IMAGE_TAG = "${env.BRANCH_NAME}-${GIT_HASH}"
}

四、构建缓存优化

Docker 层缓存


# 好的做法:先复制 lock 文件
COPY package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

# 坏的做法:每次代码变更都重新安装依赖
COPY . .
RUN npm ci && npm run build

Jenkins 构建缓存


pipeline {
    agent { 
        docker {
            image 'maven:3.9-eclipse-temurin-17'
            args '-v $HOME/.m2:/root/.m2 -v $HOME/.npm:/root/.npm'
        }
    }
    // 这样 Maven 本地仓库和 npm 缓存会在构建间复用
    // 第一次构建下载依赖 5 分钟,后续构建 30 秒
}

BuildKit 缓存


# Dockerfile 中使用 BuildKit 缓存挂载
# syntax=docker/dockerfile:1.6
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /build
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 mvn dependency:go-offline

COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn clean package -DskipTests

# 启用 BuildKit
DOCKER_BUILDKIT=1 docker build -t myapp .
**踩坑提示**:Docker 层缓存只在本地有效。在 CI 中每次构建都是新环境,需要通过 Volume 挂载来持久化缓存目录。Docker BuildKit 的 `--mount=type=cache` 是更现代的方案。

五、镜像安全扫描

Trivy 集成


stage('Security Scan') {
    steps {
        // 安装 Trivy(如果 Agent 上没有)
        sh '''
            if ! command -v trivy &> /dev/null; then
                curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
            fi
        '''
        
        // 扫描镜像,HIGH/CRITICAL 级别漏洞导致构建失败
        sh """
            trivy image \
                --exit-code 1 \
                --severity HIGH,CRITICAL \
                --ignore-unfixed \
                --format json \
                --output trivy-report.json \
                ${IMAGE_NAME}:${IMAGE_TAG}
        """
        
        // 归档报告
        archiveArtifacts artifacts: 'trivy-report.json', fingerprint: true
        
        // 发布到 Jenkins 报告界面
        recordIssues(
            tools: [trivy(id: 'trivy', pattern: 'trivy-report.json')],
            qualityGates: [[threshold: 1, type: 'TOTAL', unstable: false]]
        )
    }
}

Cosign 镜像签名


# 生成签名密钥
cosign generate-key-pair
# 设置环境变量:
# COSIGN_PRIVATE_KEY
# COSIGN_PASSWORD

# 签名镜像
cosign sign --key env://COSIGN_PRIVATE_KEY registry.com/myapp:42

# 验证签名(部署前验证)
cosign verify --key cosign.pub registry.com/myapp:42

六、本篇要点回顾

1. Docker Socket 挂载方式简单但有安全风险,生产环境用 DinD 或专用构建节点

2. 多阶段 Dockerfile 构建可以大幅减小镜像体积,移除编译工具和源码

3. 镜像标签用 BRANCH-COMMIT 格式,便于追踪代码版本

4. 构建缓存:Docker 层缓存 + Volume 挂载缓存 + BuildKit 缓存挂载

5. Trivy 扫描 + Cosign 签名实现镜像供应链安全

下一篇预告:CI/CD 的 Jenkins 篇到此结束,接下来进入 GitLab CI 实战篇:《快速入门:.gitlab-ci.yml 语法与 Runner 部署》。

内容概要:本文围绕三相并网逆变器的控制策略展开研究,重点探讨了虚拟阻抗统一有源阻尼相结合的控制方法,并实现了SVPWM(空间矢量脉宽调制)SPWM(正弦脉宽调制)两种调制方式在Simulink平台下的仿真建模。通过引入虚拟阻抗改善系统输出阻抗特性,结合统一有源阻尼技术有效抑制LC或LCL滤波器引起的谐振问题,从而提升逆变器在弱电网条件下的并网稳定性电能质量。研究涵盖了控制策略的设计、调制算法的实现、动态响应分析及谐波抑制效果评估,同时拓展涉及正负序分离、中点电位平衡、DPWMA调制等关键技术,构建了完整的高性能并网逆变器控制系统仿真体系。; 适合人群:适用于从事电力电子、新能源发电、智能电网及相关领域的研究生、科研人员和工程技术人员,特别是具备三相并网逆变器控制理论基础并熟悉MATLAB/Simulink仿真环境的专业人士;; 使用场景及目标:①用于高校科研机构开展并网逆变器稳定性控制策略的深入研究;②支撑学位论文撰写、学术期刊投稿或科研项目申报中的仿真验证工作;③为企业研发高性能、高可靠性的并网逆变器产品提供先进的控制方案技术原型支持;; 阅读建议:建议读者结合提供的Simulink模型文件进行实际操作仿真验证,重点关注虚拟阻抗参数设计有源阻尼的协同作用机制,深入理解不同调制策略对系统性能的影响,并可进一步拓展学习文中提及的正负序控制、中点电位平衡等先进控制技术,全面提升对复杂电网环境下并网系统稳定运行机制的认知水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值