Skip to content

Jenkins 与 Docker、Kubernetes 集成

「Jenkins 怎么构建 Docker 镜像?怎么部署到 K8s?」——集成是 Jenkins 的强项。

Jenkins 的价值在于它能连接各种工具链。Docker 构建、K8s 部署、Helm Chart 管理——这些都可以在 Jenkins 流水线中自动化完成。

Docker 集成

Docker Pipeline 插件

groovy
pipeline {
    agent {
        docker {
            image 'maven:3.9-eclipse-temurin-17'
            args '-v $HOME/.m2:/root/.m2'
            label 'docker'
        }
    }

    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
    }
}

多容器流水线

groovy
pipeline {
    agent none

    stages {
        stage('Build & Test') {
            steps {
                script {
                    docker.withRegistry('https://registry.example.com', 'docker-registry') {
                        def image = docker.build("myapp:${env.BUILD_NUMBER}", """
                            Dockerfile
                            --build-arg VERSION=${env.BUILD_NUMBER}
                        """)

                        // 构建后立即测试
                        def testContainer = image.run('-p 8080:8080')
                        try {
                            sh 'sleep 10 && curl -f http://localhost:8080/health'
                            sh 'docker exec ${testContainer.id} mvn test'
                        } finally {
                            testContainer.stop()
                        }

                        // 推送到 registry
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }
    }
}

DinD(Docker-in-Docker)

在 K8s Pod 中运行 Docker-in-Docker:

groovy
agent {
    kubernetes {
        label 'docker-build'
        yaml '''
            apiVersion: v1
            kind: Pod
            spec:
                containers:
                    - name: dind
                      image: docker:24-dind
                      securityContext:
                        privileged: true
                      volumeMounts:
                          - name: docker-graph-storage
                            mountPath: /var/lib/docker
                volumes:
                    - name: docker-graph-storage
                      emptyDir: {}
        '''
    }
}

stages {
    stage('Build Docker Image') {
        steps {
            container('dind') {
                sh '''
                    dockerd &
                    sleep 5
                    docker build -t myapp:${BUILD_NUMBER} .
                    docker push myapp:${BUILD_NUMBER}
                '''
            }
        }
    }
}

Kubernetes 集成

Kubernetes Plugin:动态 Agent

安装 kubernetes 插件后,Jenkins 可以自动在 K8s 集群中创建 Pod 来执行构建:

groovy
// Jenkinsfile
pipeline {
    agent {
        kubernetes {
            label 'java-build'
            defaultContainer 'maven'
            yaml '''
                apiVersion: v1
                kind: Pod
                spec:
                    serviceAccountName: jenkins-agent
                    containers:
                        - name: maven
                          image: maven:3.9-eclipse-temurin-17
                          command: ["sleep"]
                          args: ["infinity"]
                          resources:
                            requests:
                                cpu: "1"
                                memory: "1Gi"
                            limits:
                                cpu: "2"
                                memory: "2Gi"
                          volumeMounts:
                              - name: maven-cache
                                mountPath: /root/.m2
                    volumes:
                        - name: maven-cache
                          persistentVolumeClaim:
                            claimName: maven-cache-pvc
            '''
        }
    }

    stages {
        stage('Build') {
            steps {
                container('maven') {
                    sh 'mvn clean package -DskipTests'
                }
            }
        }
    }
}

K8s 部署流水线

groovy
pipeline {
    agent any

    environment {
        REGISTRY = 'registry.example.com'
        APP_NAME = 'my-service'
        NAMESPACE = 'production'
    }

    stages {
        stage('Build Image') {
            steps {
                script {
                    def imageTag = "${env.REGISTRY}/${env.APP_NAME}:${env.BUILD_NUMBER}"
                    docker.withRegistry("https://${env.REGISTRY}", 'docker-credentials') {
                        def image = docker.build(imageTag, """
                            -f Dockerfile
                            --build-arg JAR_FILE=target/app.jar
                            .
                        """)
                        image.push()
                        image.push('latest')
                    }
                    currentBuild.displayName = "#${env.BUILD_NUMBER}"
                }
            }
        }

        stage('Deploy to K8s') {
            when {
                branch 'main'
            }
            steps {
                script {
                    def imageTag = "${env.REGISTRY}/${env.APP_NAME}:${env.BUILD_NUMBER}"
                    sh """
                        kubectl set image deployment/${env.APP_NAME} \\
                            ${env.APP_NAME}=${imageTag} \\
                            -n ${env.NAMESPACE}

                        kubectl rollout status deployment/${env.APP_NAME} \\
                            -n ${env.NAMESPACE} \\
                            --timeout=300s
                    """
                }
            }
        }

        stage('Smoke Test') {
            steps {
                script {
                    def svcUrl = "http://${env.APP_NAME}.${env.NAMESPACE}.svc.cluster.local"
                    sh """
                        sleep 10
                        curl -f "${svcUrl}/health"
                        curl -f "${svcUrl}/api/version"
                    """
                }
            }
        }
    }

    post {
        success {
            echo 'Deployment successful!'
            slackSend channel: '#ci-cd', color: 'good',
                message: "Deployed ${APP_NAME}:${BUILD_NUMBER} to ${NAMESPACE}"
        }
        failure {
            echo 'Deployment failed!'
            slackSend channel: '#ci-cd', color: 'danger',
                message: "Failed to deploy ${APP_NAME} to ${NAMESPACE}. Check ${BUILD_URL}"
        }
    }
}

Helm Chart 部署

groovy
stage('Deploy via Helm') {
    steps {
        script {
            withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                sh '''
                    helm upgrade --install myapp ./chart \
                        --namespace production \
                        --set image.tag=${BUILD_NUMBER} \
                        --set image.repository=${REGISTRY}/${APP_NAME} \
                        --wait --timeout 5m \
                        --timeout 300s

                    helm test myapp --namespace production
                '''
            }
        }
    }
}

凭证管理

Kubeconfig 凭证

在 Jenkins 中创建 Kubernetes configuration (kubeconfig) 类型的凭证:

groovy
// 使用 Kubeconfig 凭证部署
withCredentials([file(credentialsId: 'prod-kubeconfig', variable: 'KUBECONFIG')]) {
    sh '''
        kubectl --kubeconfig=$KUBECONFIG apply -f deployment.yaml
    '''
}

Docker Registry 凭证

groovy
// 登录到私有 registry
docker.withRegistry('https://registry.example.com', 'docker-registry') {
    def image = docker.build("myapp:${BUILD_NUMBER}")
    image.push()
}

SSH 凭证

groovy
// 使用 SSH 凭证
withCredentials([sshUserPrivateKey(
    credentialsId: 'deploy-ssh-key',
    keyFileVariable: 'SSH_KEY',
    passphraseVariable: '',
    usernameVariable: 'SSH_USER'
)]) {
    sh '''
        chmod 600 $SSH_KEY
        scp -i $SSH_KEY build.jar ${SSH_USER}@server:/opt/app/
        ssh -i $SSH_KEY ${SSH_USER}@server "systemctl restart myapp"
    '''
}

完整示例:Java 微服务 CI/CD

groovy
pipeline {
    agent {
        kubernetes {
            label 'java-microservice'
            defaultContainer 'maven'
        }
    }

    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
    }

    environment {
        REGISTRY = 'registry.example.com'
        APP_NAME = 'user-service'
        NAMESPACE = 'staging'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                container('maven') {
                    sh 'mvn clean package -DskipTests'
                }
            }
        }

        stage('Test') {
            steps {
                container('maven') {
                    sh 'mvn test'
                }
            }
        }

        stage('SonarQube') {
            steps {
                container('maven') {
                    script {
                        def scannerHome = tool 'SonarQube'
                        withSonarQubeEnv('SonarQube') {
                            sh """
                                ${scannerHome}/bin/sonar-scanner \
                                    -Dsonar.projectKey=${APP_NAME} \
                                    -Dsonar.sources=src
                            """
                        }
                    }
                }
            }
        }

        stage('Build & Push Image') {
            steps {
                container('docker') {
                    script {
                        def imageTag = "${REGISTRY}/${APP_NAME}:${BUILD_NUMBER}"
                        def image = docker.build(imageTag, "-f Dockerfile .")
                        docker.withRegistry("https://${REGISTRY}", 'docker-credentials') {
                            image.push()
                        }
                    }
                }
            }
        }

        stage('Trivy Scan') {
            steps {
                container('trivy') {
                    sh '''
                        trivy image --exit-code 1 \
                            --severity HIGH,CRITICAL \
                            --ignore-unfixed \
                            ${REGISTRY}/${APP_NAME}:${BUILD_NUMBER}
                    '''
                }
            }
        }

        stage('Deploy to Staging') {
            steps {
                container('kubectl') {
                    sh '''
                        kubectl set image deployment/${APP_NAME} \
                            ${APP_NAME}=${REGISTRY}/${APP_NAME}:${BUILD_NUMBER} \
                            -n ${NAMESPACE}
                        kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE}
                    '''
                }
            }
        }

        stage('Smoke Test') {
            steps {
                script {
                    def baseUrl = "http://${APP_NAME}.${NAMESPACE}.svc.cluster.local"
                    sleep(15)
                    sh """
                        curl -f ${baseUrl}/actuator/health
                        curl -f ${baseUrl}/api/users
                    """
                }
            }
        }
    }

    post {
        always {
            cleanWs()
        }
        success {
            emailext(
                subject: "SUCCESS: ${APP_NAME} #${BUILD_NUMBER}",
                body: "Pipeline succeeded. Version: ${BUILD_NUMBER}",
                to: 'team@example.com'
            )
        }
    }
}

面试追问方向

  • Docker-in-Docker 在 K8s 中运行时需要什么特权?有什么安全风险?
  • Kubernetes Plugin 的 Agent Pod 是怎么创建的?构建完成后 Pod 会怎样?
  • 如何在 Jenkins 流水线中实现「手动审批后才能部署到生产」?
  • Jenkins 和 K8s 集成时,ServiceAccount 的 RBAC 权限应该怎么配置?

Jenkins 和 Docker/K8s 的集成,是 Jenkins 从 CI 工具进化为完整 CI/CD 平台的关键。理解这些集成方式,就能构建一套完整的云原生交付流水线。

基于 VitePress 构建