CI/CD 实战:用 Jenkins 自动构建和部署你的项目

内容分享12小时前发布
0 1 0

1. 引言

在当今快速迭代的软件开发环境中,持续集成和持续部署(CI/CD)已成为现代 DevOps 实践的核心组成部分。Jenkins 作为最流行的开源自动化服务器,提供了强劲的 CI/CD 功能,能够协助团队自动化构建、测试和部署流程。

本教程将手把手教你搭建完整的 Jenkins CI/CD 流水线,从环境配置到自动化部署,每个步骤都包含详细的代码和配置说明。

2. 环境准备与 Jenkins 安装

2.1 系统要求

  • Ubuntu 20.04 LTS 或 CentOS 8
  • 至少 4GB RAM
  • 20GB 可用磁盘空间
  • Java 11 或更高版本 #Linux #每天一个知识点

Java 2.2 安装 Java

创建安装脚本文件: install_java.sh

#!/bin/bash

# 更新系统包管理器

sudo apt update && sudo apt upgrade -y

# 安装 Java 11

sudo apt install -y openjdk-11-jdk

# 验证安装

java -version javac -version

# 设置 JAVA_HOME 环境变量

echo 'export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64' >> ~/.bashrc echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc source ~/.bashrc

运行脚本:

chmod +x install_java.sh
./install_java.sh

2.3 安装 Jenkins

创建 Jenkins 安装脚本: install_jenkins.sh

#!/bin/bash

# 下载并添加 Jenkins 仓库密钥

curl -fsSL https://pkg.jenkins.io/debian/jenkins.io.key | sudo tee  /usr/share/keyrings/jenkins-keyring.asc > /dev/null

# 添加 Jenkins 仓库

echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]  https://pkg.jenkins.io/debian binary/ | sudo tee  /etc/apt/sources.list.d/jenkins.list > /dev/null

# 更新包列表并安装 Jenkins

sudo apt update sudo apt install -y jenkins

# 启动 Jenkins 服务

sudo systemctl enable jenkins sudo systemctl start jenkins sudo systemctl status jenkins

# 开放 Jenkins 默认端口 8080

sudo ufw allow 8080 sudo ufw status

运行安装脚本:

chmod +x install_jenkins.sh
./install_jenkins.sh

2.4 初始 Jenkins 配置

访问
http://your-server-ip:8080 完成初始设置:

# 获取初始管理员密码

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

按照向导安装推荐插件并创建管理员账户。

3. Jenkins 系统配置

3.1 安装必要插件

创建插件安装脚本: install_plugins.sh

#!/bin/bash

# Jenkins CLI 安装插件

JENKINS_URL="http://localhost:8080" JENKINS_CLI_JAR="/var/lib/jenkins/jenkins-cli.jar"

# 插件列表

PLUGINS=( "git" "github" "pipeline" "docker" "blueocean" "credentials-binding" "ssh-agent" "email-ext" "htmlpublisher" "jacoco" "sonar" "slack" )

# 安装每个插件

for plugin in "${PLUGINS[@]}"; do echo "安装插件: $plugin" java -jar $JENKINS_CLI_JAR -s $JENKINS_URL install-plugin $plugin -deploy done

# 重启 Jenkins 使插件生效

sudo systemctl restart jenkins

3.2 配置系统环境

在 Jenkins 管理界面中配置:

  1. 全局工具配置
  2. JDK:配置 Java 11 路径
  3. Git:配置 Git 可执行文件路径
  4. Maven/Gradle:根据需要配置构建工具
  5. 凭据配置
  6. 添加 GitHub SSH 密钥
  7. 添加 Docker Hub 凭据
  8. 添加服务器 SSH 凭据

4. 示例项目准备

4.1 创建 Spring Boot 示例应用

创建项目结构:

sample-spring-app/
├── src/
│   └── main/
│       └── java/
│           └── com/
│               └── example/
│                   └── demo/
│                       └── DemoApplication.java
├── pom.xml
├── Dockerfile
├── Jenkinsfile
└── k8s/
    └── deployment.yaml

创建 Maven 配置文件: pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>

<artifactId>sample-spring-app</artifactId>

<version>1.0.0</version>

<packaging>jar</packaging>

<name>Sample Spring Boot Application</name>

<description>Demo project for Jenkins CI/CD</description>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.7.0</version>

<relativePath/>

</parent>

<properties>

<java.version>11</java.version>

<maven.compiler.source>11</maven.compiler.source>

<maven.compiler.target>11</maven.compiler.target>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

<plugin>

<groupId>org.jacoco</groupId>

<artifactId>jacoco-maven-plugin</artifactId>

<version>0.8.8</version>

<executions>

<execution>

<goals>

<goal>prepare-agent</goal>

</goals>

</execution>

<execution>

<id>report</id>

<phase>test</phase>

<goals>

<goal>report</goal>

</goals>

</execution>

</executions>

</plugin>

</plugins>

</build>

</project>

创建 Spring Boot 主类:
src/main/java/com/example/demo/DemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication @RestController public class DemoApplication {

public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }

@GetMapping("/") public String home() { return "Hello from Spring Boot CI/CD Demo!"; }

@GetMapping("/health") public String health() { return "Application is healthy!"; } }

C 4.2 创建 Docker 配置

创建 Dockerfile: Dockerfile

# 使用 OpenJDK 11 作为基础镜像

FROM openjdk:11-jre-slim

# 设置工作目录

WORKDIR /app

# 添加 JAR 文件到容器

COPY target/sample-spring-app-1.0.0.jar app.jar

# 创建非 root 用户

RUN groupadd -r spring && useradd -r -g spring spring USER spring

# 暴露端口

EXPOSE 8080

# 设置 JVM 参数

ENV JAVA_OPTS="-Xms256m -Xmx512m -Djava.security.egd=file:/dev/./urandom"

# 启动应用

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

创建 Docker Compose 文件: docker-compose.yml

version: '3.8'

services: spring-app: build: . ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3

nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - spring-app restart: unless-stopped

5. Jenkins Pipeline 配置

5.1 创建 Jenkinsfile

创建 Jenkinsfile: Jenkinsfile

pipeline {
    agent any

    environment {
        // 应用配置
        APP_NAME = 'sample-spring-app'
        APP_VERSION = '1.0.0'

        // 容器注册表配置
        DOCKER_REGISTRY = 'docker.io'
        DOCKER_CREDENTIALS = 'docker-hub-credentials'

        // 服务器配置
        DEPLOY_SERVER = 'deploy-server'
        DEPLOY_PATH = '/opt/apps'

        // SonarQube 配置
        SONAR_HOST_URL = 'http://sonarqube:9000'
        SONAR_CREDENTIALS = 'sonar-token'
    }

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

    parameters {
        choice(
            name: 'DEPLOY_ENV',
            choices: ['dev', 'staging', 'prod'],
            description: '选择部署环境'
        )
        booleanParam(
            name: 'RUN_INTEGRATION_TESTS',
            defaultValue: true,
            description: '是否运行集成测试'
        )
    }

    stages {
        stage('代码检出') {
            steps {
                checkout scm
                script {
                    currentBuild.displayName = "#${BUILD_NUMBER}-${params.DEPLOY_ENV}"
                    currentBuild.description = "分支: ${env.BRANCH_NAME}"
                }
            }
        }

        stage('代码质量检查') {
            steps {
                withSonarQubeEnv('sonarqube') {
                    sh 'mvn clean verify sonar:sonar -Dsonar.projectKey=sample-spring-app'
                }
            }
        }

        stage('编译构建') {
            steps {
                sh """
                    mvn clean compile -DskipTests
                    echo "编译完成"
                """
            }
        }

        stage('单元测试') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                    jacoco(
                        execPattern: 'target/jacoco.exec',
                        classPattern: 'target/classes',
                        sourcePattern: 'src/main/java'
                    )
                }
            }
        }

        stage('集成测试') {
            when {
                expression { params.RUN_INTEGRATION_TESTS }
            }
            steps {
                sh 'mvn verify -Pintegration-tests'
            }
            post {
                always {
                    junit 'target/failsafe-reports/*.xml'
                }
            }
        }

        stage('代码质量门禁') {
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }

        stage('构建 Docker 镜像') {
            steps {
                script {
                    docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${APP_VERSION}-${env.BUILD_NUMBER}")
                }
            }
        }

        stage('安全扫描') {
            steps {
                sh """
                    docker scan --file Dockerfile ${DOCKER_REGISTRY}/${APP_NAME}:${APP_VERSION}-${env.BUILD_NUMBER}
                """
            }
        }

        stage('推送镜像') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", DOCKER_CREDENTIALS) {
                        docker.image("${DOCKER_REGISTRY}/${APP_NAME}:${APP_VERSION}-${env.BUILD_NUMBER}").push()
                    }
                }
            }
        }

        stage('部署到环境') {
            when {
                expression { params.DEPLOY_ENV != 'none' }
            }
            steps {
                script {
                    deployToEnvironment(params.DEPLOY_ENV)
                }
            }
        }
    }

    post {
        always {
            emailext (
                subject: "构建通知: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: """
                    <h2>构建信息</h2>

<p><b>项目:</b> ${env.JOB_NAME}</p>

<p><b>构建号:</b> ${env.BUILD_NUMBER}</p>

<p><b>状态:</b> ${currentBuild.result ?: 'SUCCESS'}</p>

<p><b>持续时间:</b> ${currentBuild.durationString}</p>

<p><b>部署环境:</b> ${params.DEPLOY_ENV}</p>

<p><a href="${env.BUILD_URL}">查看构建详情</a></p>

""", to: "dev-team@company.com", attachLog: currentBuild.result != 'SUCCESS' ) cleanWs() } success { slackSend( channel: '#build-notifications', message: "✅ 构建成功: ${env.JOB_NAME} #${env.BUILD_NUMBER}", color: 'good' ) } failure { slackSend( channel: '#build-notifications', message: "❌ 构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}", color: 'danger' ) } unstable { slackSend( channel: '#build-notifications', message: "⚠️ 构建不稳定: ${env.JOB_NAME} #${env.BUILD_NUMBER}", color: 'warning' ) } } }

// 部署到不同环境的方法 def deployToEnvironment(env) { switch(env) { case 'dev': deployDev() break case 'staging': deployStaging() break case 'prod': deployProd() break default: error "未知环境: ${env}" } }

def deployDev() { sshagent([DEPLOY_SERVER]) { sh """ ssh -o StrictHostKeyChecking=no deploy@dev-server " cd ${DEPLOY_PATH} && docker-compose pull && docker-compose up -d && sleep 10 && echo '开发环境部署完成' " """ } }

def deployStaging() { sshagent([DEPLOY_SERVER]) { sh """ ssh -o StrictHostKeyChecking=no deploy@staging-server " cd ${DEPLOY_PATH} && docker-compose pull && docker-compose up -d && sleep 10 && echo '预发布环境部署完成' " """ } }

def deployProd() { sshagent([DEPLOY_SERVER]) { sh """ ssh -o StrictHostKeyChecking=no deploy@prod-server " cd ${DEPLOY_PATH} && docker-compose pull && docker-compose up -d && sleep 10 && echo '生产环境部署完成' " """ } }

C 5.2 CI/CD 流程可视化

以下是完整的 CI/CD 流程示意图:

6. 高级配置与优化

6.1 创建共享库

创建共享库结构:

jenkins-shared-library/
├── vars/
│   └── deployUtils.groovy
├── src/
│   └── com/
│       └── company/
│           └── BuildUtils.groovy
└── resources/
    └── com/
        └── company/
            └── templates/
                └── deployment.yaml

创建共享工具类: vars/deployUtils.groovy

#!/usr/bin/groovy

def call(Map config = [:]) { def defaults = [ timeout: 30, retries: 3, healthCheckUrl: '', rollbackOnFailure: true ] config = defaults + config pipeline { agent any stages { stage('预部署检查') { steps { script { checkPrerequisites() validateConfig(config) } } } stage('部署') { steps { retry(config.retries) { timeout(time: config.timeout, unit: 'MINUTES') { script { performDeployment(config) } } } } } stage('健康检查') { steps { script { healthCheck(config.healthCheckUrl) } } } } post { failure { script { if (config.rollbackOnFailure) { rollbackDeployment() } notifyFailure() } } success { notifySuccess() } } } }

def checkPrerequisites() { // 检查部署前提条件 sh 'echo "检查部署前提条件..."' }

def validateConfig(config) { // 验证配置 if (!config.healthCheckUrl) { error "健康检查 URL 不能为空" } }

def performDeployment(config) { // 执行部署逻辑 sh 'echo "执行部署..."' }

def healthCheck(url) { // 健康检查 sh """ echo "执行健康检查: ${url}" curl -f ${url} || exit 1 """ }

def rollbackDeployment() { // 回滚部署 sh 'echo "执行回滚..."' }

def notifyFailure() { // 失败通知 emailext( subject: "部署失败: ${env.JOB_NAME}", body: "部署失败,请检查日志。", to: "devops@company.com" ) }

def notifySuccess() { // 成功通知 emailext( subject: "部署成功: ${env.JOB_NAME}", body: "部署成功完成。", to: "devops@company.com" ) }

6.2 配置 Jenkins 凭据

创建凭据配置脚本: configure_credentials.sh

#!/bin/bash

# 配置 Docker Hub 凭据

java -jar /var/lib/jenkins/jenkins-cli.jar -s http://localhost:8080 groovy = <<EOF import com.cloudbees.plugins.credentials.*

import com.cloudbees.plugins.credentials.impl.*

import org.jenkinsci.plugins.plaincredentials.impl.* import hudson.util.Secret

domain = Domain.global() store = Jenkins.instance.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()

// Docker Hub 用户名密码凭据 dockerHubCredentials = new UsernamePasswordCredentialsImpl( CredentialsScope.GLOBAL, "docker-hub-credentials", "Docker Hub Credentials", "your-docker-username", "your-docker-password" )

// SSH 部署密钥 sshCredentials = new BasicSSHUserPrivateKey( CredentialsScope.GLOBAL, "deploy-ssh-key", "deploy-user", new BasicSSHUserPrivateKey.UsersPrivateKeySource(), "", "Deployment SSH Key" )

// 添加凭据到存储 store.addCredentials(domain, dockerHubCredentials) store.addCredentials(domain, sshCredentials)

println "凭据配置完成" EOF

7. 监控与日志

7.1 配置构建监控

创建监控仪表板配置: configure_monitoring.sh

#!/bin/bash

# 安装监控插件

java -jar /var/lib/jenkins/jenkins-cli.jar -s http://localhost:8080 install-plugin monitoring -deploy java -jar /var/lib/jenkins/jenkins-cli.jar -s http://localhost:8080 install-plugin build-monitor-plugin -deploy

# 重启 Jenkins

sudo systemctl restart jenkins

# 配置构建历史清理

cat > /var/lib/jenkins/job-config-history.xml << EOF <org.jenkinsci.plugins.jobConfigHistory.JobConfigHistory>

<maxHistory>30</maxHistory>

<saveModuleConfiguration>true</saveModuleConfiguration>

<skipDuplicateHistory>true</skipDuplicateHistory>

</org.jenkinsci.plugins.jobConfigHistory.JobConfigHistory>

EOF

7.2 日志配置

创建日志配置: logging_config.groovy

import java.util.logging.ConsoleHandler
import java.util.logging.FileHandler
import java.util.logging.SimpleFormatter

// 配置 Jenkins 日志 def logger = java.util.logging.Logger.getLogger("")

// 清除现有处理器 logger.handlers.each { logger.removeHandler(it) }

// 控制台处理器 def consoleHandler = new ConsoleHandler() consoleHandler.level = java.util.logging.Level.INFO logger.addHandler(consoleHandler)

// 文件处理器 def fileHandler = new FileHandler("/var/log/jenkins/jenkins.log", 10485760, 10, true) fileHandler.level = java.util.logging.Level.ALL fileHandler.formatter = new SimpleFormatter() logger.addHandler(fileHandler)

// 设置日志级别 logger.level = java.util.logging.Level.ALL

8. 安全配置

8.1 Jenkins 安全加固

创建安全配置脚本: security_hardening.sh

#!/bin/bash

# 备份原始配置

cp /var/lib/jenkins/config.xml /var/lib/jenkins/config.xml.backup

# 配置安全性设置

cat > /var/lib/jenkins/security.groovy << EOF import jenkins.model.*

import jenkins.security.s2m.AdminWhitelistRule

def instance = Jenkins.getInstance()

// 启用安全性 def strategy = new GlobalMatrixAuthorizationStrategy()

// 添加权限 strategy.add(Jenkins.ADMINISTER, "admin") strategy.add(Jenkins.READ, "authenticated") strategy.add(hudson.model.Item.READ, "authenticated") strategy.add(hudson.model.Item.BUILD, "authenticated") strategy.add(hudson.model.Item.CONFIGURE, "admin")

instance.setAuthorizationStrategy(strategy)

// 配置安全领域 def realm = new HudsonPrivateSecurityRealm(false) realm.createAccount("admin", "secure-password-here") instance.setSecurityRealm(realm)

// 禁用 Jenkins CLI 远程访问 instance.getDescriptor("jenkins.CLI").get().setEnabled(false)

// 保存配置 instance.save() EOF

# 执行安全配置

java -jar /var/lib/jenkins/jenkins-cli.jar -s http://localhost:8080 groovy security.groovy

# 清理

rm /var/lib/jenkins/security.groovy

echo "Jenkins 安全配置完成"

9. 故障排除与维护

9.1 常见问题解决

创建故障排除脚本: troubleshooting.sh

#!/bin/bash

echo "=== Jenkins 故障排除工具 ==="

# 检查服务状态

echo "1.

sudo systemctl status jenkins --no-pager -l

# 检查磁盘空间

echo "2.

df -h /var/lib/jenkins

# 检查内存使用

echo "3.

free -h

# 检查日志错误

echo "4.

tail -100 /var/log/jenkins/jenkins.log | grep -i error

# 检查构建队列

echo "5.

java -jar /var/lib/jenkins/jenkins-cli.jar -s http://localhost:8080 list-queue

# 检查插件更新

echo "6.

java -jar /var/lib/jenkins/jenkins-cli.jar -s http://localhost:8080 list-plugins | grep -i available

echo "故障排除检查完成"

9.2 备份与恢复

创建备份脚本: backup_jenkins.sh

#!/bin/bash

BACKUP_DIR="/opt/jenkins-backups" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="jenkins_backup_${TIMESTAMP}.tar.gz"

echo "开始备份 Jenkins..."

# 创建备份目录

mkdir -p $BACKUP_DIR

# 停止 Jenkins 服务

sudo systemctl stop jenkins

# 创建备份

sudo tar -czf $BACKUP_DIR/$BACKUP_FILE  --exclude='./war'  --exclude='./cache'  --exclude='./logs'  -C /var/lib jenkins

# 启动 Jenkins 服务

sudo systemctl start jenkins

# 验证备份

if [ -f "$BACKUP_DIR/$BACKUP_FILE" ]; then echo "备份成功: $BACKUP_DIR/$BACKUP_FILE" echo "备份大小: $(du -h $BACKUP_DIR/$BACKUP_FILE | cut -f1)" else echo "备份失败!" exit 1 fi

# 清理旧备份(保留最近7天)

find $BACKUP_DIR -name "jenkins_backup_*.tar.gz" -mtime +7 -delete

echo "备份完成"

10. 总结

通过本教程,你已经学会了如何:

  1. 安装和配置 Jenkins 服务器
  2. 创建完整的 CI/CD 流水线 使用 Jenkins Pipeline
  3. 集成代码质量检查 和自动化测试
  4. 构建和部署 Docker 容器
  5. 配置多环境部署 策略
  6. 实现监控和通知 机制
  7. 加强 Jenkins 安全性
  8. 设置备份和恢复 流程

这个完整的 Jenkins CI/CD 解决方案可以协助团队实现高效的自动化构建和部署流程,提高软件交付质量和速度。记得根据你的具体需求调整配置,并定期更新和维护你的 Jenkins 环境。

© 版权声明

相关文章

1 条评论

  • 头像
    缪斯不迷路 读者

    收藏了,感谢分享

    无记录
    回复