Koupleless 合并部署完整教程
目录
Koupleless 简介
Koupleless 是一种模块化的 Serverless 技术解决方案,它能让普通应用以较低的代价演进为 Serverless 研发模式,让代码与资源解耦,轻松独立维护,同时支持秒级构建部署、合并部署、动态伸缩等能力,为用户提供极致的研发运维体验。
适用场景
- 应用构建发布慢或 SDK 升级繁琐:传统应用构建发布需要 6-10 分钟,使用 Koupless 可降至 10 秒级
- 长尾应用资源浪费:企业 80% 的应用 CPU 使用率低于 10%,合并部署可显著降低资源成本
- 研发协作效率低:多人开发一个应用时,需要统一时间窗口发布, Koupless 支持模块独立迭代
- 中台应用难以沉淀业务资产:通过基座沉淀公共能力,模块实现具体业务逻辑
- 微服务演进成本高:支持应用在单体、模块化、微服务架构间平滑过渡
合并部署概述
什么是合并部署
合并部署是指将多个独立的应用(在 Koupless 中称为"模块")部署到同一个 JVM 进程中,共享基座的资源和依赖,但保持代码和运行时的隔离。
合并部署的优势
- 节省资源:多个模块共享基座的内存(Metaspace 和 Heap),CPU 使用率有效提升
- 快速启动:模块构建产物从数百 MB 瘦身到几十 MB,启动时间大幅缩短
- 简化运维:统一管理多个模块,降低维护成本
- 灵活扩展:支持动态添加、移除模块,无需重启基座
架构原理
Koupleless 基于 SOFAArk 框架实现类隔离和合并部署:
- 基座(Base):提供公共依赖和基础能力,如 SpringBoot 框架、中间件 SDK 等
- 模块(Biz):业务功能模块,依赖基座提供的公共能力
- 类加载机制:模块优先从自己的 ClassLoader 查找类,找不到再委托给基座 ClassLoader
环境准备
前置条件
- JDK 8 或更高版本
- Maven 3.6+
- SpringBoot 2.x 或 3.x
- 可用的 IDE(IntelliJ IDEA、Eclipse 等)
版本选择
当前 Koupleless 主要版本:
<koupleless.runtime.version>0.5.6</koupleless.runtime.version>
<sofa.ark.version>2.2.14</sofa.ark.version>工具准备
下载 Arkctl 工具(用于模块部署):
# Mac/Linux
wget https://github.com/koupleless/koupleless/releases/download/arkctl-release-0.2.0/arkctl-darwin-amd64
mv arkctl-darwin-amd64 /usr/local/bin/arkctl
chmod +x /usr/local/bin/arkctl
# Linux
wget https://github.com/koupleless/koupleless/releases/download/arkctl-release-0.2.0/arkctl-linux-amd64
mv arkctl-linux-amd64 /usr/local/bin/arkctl
chmod +x /usr/local/bin/arkctl基座改造
1. 添加依赖
在基座的 pom.xml 中添加 Koupleless 基座依赖:
<properties>
<koupleless.runtime.version>0.5.6</koupleless.runtime.version>
<sofa.ark.version>2.2.14</sofa.ark.version>
</properties>
<dependencies>
<!-- Koupleless 基座启动器 -->
<dependency>
<groupId>com.alipay.sofa.koupleless</groupId>
<artifactId>koupleless-base-starter</artifactId>
<version>${koupleless.runtime.version}</version>
</dependency>
<!-- Web 插件(支持多 Web 模块合并部署) -->
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>web-ark-plugin</artifactId>
</dependency>
</dependencies>2. 配置应用名
在 application.properties 或 application.yml 中配置应用名:
# application.properties
spring.application.name=base-application3. 配置基座构建插件
在 pom.xml 的 build 部分添加基座构建插件:
<build>
<plugins>
<!-- Koupleless 基座构建插件 -->
<plugin>
<groupId>com.alipay.sofa.koupleless</groupId>
<artifactId>koupleless-base-build-plugin</artifactId>
<version>${koupleless.runtime.version}</version>
<executions>
<execution>
<goals>
<goal>add-patch</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 生成 starter 的 artifactId,需要替换为实际的应用名 -->
<dependencyArtifactId>${baseAppName}-dependencies-starter</dependencyArtifactId>
<!-- 生成 jar 的版本号 -->
<dependencyVersion>0.0.1-SNAPSHOT</dependencyVersion>
</configuration>
</plugin>
<!-- SpringBoot 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>4. 生成依赖 Starter
执行以下命令生成基座的依赖 starter:
mvn com.alipay.sofa.koupleless:koupleless-base-build-plugin::packageDependency -f pom.xml这个命令会生成一个 ${baseAppName}-dependencies-starter 的依赖包,模块项目可以将其作为 parent 来继承基座的所有依赖。
5. 启动基座
完成上述配置后,可以直接通过 IDE 或命令行启动基座:
mvn spring-boot:run模块改造
1. 创建模块项目
模块可以是现有的 SpringBoot 应用,也可以是新创建的项目。建议使用 Maven 多模块结构管理多个模块。
2. 配置模块依赖
在模块的 pom.xml 中添加模块依赖和打包插件:
<properties>
<koupleless.runtime.version>0.5.6</koupleless.runtime.version>
<sofa.ark.version>2.2.14</sofa.ark.version>
</properties>
<dependencies>
<!-- Koupleless 模块启动器(provided 作用域) -->
<dependency>
<groupId>com.alipay.sofa.koupleless</groupId>
<artifactId>koupleless-app-starter</artifactId>
<version>${koupleless.runtime.version}</version>
<scope>provided</scope>
</dependency>
<!-- 可选:继承基座依赖 starter -->
<parent>
<groupId>com.example</groupId>
<artifactId>base-application-dependencies-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
</dependencies>3. 配置打包插件
在模块的 pom.xml 中添加 SOFAArk 打包插件:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- SOFAArk 打包插件 -->
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<version>${sofa.ark.version}</version>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 不生成可执行 jar -->
<skipArkExecutable>true</skipArkExecutable>
<!-- 输出目录 -->
<outputDirectory>./target</outputDirectory>
<!-- 模块名称(唯一标识) -->
<bizName>module1</bizName>
<!-- Web 上下文路径(需要与其他模块不同) -->
<webContextPath>/module1</webContextPath>
<!-- 开启声明模式 -->
<declaredMode>true</declaredMode>
<!-- 模块瘦身配置文件 -->
<packExcludesConfig>rules.txt</packExcludesConfig>
</configuration>
</plugin>
<!-- SpringBoot 打包插件(可选,用于独立部署) -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>4. 配置模块瘦身
下载自动排包配置文件 rules.txt,放在模块的 conf/ark/ 目录下:
# 创建目录
mkdir -p conf/ark
# 下载配置文件
wget https://github.com/koupleless/samples/raw/master/springboot-samples/slimming/log4j2/biz1/conf/ark/rules.txt -O conf/ark/rules.txtrules.txt 内容示例:
# 排除的依赖(groupId:artifactId:version)
excludes=org.springframework.boot:spring-boot-starter-logging,commons-logging:commons-logging
# 排除的 groupId
excludeGroupIds=org.slf4j
# 排除的 artifactId
excludeArtifactIds=logback-classic5. 开发模块代码
创建模块的业务代码,例如 REST Controller:
package com.example.module1.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.context.ApplicationContext;
@RestController
@RequestMapping("/")
public class SampleController {
private static final Logger LOGGER = LoggerFactory.getLogger(SampleController.class);
@Autowired
private ApplicationContext applicationContext;
@GetMapping("/")
public String hello() {
String appName = applicationContext.getApplicationName();
LOGGER.info("{} web test: into sample controller", appName);
return String.format("hello to %s deploy", appName);
}
@GetMapping("/health")
public String health() {
return "OK";
}
}6. 配置应用名
在模块的 application.properties 中配置应用名:
spring.application.name=module1模块瘦身
为什么要模块瘦身
模块瘦身可以:
- 减小模块包大小:从数百 MB 降低到几十 MB
- 减少内存占用:降低 Metaspace 占用,基座可以安装更多模块
- 加快启动速度:模块启动时间从分钟级降低到秒级
- 提高部署效率:小体积包更容易传输和部署
瘦身原理
模块瘦身的核心思想是:移除模块中与基座重复的依赖,让模块复用基座的类
通过以下方式实现:
- 继承基座依赖 starter:模块以
${baseAppName}-dependencies-starter作为 parent - 自动排包:使用
rules.txt配置文件排除不需要的依赖 - 依赖委托:模块依赖设置为
provided作用域,委托给基座加载
配置方法
方法一:继承基座依赖 starter(推荐)
在模块的 pom.xml 中配置:
<parent>
<groupId>com.example</groupId>
<artifactId>base-application-dependencies-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>这样模块会自动继承基座的所有依赖,无需手动配置版本。
方法二:手动配置依赖作用域
在模块的 pom.xml 中,将基座已有的依赖设置为 provided 作用域:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>方法三:使用排除配置
在 sofa-ark-maven-plugin 中配置排除项:
<configuration>
<excludeGroupIds>org.springframework,org.slf4j</excludeGroupIds>
<excludeArtifactIds>spring-boot-starter-logging</excludeArtifactIds>
</configuration>验证瘦身效果
构建模块并查看包大小:
# 构建模块
mvn clean package
# 查看构建产物
ls -lh target/*.jar
# 瘦身前
-rw-r--r-- 1 user staff 250M 1 15 10:00 module1-1.0.0.jar
-rw-r--r-- 1 user staff 180M 1 15 10:00 module1-1.0.0-ark-biz.jar
# 瘦身后
-rw-r--r-- 1 user staff 250M 1 15 10:30 module1-1.0.0.jar
-rw-r--r-- 1 user staff 15M 1 15 10:30 module1-1.0.0-ark-biz.jar可以看到,瘦身后的 ark-biz.jar 从 180M 降低到 15M,效果显著。
部署与验证
1. 启动基座
首先启动基座应用:
cd base-application
mvn spring-boot:run或使用 IDE 启动主类。
2. 构建模块
在模块项目中执行构建:
cd module1
mvn clean package构建成功后会生成两个 jar 包:
module1-1.0.0.jar:普通 SpringBoot fat jar,可独立运行module1-1.0.0-ark-biz.jar:Koupleless 模块包,用于合并部署
3. 使用 Arkctl 部署模块
使用 Arkctl 工具将模块部署到基座:
# 部署模块
arkctl deploy target/module1-1.0.0-ark-biz.jar
# 或者使用 curl 命令
curl -X POST http://localhost:1238/installBiz \
-H "Content-Type: application/json" \
-d '{"bizName":"module1","bizVersion":"1.0.0","bizUrl":"file:///path/to/module1-1.0.0-ark-biz.jar"}'4. 验证部署
部署成功后,可以通过以下方式验证:
查看模块状态
arkctl status输出示例:
Biz count: 1
bizName: module1
bizVersion: 1.0.0
bizState: ACTIVATED
mainClass: com.example.module1.Module1Application
webContextPath: /module1访问模块接口
# 访问模块1的接口
curl http://localhost:8080/module1/
# 输出: hello to module1 deploy
curl http://localhost:8080/module1/health
# 输出: OK查看日志
在基座控制台可以看到模块启动日志:
[INFO] Install biz: module1, version: 1.0.0, state: ACTIVATED
[INFO] Module module1 started successfully5. 部署多个模块
重复上述步骤部署 module2:
cd module2
mvn clean package
arkctl deploy target/module2-1.0.0-ark-biz.jar验证多模块部署:
# 查看所有模块
arkctl status
# 访问不同模块
curl http://localhost:8080/module1/
curl http://localhost:8080/module2/6. 卸载模块
# 卸载指定模块
arkctl uninstall --bizName=module1 --bizVersion=1.0.0
# 或者使用 curl
curl -X POST http://localhost:1238/uninstallBiz \
-H "Content-Type: application/json" \
-d '{"bizName":"module1","bizVersion":"1.0.0"}'常见问题与解决方案
1. 类冲突问题
问题描述:模块和基座存在相同类名的类,导致类冲突。
解决方案:
- 使用类隔离机制,模块优先加载自己的类
- 公共类下沉到基座,模块通过依赖委托复用
- 避免在模块和基座中定义相同包名和类名的类
2. 静态字段共享问题
问题描述:多个模块共享静态字段,导致数据混乱。
解决方案:
使用 StaticFieldMapWrapper 适配静态字段:
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
public class StaticFieldMapWrapper<T> {
private final ConcurrentHashMap<ClassLoader, T> classLoaderTMap = new ConcurrentHashMap<>();
private Supplier<T> getDefaultMethod;
public StaticFieldMapWrapper(Supplier<T> getDefaultMethod) {
this.getDefaultMethod = getDefaultMethod;
}
public T getOrPutDefault() {
T t = classLoaderTMap.get(Thread.currentThread().getContextClassLoader());
if (t == null && getDefaultMethod != null) {
t = getDefaultMethod.get();
classLoaderTMap.put(Thread.currentThread().getContextClassLoader(), t);
}
return t;
}
}3. Dubbo 兼容性问题
问题描述:模块中使用 Dubbo 泛化调用时出现问题。
解决方案:
- 确保 Dubbo 相关依赖下沉到基座
- 为 Dubbo 配置正确的 ClassLoader 切换
- 参考官方 adapter 仓库的解决方案
4. Nacos 线程过多
问题描述:每个模块启动时创建 Nacos 客户端,导致线程数过多。
解决方案:
在基座中缓存 Nacos 客户端:
private static final Map<Properties, ConfigService> CONFIG_SERVICE_MAP = new ConcurrentHashMap<>();
public static ConfigService getConfigService(Properties properties) {
return CONFIG_SERVICE_MAP.computeIfAbsent(properties, k -> {
try {
return NacosFactory.createConfigService(k);
} catch (NacosException e) {
throw new RuntimeException(e);
}
});
}5. 模块启动顺序问题
问题描述:模块之间有依赖关系,需要按顺序启动。
解决方案:
- 在基座中配置模块启动顺序
- 使用依赖关系控制启动顺序
- 避免循环依赖
6. 内存泄漏
问题描述:模块卸载后,内存没有释放,导致 Metaspace 持续增长。
解决方案:
- 确保模块卸载时清理所有资源(线程池、连接等)
- 使用弱引用管理缓存
- 定期重启基座或进行内存整理
7. Spring 上下文冲突
问题描述:模块和基座的 Spring Bean 冲突。
解决方案:
- 使用不同的 Bean 名称
- 模块中使用
@Primary注解明确优先级 - 避免模块和基座定义相同的 Bean ---
生产环境部署
1. 部署架构
生产环境推荐部署架构:
用户流量 -> Nginx/SLB -> 基座应用(K8s Pod)
|
+-> 模块1
+-> 模块2
+-> 模块32. K8s 部署配置
基座 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: base-application
spec:
replicas: 3
selector:
matchLabels:
app: base-application
template:
metadata:
labels:
app: base-application
spec:
containers:
- name: base-application
image: registry.example.com/base-application:1.0.0
ports:
- containerPort: 8080
env:
- name: MODULE_AUTO_INSTALL
value: "true"
- name: MODULE_OSS_ENDPOINT
value: "oss-cn-region.aliyuncs.com"
- name: MODULE_OSS_BUCKET
value: "koupleless-modules"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
volumeMounts:
- name: module-storage
mountPath: /opt/modules
volumes:
- name: module-storage
persistentVolumeClaim:
claimName: module-pvc模块发布流水线
# 模块 CI/CD Pipeline
stages:
- build
- test
- package
- deploy
build_module:
stage: build
script:
- mvn clean compile
test_module:
stage: test
script:
- mvn test
package_module:
stage: package
script:
- mvn clean package
- cp target/*.ark-biz.jar module.ark-biz.jar
artifacts:
paths:
- module.ark-biz.jar
deploy_module:
stage: deploy
script:
# 上传模块到 OSS
- ossutil cp module.ark-biz.jar oss://koupleless-modules/module1/${CI_COMMIT_SHA}.ark-biz.jar
# 通知基座更新模块
- curl -X POST http://base-service:8080/install \
-H "Content-Type: application/json" \
-d '{"module":"module1","version":"'${CI_COMMIT_SHA}'"}'3. 模块动态部署
方案一:OSS 监听模式
基座监听 OSS 目录变化,自动部署新模块:
@Service
public class ModuleDeploymentService {
@Autowired
private OssClient ossClient;
@Scheduled(fixedDelay = 5000)
public void checkModuleUpdates() {
// 检查 OSS 上的模块版本
List<String> modules = getModuleList();
for (String module : modules) {
String latestVersion = getLatestVersion(module);
String currentVersion = getCurrentVersion(module);
if (!latestVersion.equals(currentVersion)) {
// 下载新模块
downloadModule(module, latestVersion);
// 卸载旧模块
uninstallModule(module, currentVersion);
// 安装新模块
installModule(module, latestVersion);
}
}
}
}方案二:配置中心模式
通过配置中心控制模块部署:
# 配置中心配置
modules:
module1:
version: 1.0.0
enabled: true
healthCheck: /health
module2:
version: 2.0.0
enabled: true
healthCheck: /health基座监听配置变化:
@NacosConfigListener(dataId = "modules.json")
public void onModuleConfigChange(String config) {
ModulesConfig modulesConfig = JSON.parseObject(config, ModulesConfig.class);
// 对比配置差异
List<ModuleChange> changes = compareModuleConfig(modulesConfig);
// 应用变更
for (ModuleChange change : changes) {
switch (change.getType()) {
case INSTALL:
installModule(change.getModule());
break;
case UNINSTALL:
uninstallModule(change.getModule());
break;
case UPDATE:
updateModule(change.getModule());
break;
}
}
}下面把教程里没展开的两件事一次讲透:
- 静态合并部署(Static Packaged Deployment)——一次性把基座 + N 个模块打成一个可执行 Fat Jar,启动即“全员就位”;
- 端口隔离——让每个模块真的监听自己的端口,实现“进程内多 WebServer”。
------------------------------------------------
方案三、静态合并部署(Static Packaged Deployment)
适用场景
- 上线流程极简,不允许运维侧再敲 arkctl deploy;
- 容器镜像只启一个进程,K8s liveness/readiness 探针直接探测基座即可;
- 模块版本固定,不需要热卸载/热加载。
打包思路
基座 pom 里把各模块的 ark-biz.jar 当资源一起打进 BOOT-INF/lib 目录;
基座启动时借助 SOFAArk 的 “static-biz” 机制,让这些 ark-biz.jar 随基座生命周期一起 install + start。实操步骤
step-1 模块端正常 mvn package 得到 moduleX-1.0.0-ark-biz.jar
step-2 基座 pom 增加 profile(只在打静态包时激活)
<!-- 基座 pom.xml -->
<profiles>
<profile>
<id>static-pack</id>
<dependencies>
<!-- 把模块 ark-biz 当 jar 依赖进来 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>module1</artifactId>
<version>1.0.0</version>
<classifier>ark-biz</classifier>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>module2</artifactId>
<version>1.0.0</version>
<classifier>ark-biz</classifier>
<type>jar</type>
</dependency>
</dependencies>
</profile>
</profiles>step-3 基座启动类加 @StaticBiz 注解(0.5.6+ 支持),告诉 SOFAArk 把 classpath 下所有 ark-biz.jar 静态安装:
@SpringBootApplication
@StaticBiz // 关键
public class BaseApplication {
public static void main(String[] args) {
SpringApplication.run(BaseApplication.class, args);
}
}step-4 打静态包
mvn clean package -Pstatic-pack生成的 base-application-1.0.0-exec.jar 里同时包含 module1-ark-biz.jar & module2-ark-biz.jar;
java -jar base-application-1.0.0-exec.jar 启动后,arkctl status 能看到两个模块已经是 ACTIVATED,无需再 deploy。
让模块监听自己的端口
默认行为
- 所有模块与基座共用同一个嵌入式 Tomcat(端口 8080),仅靠 webContextPath 区分;
- 好处:节省线程、节省内存;
- 坏处:无法“端口级”隔离,也做不到“模块单独暴露管理口”。
Koupleless 0.5.6 开始支持 “多 WebServer 模式”,即每个模块可以起独立的 Netty/Tomcat,真正 bind 自己的端口。
- 模块端增加依赖
<!-- 模块 pom.xml -->
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>web-ark-plugin</artifactId>
<classifier>multi-web</classifier> <!-- 关键 classifier -->
<scope>provided</scope>
</dependency>- 配置端口 & 上下文
# module1/application.yml
server:
port: 8081 # 模块独占
servlet:
context-path: / # 可以为 /
spring:
application:
name: module1- 打包插件声明
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<configuration>
<bizName>module1</bizName>
<webContextPath>/</webContextPath>
<!-- 开启多 web 模式 -->
<webMultiFlag>true</webMultiFlag>
</configuration>
</plugin>- 部署验证
基座仍跑 8080,module1 会额外监听 8081,module2 可再配 8082,互不影响;
curl http://localhost:8081/ 直接返回模块 1 的响应,无需再加前缀。
注意
- 端口多占 n 个,内存/线程也会相应增加,按需权衡;
- 生产环境需在 K8s Service 里把 8081、8082… 也声明出来,或走 Istio 多端口即可。