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 或更高版本
2.2 安装 Java
创建安装脚本文件: install_java.sh
bash
#!/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
运行脚本:
bash
chmod +x install_java.sh
./install_java.sh
2.3 安装 Jenkins
创建 Jenkins 安装脚本: install_jenkins.sh
bash
#!/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
运行安装脚本:
bash
chmod +x install_jenkins.sh
./install_jenkins.sh
2.4 初始 Jenkins 配置
访问 http://your-server-ip:8080 完成初始设置:
bash
# 获取初始管理员密码
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
按照向导安装推荐插件并创建管理员账户。
3. Jenkins 系统配置
3.1 安装必要插件
创建插件安装脚本: install_plugins.sh
bash
#!/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 示例应用
创建项目结构:
css
sample-spring-app/
├── src/
│ └── main/
│ └── java/
│ └── com/
│ └── example/
│ └── demo/
│ └── DemoApplication.java
├── pom.xml
├── Dockerfile
├── Jenkinsfile
└── k8s/
└── deployment.yaml
创建 Maven 配置文件: pom.xml
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
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!";
}
}
4.2 创建 Docker 配置
创建 Dockerfile: 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
yaml
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
groovy
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 '生产环境部署完成'
"
"""
}
}
5.2 CI/CD 流程可视化
以下是完整的 CI/CD 流程示意图:
6. 高级配置与优化
6.1 创建共享库
创建共享库结构:
css
jenkins-shared-library/
├── vars/
│ └── deployUtils.groovy
├── src/
│ └── com/
│ └── company/
│ └── BuildUtils.groovy
└── resources/
└── com/
└── company/
└── templates/
└── deployment.yaml
创建共享工具类: vars/deployUtils.groovy
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
bash
#!/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.domains.*
import com.cloudbees.plugins.credentials.impl.*
import com.cloudbees.jenkins.plugins.sshcredentials.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
bash
#!/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
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
bash
#!/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 hudson.security.*
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
bash
#!/bin/bash
echo "=== Jenkins 故障排除工具 ==="
# 检查服务状态
echo "1. 检查 Jenkins 服务状态..."
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
bash
#!/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 环境。