01-Jenkins_架构与核心原理
Jenkins采用经典的Master-Agent分布式架构设计,支持大规模构建任务的并行执行。整体架构遵循"控制器-执行器"模式,实现了任务调度与实际执行的解耦。外部系统Agent集群Jenkins Master (控制器)调度层安全层认证管理器授权策略安全域存储层JENKINS_HOMEconfig.xmljobs/plugins/workspace/核心引擎Web服务器Winstone/Jet
Jenkins架构与核心原理:深入理解CI/CD基础设施
1. Jenkins整体架构设计
1.1 架构概览
Jenkins采用经典的Master-Agent分布式架构设计,支持大规模构建任务的并行执行。整体架构遵循"控制器-执行器"模式,实现了任务调度与实际执行的解耦。
1.2 Master-Agent通信协议
Jenkins Master与Agent之间支持多种通信协议,每种协议适用于不同的网络环境:
协议对比分析:
| 协议 | 连接方向 | 适用场景 | 安全性 | 穿透性 |
|---|---|---|---|---|
| SSH | Master→Agent | Agent在公网、有SSH服务 | 高(密钥认证) | 需开放SSH端口 |
| JNLP | Agent→Master | Agent在内网、防火墙后 | 中(密钥认证) | 仅需出站HTTP |
| WebSocket | Agent→Master | 网络受限环境 | 高(TLS加密) | 穿透NAT/防火墙 |
1.3 核心设计原则
Jenkins架构设计遵循以下核心原则:
/**
* Jenkins核心设计原则体现
*
* 1. 可扩展性:所有核心功能通过ExtensionPoint实现
* 2. 持久化:基于文件系统的轻量级存储
* 3. 分布式:Master-Agent分离,支持横向扩展
* 4. 安全性:多层安全架构,细粒度权限控制
*/
// 扩展点示例:自定义构建触发器
public class CustomTrigger extends Trigger<Job<?,?>> {
/**
* 通过ExtensionPoint实现功能扩展
* Jenkins启动时自动扫描并注册
*/
@Override
public void start(Job<?,?> job, boolean newInstance) {
super.start(job, newInstance);
// 触发器初始化逻辑
}
@Override
public void run() {
// 触发构建执行
if (shouldTrigger()) {
job.scheduleBuild(new Cause.UserIdCause());
}
}
private boolean shouldTrigger() {
// 自定义触发条件判断
return true;
}
}
2. 核心组件深度解析
2.1 Jenkins核心引擎
Jenkins核心引擎基于Java Servlet规范构建,采用Standalone模式运行,内置Winstone(后迁移至Jetty)作为Web容器。
核心类职责说明:
| 类名 | 职责 | 生命周期 |
|---|---|---|
Jenkins |
应用程序入口,管理全局状态 | 单例,随JVM生命周期 |
ExtensionComponent |
扩展点包装器,支持排序 | 随扩展注册创建 |
Descriptor |
描述符模式,管理配置元数据 | 单例,随Jenkins初始化 |
Queue |
构建队列,管理任务调度 | 单例,随Jenkins初始化 |
2.2 插件系统架构
Jenkins插件系统是其核心优势之一,通过ExtensionPoint机制实现了高度可扩展性。
插件描述文件结构:
<!--
文件路径: META-INF/maven/org.jenkins-ci.plugins/plugin-name/plugin.xml
描述: 插件元数据定义,Jenkins启动时解析
-->
<plugin>
<!-- 插件基本信息 -->
<name>Git Plugin</name>
<version>5.0.0</version>
<description>Git SCM integration for Jenkins</description>
<!-- 依赖声明 -->
<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>3.0.0</version>
<optional>false</optional>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
<!-- 扩展点声明 -->
<extensions>
<extension>
<impl>org.jenkinsci.plugins.git.GitSCM</impl>
<ordinal>100</ordinal>
</extension>
</extensions>
</plugin>
2.3 插件类加载机制
/**
* Jenkins插件类加载器实现原理
*
* 每个插件拥有独立的ClassLoader,实现类隔离
* 避免不同插件间的类冲突
*/
public class PluginClassLoader extends URLClassLoader {
private final PluginWrapper plugin;
private final ClassLoader parent;
private final Set<String> parentFirstPackages;
private final Set<String> pluginFirstPackages;
/**
* 类加载优先级策略
*
* 1. java.* 委托给父加载器(安全要求)
* 2. parentFirstPackages 中的包委托给父加载器
* 3. pluginFirstPackages 中的包优先从插件加载
* 4. 其他类先尝试插件加载,失败后委托父加载器
*/
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 安全检查:禁止加载某些敏感类
checkPackageAccess(name);
// 已加载类缓存检查
Class<?> loaded = findLoadedClass(name);
if (loaded != null) {
return loaded;
}
// 核心类委托给父加载器
if (name.startsWith("java.") || isParentFirst(name)) {
return parent.loadClass(name);
}
// 插件优先加载
if (isPluginFirst(name)) {
try {
return findClass(name);
} catch (ClassNotFoundException e) {
return parent.loadClass(name);
}
}
// 默认:先父加载器,后插件
try {
return parent.loadClass(name);
} catch (ClassNotFoundException e) {
return findClass(name);
}
}
private boolean isParentFirst(String className) {
return parentFirstPackages.stream()
.anyMatch(pkg -> className.startsWith(pkg));
}
private boolean isPluginFirst(String className) {
return pluginFirstPackages.stream()
.anyMatch(pkg -> className.startsWith(pkg));
}
}
3. 任务调度与执行模型
3.1 构建队列机制
Jenkins构建队列是任务调度的核心组件,负责管理待执行任务的排队、分配和执行。
队列数据结构实现:
/**
* Jenkins Queue核心数据结构
*
* 维护多个队列实现不同优先级和状态管理
*/
public class Queue {
// 待处理任务队列(按优先级排序)
private final SortedList<WaitingItem> waitingList = new SortedList<>();
// 阻塞任务队列(等待资源/依赖)
private final List<BlockedItem> blockedList = new ArrayList<>();
// 可执行任务队列(等待Executor)
private final List<BuildableItem> buildables = new ArrayList<>();
// 待分配任务(已选择Executor)
private final Map<Executor, BuildableItem> pending = new HashMap<>();
/**
* 任务调度主循环
*
* 由Queue.Maintainer线程定期执行
* 处理状态转换和资源分配
*/
@SuppressWarnings("deprecation")
public void maintain() {
// 1. 清理已完成/取消的任务
cleanupCompletedItems();
// 2. 检查blocked任务是否可解除阻塞
for (BlockedItem item : new ArrayList<>(blockedList)) {
if (canRun(item)) {
blockedList.remove(item);
buildables.add(item.toBuildable());
}
}
// 3. 检查waiting任务是否可进入buildable
for (WaitingItem item : new ArrayList<>(waitingList)) {
if (isReady(item)) {
waitingList.remove(item);
if (hasBlocker(item)) {
blockedList.add(new BlockedItem(item));
} else {
buildables.add(new BuildableItem(item));
}
}
}
// 4. 为buildable任务分配Executor
for (BuildableItem item : new ArrayList<>(buildables)) {
Executor executor = findAvailableExecutor(item);
if (executor != null) {
buildables.remove(item);
pending.put(executor, item);
executor.start(item);
}
}
}
/**
* 查找可用Executor
*
* 考虑Label约束、负载均衡策略
*/
private Executor findAvailableExecutor(BuildableItem item) {
Label label = item.getAssignedLabel();
// 获取满足Label约束的Computer列表
List<Computer> computers = getComputers(label);
// 负载均衡:选择负载最低的Computer
Computer selected = computers.stream()
.filter(c -> c.isOnline() && c.isAcceptingTasks())
.min(Comparator.comparingInt(Computer::countBusy))
.orElse(null);
return selected != null ? selected.getIdleExecutor() : null;
}
}
3.2 Executor工作原理
Executor状态管理:
/**
* Executor状态机实现
*
* 管理单个执行线程的生命周期
*/
public class Executor extends Thread {
private volatile State state = State.IDLE;
private Run<?, ?> currentBuild;
private long startTime;
private long estimatedDuration;
/**
* Executor状态枚举
*/
public enum State {
IDLE, // 空闲,可接受任务
PENDING, // 等待任务启动
RUNNING, // 正在执行任务
COMPLETING, // 任务完成中
TERMINATING // 正在终止
}
/**
* 执行主循环
*/
@Override
public void run() {
while (!isTerminated()) {
try {
// 等待任务分配
synchronized (this) {
while (currentBuild == null && !isTerminated()) {
wait();
}
}
if (isTerminated()) break;
// 执行构建
state = State.RUNNING;
startTime = System.currentTimeMillis();
try {
currentBuild.run();
} catch (Throwable t) {
// 记录构建失败
currentBuild.setResult(Result.FAILURE);
} finally {
// 清理资源
state = State.COMPLETING;
cleanup();
Run<?, ?> completed = currentBuild;
currentBuild = null;
state = State.IDLE;
// 通知Queue任务完成
owner.getQueue().onCompleted(completed);
}
} catch (InterruptedException e) {
// 响应中断
if (currentBuild != null) {
currentBuild.getExecutor().interrupt();
}
}
}
}
/**
* 中止当前构建
*/
public void interrupt(Result result) {
if (currentBuild != null) {
currentBuild.setResult(result);
currentBuild.getExecution().stop();
}
super.interrupt();
}
}
3.3 任务调度策略
优先级计算实现:
/**
* 任务优先级计算器
*
* 综合多个因素计算任务执行优先级
*/
public class PriorityCalculator {
/**
* 计算任务优先级
*
* @param item 待计算的任务项
* @return 优先级值(数值越大优先级越高)
*/
public int calculatePriority(Queue.Item item) {
int priority = 0;
// 1. 基础优先级(来自Job配置)
priority += getJobPriority(item);
// 2. 等待时间补偿(防止饥饿)
long waitTime = System.currentTimeMillis() - item.getInQueueSince();
priority += (int) (waitTime / 60000); // 每分钟+1
// 3. 用户优先级(管理员可手动调整)
priority += getUserPriority(item);
// 4. 任务类型权重
priority += getTypeWeight(item);
return priority;
}
private int getJobPriority(Queue.Item item) {
// 从Job配置中读取优先级
Job<?, ?> job = item.task instanceof Job ? (Job<?, ?>) item.task : null;
if (job != null && job.getProperty(PriorityJobProperty.class) != null) {
return job.getProperty(PriorityJobProperty.class).getPriority();
}
return 0;
}
private int getTypeWeight(Queue.Item item) {
// 不同类型任务的权重
if (item.task instanceof FreeStyleProject) return 10;
if (item.task instanceof WorkflowJob) return 15;
if (item.task instanceof MatrixProject) return 5;
return 0;
}
}
4. 数据持久化机制
4.1 JENKINS_HOME目录结构
4.2 配置文件格式与解析
<!--
文件: JENKINS_HOME/config.xml
描述: Jenkins全局配置文件
采用XStream序列化格式,支持版本迁移
-->
<hudson>
<!-- 版本号,用于配置迁移 -->
<version>2.400</version>
<!-- 安全配置 -->
<securityRealm class="hudson.security.HudsonPrivateSecurityRealm">
<disableSignup>true</disableSignup>
<enableCaptcha>false</enableCaptcha>
</securityRealm>
<authorizationStrategy class="hudson.security.RoleBasedAuthorizationStrategy">
<!-- 角色权限配置 -->
</authorizationStrategy>
<!-- 全局工具配置 -->
<jdk>
<name>JDK 11</name>
<home>/usr/lib/jvm/java-11-openjdk</home>
</jdk>
<maven>
<name>Maven 3.8</name>
<home>/usr/share/maven</home>
</maven>
<!-- 全局环境变量 -->
<globalNodeProperties>
<hudson.slaves.EnvironmentVariablesNodeProperty>
<envVars serialization="custom">
<unserializable-parents/>
<tree-map>
<entry>
<string>JAVA_HOME</string>
<string>/usr/lib/jvm/java-11</string>
</entry>
</tree-map>
</envVars>
</hudson.slaves.EnvironmentVariablesNodeProperty>
</globalNodeProperties>
<!-- Agent配置 -->
<slaves>
<hudson.slaves.DumbSlave>
<name>agent-01</name>
<remoteFS>/home/jenkins/agent</remoteFS>
<numExecutors>2</numExecutors>
<mode>NORMAL</mode>
<retentionStrategy class="hudson.slaves.RetentionStrategy$Always"/>
<launcher class="hudson.slaves.JNLPLauncher">
<workDirSettings>
<disabled>false</disabled>
<internalDir>remoting</internalDir>
</workDirSettings>
</launcher>
<label>linux docker</label>
</hudson.slaves.DumbSlave>
</slaves>
</hudson>
4.3 构建历史存储
/**
* 构建历史存储机制
*
* 每次构建生成独立的目录存储相关数据
*/
public class RunMap<S extends Job<S,R>, R extends Run<S,R>>
extends AbstractMap<Integer, R> {
// 构建目录命名格式: builds/2024-01-15_10-30-45/
private static final DateFormat DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
/**
* 构建目录结构
*
* builds/
* ├── 1/ # 构建编号
* │ ├── build.xml # 构建元数据
* │ ├── log # 构建日志
* │ ├── changelog.xml # 变更记录
* │ └── archive/ # 归档产物
* ├── 2/
* ├── 3/
* └── permutations/ # Matrix构建子任务
*/
/**
* 加载构建历史
*
* 懒加载策略:仅加载元数据,日志按需读取
*/
public synchronized R loadBuild(File buildDir) throws IOException {
// 读取build.xml获取构建元数据
File buildXml = new File(buildDir, "build.xml");
if (!buildXml.exists()) {
return null;
}
// 使用XStream反序列化
XStream xs = new XStream();
R run = (R) xs.fromXML(buildXml);
// 设置构建目录引用
run.setBuildDir(buildDir);
return run;
}
/**
* 构建日志存储
*
* 采用流式写入,支持实时查看
*/
public static class BuildLog {
private final File logFile;
private RandomAccessFile writer;
/**
* 写入日志行
*
* 追加模式,支持并发读取
*/
public synchronized void writeLine(String line) throws IOException {
if (writer == null) {
writer = new RandomAccessFile(logFile, "rw");
writer.seek(writer.length());
}
writer.writeBytes(line + "\n");
}
/**
* 读取日志范围
*
* 支持分页读取,避免内存溢出
*/
public List<String> readLines(long offset, int count) throws IOException {
List<String> lines = new ArrayList<>();
try (RandomAccessFile reader = new RandomAccessFile(logFile, "r")) {
reader.seek(offset);
for (int i = 0; i < count; i++) {
String line = reader.readLine();
if (line == null) break;
lines.add(line);
}
}
return lines;
}
}
}
4.4 敏感数据加密存储
凭证加密实现:
/**
* Jenkins凭证加密机制
*
* 使用多层加密保护敏感数据
*/
public class Secret {
private final byte[] iv; // 初始化向量
private final byte[] ciphertext; // 密文
/**
* 加密明文
*
* 使用AES-128-CBC加密算法
*/
public static Secret encrypt(String plaintext) {
try {
// 获取主密钥
ConfidentialKey key = ConfidentialStore.get().getKey();
// 生成随机IV
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
// AES加密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(key.getPayload(), "AES"),
new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return new Secret(iv, encrypted);
} catch (Exception e) {
throw new RuntimeException("Encryption failed", e);
}
}
/**
* 解密密文
*/
public String decrypt() {
try {
ConfidentialKey key = ConfidentialStore.get().getKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE,
new SecretKeySpec(key.getPayload(), "AES"),
new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Decryption failed", e);
}
}
/**
* 序列化格式
*
* 输出格式: {BASE64_IV}{BASE64_CIPHERTEXT}
*/
@Override
public String toString() {
return "{" + Base64.getEncoder().encodeToString(iv) + "}" +
Base64.getEncoder().encodeToString(ciphertext);
}
}
5. 扩展点体系
5.1 ExtensionPoint机制
Jenkins扩展点(ExtensionPoint)是实现插件扩展的核心机制,所有可扩展功能都通过定义ExtensionPoint接口实现。
5.2 常用扩展点列表
| 扩展点 | 用途 | 典型实现 |
|---|---|---|
Builder |
构建步骤 | Shell, Ant, Maven, Gradle |
SCM |
版本控制 | Git, Subversion, Mercurial |
Trigger |
构建触发 | TimerTrigger, GitHubPushTrigger |
Publisher |
构建后操作 | JUnitResultArchiver, Mailer |
SecurityRealm |
认证方式 | LDAP, ActiveDirectory, OAuth |
AuthorizationStrategy |
授权策略 | RoleBased, ProjectMatrix |
LabelFinder |
Agent标签 | 动态标签分配 |
QueueSorter |
队列排序 | 优先级调度 |
ToolInstallation |
工具配置 | JDK, Maven, Gradle |
BuildWrapper |
构建包装 | 环境变量, 超时控制 |
5.3 自定义扩展点实现
/**
* 自定义Builder扩展点示例
*
* 实现一个执行Docker命令的构建步骤
*/
public class DockerBuilder extends Builder implements SimpleBuildStep {
private final String imageName;
private final String command;
private final boolean forcePull;
/**
* 构造函数(由Descriptor.newInstance调用)
*
* @param imageName Docker镜像名称
* @param command 容器内执行的命令
* @param forcePull 是否强制拉取最新镜像
*/
@DataBoundConstructor
public DockerBuilder(String imageName, String command, boolean forcePull) {
this.imageName = imageName;
this.command = command;
this.forcePull = forcePull;
}
/**
* 构建执行入口
*
* @param build 构建上下文
* @param ws 工作空间
* @param launcher 命令执行器
* @param listener 日志输出
*/
@Override
public void perform(Run<?, ?> build, FilePath ws, Launcher launcher,
TaskListener listener) throws InterruptedException, IOException {
PrintWriter logger = listener.getLogger();
logger.println("=== Docker Build Step ===");
logger.println("Image: " + imageName);
logger.println("Command: " + command);
// 构建Docker命令
ArgumentListBuilder args = new ArgumentListBuilder();
args.add("docker");
if (forcePull) {
// 先拉取镜像
launcher.launch()
.cmds("docker", "pull", imageName)
.stdout(logger)
.join();
}
// 执行Docker run命令
args.add("run", "--rm");
args.add("-v", ws.getRemote() + ":/workspace");
args.add("-w", "/workspace");
args.add(imageName);
args.addTokenized(command);
// 执行并获取结果
int exitCode = launcher.launch()
.cmds(args)
.stdout(logger)
.pwd(ws)
.join();
// 设置构建结果
if (exitCode != 0) {
build.setResult(Result.FAILURE);
logger.println("Docker command failed with exit code: " + exitCode);
} else {
logger.println("Docker command completed successfully");
}
}
// Getter方法(用于Jelly视图渲染)
public String getImageName() { return imageName; }
public String getCommand() { return command; }
public boolean isForcePull() { return forcePull; }
/**
* 描述符类
*
* 管理配置元数据和表单验证
*/
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
/**
* 显示名称(在UI中显示)
*/
@Override
public String getDisplayName() {
return "Execute Docker container";
}
/**
* 适用性检查
*
* @param jobType 任务类型
* @return 是否适用于该任务类型
*/
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true; // 适用于所有项目类型
}
/**
* 表单验证:镜像名称
*/
@FormValidator
public FormValidation doCheckImageName(@QueryParameter String value) {
if (value == null || value.isEmpty()) {
return FormValidation.error("Image name is required");
}
// 验证镜像名称格式
if (!value.matches("^[a-zA-Z0-9][a-zA-Z0-9._-]*(/[a-zA-Z0-9][a-zA-Z0-9._-]*)?(:[a-zA-Z0-9._-]+)?$")) {
return FormValidation.warning("Image name format may be invalid");
}
return FormValidation.ok();
}
/**
* 表单自动完成
*/
public ListBoxModel doFillImageNameItems() {
ListBoxModel items = new ListBoxModel();
items.add("Select an image", "");
items.add("nginx:latest", "nginx:latest");
items.add("node:18", "node:18");
items.add("python:3.11", "python:3.11");
return items;
}
}
}
5.4 扩展点注册与发现
扩展点注册代码:
/**
* ExtensionList实现原理
*
* 管理特定类型扩展点的所有实现
*/
public class ExtensionList<T> extends CopyOnWriteArrayList<ExtensionComponent<T>> {
private final Class<T> extensionType;
private final Jenkins jenkins;
/**
* 获取扩展实例列表
*
* 返回按ordinal排序的扩展实例
*/
public List<T> getInstances() {
return this.stream()
.sorted(Comparator.comparingDouble(ExtensionComponent::ordinal).reversed())
.map(ExtensionComponent::getInstance)
.collect(Collectors.toList());
}
/**
* 动态注册扩展
*
* 用于运行时添加扩展(如动态加载插件)
*/
public void add(ExtensionComponent<T> component) {
// 检查是否已存在
for (ExtensionComponent<T> existing : this) {
if (existing.getInstance().getClass() == component.getInstance().getClass()) {
return; // 已存在,跳过
}
}
super.add(component);
// 触发扩展添加事件
fireOnAdded(component.getInstance());
}
/**
* 查找特定扩展
*/
public <U extends T> U get(Class<U> type) {
for (ExtensionComponent<T> component : this) {
if (type.isInstance(component.getInstance())) {
return type.cast(component.getInstance());
}
}
return null;
}
}
6. 安全架构设计
6.1 安全框架概览
Jenkins安全框架基于多层防护设计,涵盖认证、授权、加密和审计等维度。
6.2 认证机制
/**
* Jenkins认证流程实现
*
* 支持多种认证方式的统一抽象
*/
public abstract class SecurityRealm implements ExtensionPoint {
/**
* 用户认证入口
*
* @param username 用户名
* @param password 密码
* @return 认证成功返回Authentication对象
* @throws AuthenticationException 认证失败
*/
public abstract Authentication authenticate(String username, String password)
throws AuthenticationException;
/**
* 加载用户详情
*
* 用于权限检查和用户信息展示
*/
public abstract UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException;
/**
* 默认本地认证实现
*/
public static class HudsonPrivateSecurityRealm extends SecurityRealm {
private final Map<String, User> users = new ConcurrentHashMap<>();
private final PasswordEncoder encoder = new BCryptPasswordEncoder();
@Override
public Authentication authenticate(String username, String password) {
User user = users.get(username);
if (user == null) {
throw new BadCredentialsException("User not found: " + username);
}
if (!encoder.matches(password, user.getPasswordHash())) {
throw new BadCredentialsException("Invalid password");
}
// 创建认证令牌
return new UsernamePasswordAuthenticationToken(
username,
null,
user.getAuthorities()
);
}
@Override
public UserDetails loadUserByUsername(String username) {
User user = users.get(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(
username,
user.getPasswordHash(),
user.getAuthorities()
);
}
}
}
6.3 授权策略
权限矩阵实现:
/**
* 矩阵授权策略实现
*
* 支持全局和项目级别的细粒度权限控制
*/
public class GlobalMatrixAuthorizationStrategy extends AuthorizationStrategy {
// 权限矩阵: SID -> Permission -> Boolean
private final Map<Permission, Map<String, Boolean>> grantedPermissions
= new ConcurrentHashMap<>();
/**
* 权限检查
*
* @param a 用户身份
* @param permission 请求的权限
* @param resource 资源对象
*/
@Override
public boolean hasPermission(Authentication a, Permission permission,
Resource resource) {
// 获取用户的所有SID(用户名+所属组)
Set<String> sids = getSids(a);
// 检查权限继承链
Permission p = permission;
while (p != null) {
if (hasExplicitPermission(sids, p)) {
return true;
}
p = p.impliedBy; // 检查父权限
}
return false;
}
/**
* 检查显式权限
*/
private boolean hasExplicitPermission(Set<String> sids, Permission permission) {
Map<String, Boolean> acl = grantedPermissions.get(permission);
if (acl == null) {
return false;
}
for (String sid : sids) {
Boolean granted = acl.get(sid);
if (Boolean.TRUE.equals(granted)) {
return true;
}
}
return false;
}
/**
* 获取用户的所有安全标识符
*/
private Set<String> getSids(Authentication a) {
Set<String> sids = new HashSet<>();
// 添加用户名
sids.add(a.getName());
// 添加所属组
for (GrantedAuthority auth : a.getAuthorities()) {
sids.add(auth.getAuthority());
}
return sids;
}
/**
* 授予权限
*/
public void grant(Permission permission, String sid) {
grantedPermissions
.computeIfAbsent(permission, k -> new ConcurrentHashMap<>())
.put(sid, Boolean.TRUE);
}
/**
* 撤销权限
*/
public void revoke(Permission permission, String sid) {
Map<String, Boolean> acl = grantedPermissions.get(permission);
if (acl != null) {
acl.remove(sid);
}
}
}
6.4 CSRF防护
/**
* CSRF防护机制
*
* 使用Crumb令牌验证请求来源
*/
public class CrumbIssuer implements ExtensionPoint {
private final String crumbSalt = UUID.randomUUID().toString();
/**
* 生成Crumb令牌
*
* 基于会话ID和盐值生成唯一令牌
*/
public String issueCrumb(HttpServletRequest request) {
String sessionId = request.getSession().getId();
String raw = sessionId + ":" + crumbSalt;
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(raw.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 验证Crumb令牌
*
* @param request HTTP请求
* @return 验证是否通过
*/
public boolean validateCrumb(HttpServletRequest request, String crumb) {
String expected = issueCrumb(request);
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
crumb.getBytes(StandardCharsets.UTF_8)
);
}
/**
* 请求过滤器
*
* 拦截所有POST/PUT/DELETE请求进行验证
*/
@Extension
public static class CrumbFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
// 仅对修改操作进行验证
if (requiresCrumbCheck(req)) {
CrumbIssuer issuer = Jenkins.get().getCrumbIssuer();
String crumb = req.getHeader(issuer.getCrumbRequestField());
if (!issuer.validateCrumb(req, crumb)) {
((HttpServletResponse) response).sendError(
HttpServletResponse.SC_FORBIDDEN,
"Invalid CSRF Crumb"
);
return;
}
}
chain.doFilter(request, response);
}
private boolean requiresCrumbCheck(HttpServletRequest req) {
String method = req.getMethod().toUpperCase();
return "POST".equals(method) || "PUT".equals(method) ||
"DELETE".equals(method);
}
}
}
7. 高可用与集群部署
7.1 高可用架构设计
7.2 主备切换机制
主备切换实现:
/**
* Jenkins高可用主备切换实现
*
* 基于分布式锁实现Active-Standby模式
*/
public class FailoverManager implements ExtensionPoint {
private final DistributedLock lock;
private volatile boolean isActive = false;
private final Thread heartbeatThread;
/**
* 分布式锁接口
*
* 可基于ZooKeeper、etcd、Redis等实现
*/
public interface DistributedLock {
boolean tryLock(long timeout, TimeUnit unit);
void unlock();
boolean isLocked();
String getLockOwner();
}
/**
* 初始化故障转移管理器
*/
public FailoverManager() {
// 创建分布式锁(示例使用ZooKeeper)
this.lock = new ZooKeeperDistributedLock(
System.getProperty("jenkins.ha.zkServers", "localhost:2181"),
"/jenkins/ha/lock"
);
// 启动心跳线程
this.heartbeatThread = new Thread(this::heartbeatLoop, "HA-Heartbeat");
this.heartbeatThread.setDaemon(true);
this.heartbeatThread.start();
}
/**
* 心跳循环
*
* 定期续约锁,检测主节点状态
*/
private void heartbeatLoop() {
while (!Thread.currentThread().isInterrupted()) {
try {
if (isActive) {
// Active节点:续约锁
if (!lock.isLocked()) {
// 锁丢失,降级为Standby
demote();
}
} else {
// Standby节点:尝试获取锁
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
promote();
}
}
Thread.sleep(5000); // 5秒心跳间隔
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
// 记录错误并继续
}
}
}
/**
* 提升为Active节点
*/
private synchronized void promote() {
if (isActive) return;
try {
// 1. 加载JENKINS_HOME
Jenkins jenkins = Jenkins.getInstance();
jenkins.reload();
// 2. 恢复构建队列
jenkins.getQueue().restore();
// 3. 接受Agent连接
for (Computer c : jenkins.getComputers()) {
if (c instanceof SlaveComputer) {
((SlaveComputer) c).setAcceptingTasks(true);
}
}
// 4. 启动调度器
jenkins.getQueue().maintain();
isActive = true;
} catch (Exception e) {
// 提升失败,释放锁
lock.unlock();
throw new RuntimeException("Failed to promote to active", e);
}
}
/**
* 降级为Standby节点
*/
private synchronized void demote() {
if (!isActive) return;
try {
// 1. 停止接受新任务
Jenkins jenkins = Jenkins.getInstance();
jenkins.getQueue().maintain();
// 2. 等待当前构建完成或超时
waitForBuildsCompletion(60, TimeUnit.SECONDS);
// 3. 断开Agent连接
for (Computer c : jenkins.getComputers()) {
if (c instanceof SlaveComputer) {
((SlaveComputer) c).setAcceptingTasks(false);
}
}
// 4. 释放锁
lock.unlock();
isActive = false;
} catch (Exception e) {
throw new RuntimeException("Failed to demote to standby", e);
}
}
private void waitForBuildsCompletion(long timeout, TimeUnit unit)
throws InterruptedException {
long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
Jenkins jenkins = Jenkins.getInstance();
while (System.currentTimeMillis() < deadline) {
boolean hasRunning = false;
for (Computer c : jenkins.getComputers()) {
if (c.countBusy() > 0) {
hasRunning = true;
break;
}
}
if (!hasRunning) break;
Thread.sleep(1000);
}
}
}
7.3 数据一致性保障
/**
* 共享存储一致性保障
*
* 使用文件锁防止多实例同时写入
*/
public class SharedStorageManager {
private final FileLock fileLock;
private final FileChannel channel;
/**
* 获取独占锁
*
* 在访问JENKINS_HOME前必须获取锁
*/
public boolean acquireLock() {
try {
File lockFile = new File(Jenkins.get().getRootDir(), ".lock");
channel = new RandomAccessFile(lockFile, "rw").getChannel();
fileLock = channel.tryLock();
return fileLock != null;
} catch (IOException e) {
return false;
}
}
/**
* 释放锁
*/
public void releaseLock() {
try {
if (fileLock != null) {
fileLock.release();
}
if (channel != null) {
channel.close();
}
} catch (IOException e) {
// 忽略关闭错误
}
}
}
7.4 集群部署最佳实践
# Kubernetes部署配置示例
# 文件: jenkins-ha.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jenkins
labels:
app: jenkins
spec:
serviceName: jenkins
replicas: 1 # Active-Standby模式,同时只运行一个实例
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
serviceAccountName: jenkins
containers:
- name: jenkins
image: jenkins/jenkins:lts
ports:
- containerPort: 8080
name: web
- containerPort: 50000
name: agent
env:
- name: JENKINS_HOME
value: /var/jenkins_home
- name: JAVA_OPTS
value: >-
-Djenkins.install.runSetupWizard=false
-Dhudson.slaves.WorkspaceDir=/var/jenkins_home/workspace
-Dhudson.model.DirectoryBrowserSupport.CSP=""
volumeMounts:
- name: jenkins-home
mountPath: /var/jenkins_home
- name: jenkins-config
mountPath: /var/jenkins_config
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 120
periodSeconds: 10
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "4"
memory: "8Gi"
volumes:
- name: jenkins-config
configMap:
name: jenkins-config
volumeClaimTemplates:
- metadata:
name: jenkins-home
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 50Gi
---
# Service配置
apiVersion: v1
kind: Service
metadata:
name: jenkins
spec:
selector:
app: jenkins
ports:
- port: 80
targetPort: 8080
name: web
- port: 50000
targetPort: 50000
name: agent
type: LoadBalancer
---
# Agent Pod模板
apiVersion: v1
kind: ConfigMap
metadata:
name: jenkins-agent-pod-template
data:
template.yaml: |
apiVersion: v1
kind: Pod
spec:
containers:
- name: jnlp
image: jenkins/inbound-agent:latest
args: ['\$(JENKINS_SECRET)', '\$(JENKINS_NAME)']
- name: maven
image: maven:3.8-openjdk-11
command: ['sleep', '99d']
volumeMounts:
- name: maven-repo
mountPath: /root/.m2
volumes:
- name: maven-repo
emptyDir: {}
总结
本文深入分析了Jenkins的核心架构设计,涵盖以下关键知识点:
| 模块 | 核心要点 |
|---|---|
| 整体架构 | Master-Agent分布式设计、多协议通信、扩展点机制 |
| 核心组件 | Jenkins单例模式、插件类加载隔离、Descriptor模式 |
| 任务调度 | Queue状态机、Executor生命周期、优先级算法 |
| 数据持久化 | JENKINS_HOME结构、XStream序列化、敏感数据加密 |
| 扩展点体系 | ExtensionPoint接口、@Extension注解、动态注册机制 |
| 安全架构 | 认证/授权分离、CSRF防护、权限矩阵 |
| 高可用 | Active-Standby模式、分布式锁、共享存储 |
更多推荐
所有评论(0)