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 管理界面中配置:
- 全局工具配置
- JDK:配置 Java 11 路径
- Git:配置 Git 可执行文件路径
- Maven/Gradle:根据需要配置构建工具
- 凭据配置
- 添加 GitHub SSH 密钥
- 添加 Docker Hub 凭据
- 添加服务器 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. 总结
通过本教程,你已经学会了如何:
- 安装和配置 Jenkins 服务器
- 创建完整的 CI/CD 流水线 使用 Jenkins Pipeline
- 集成代码质量检查 和自动化测试
- 构建和部署 Docker 容器
- 配置多环境部署 策略
- 实现监控和通知 机制
- 加强 Jenkins 安全性
- 设置备份和恢复 流程
这个完整的 Jenkins CI/CD 解决方案可以协助团队实现高效的自动化构建和部署流程,提高软件交付质量和速度。记得根据你的具体需求调整配置,并定期更新和维护你的 Jenkins 环境。



收藏了,感谢分享