云计算百科
云计算领域专业知识百科平台

部署实战;Docker + AI Agent:让AI一键部署Spring Boot到服务器,从打包到上线只要一条指令

【部署实战】Docker + AI Agent:让AI一键部署Spring Boot到服务器,从打包到上线只要一条指令

文章目录

  • 设置时区
  • 创建应用目录
  • 复制jar包
  • 创建非root用户运行
  • 健康检查
  • JVM参数:限制堆内存、开启GC日志

写在前面 写过Spring Boot的都懂——开发一时爽,部署火葬场。

打完jar包忘了改配置文件的环境变量,上去了连不上数据库。改了代码忘了更新Docker镜像,上线后跑的还是旧版本。服务器磁盘满了没发现,部署到一半直接卡死。

这些破事每个开发者都遇到过。我们团队最近搞了一个方案:把Docker操作包装成MCP Tool,让AI Agent接手部署全流程——从打包到启动到健康检查,全部自动化,出错了还会自己回滚。

这篇文章完整拆解这套方案。环境:Spring Boot 3.3.0 + Docker 26 + MCP协议。服务器:Linux(CentOS 7/Ubuntu 22.04均可)。

一、手工部署到底有多烦 回忆一下你上次部署的流程。

本地 mvn clean package 打jar包。把jar包scp到服务器。修改application-prod.yml里的数据库地址。docker build 构建镜像。docker stop 停掉旧容器。docker run 启动新容器。docker logs -f 盯着日志看有没有报错。发现数据库连不上,查了半天发现是环境变量没设对。重新改了环境变量,再跑一遍。

整个过程20分钟起步,如果中间出问题,半小时一小时很正常。人还会漏步骤——忘了备份、忘了健康检查、忘了清理旧镜像导致磁盘满了。

AI Agent都能帮你干。

二、Dockerfile模板 dockerfile FROM eclipse-temurin:21-jre-alpine

设置时区

RUN apk add –no-cache tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo “Asia/Shanghai” > /etc/timezone

创建应用目录

WORKDIR /app

复制jar包

COPY target/*.jar app.jar

创建非root用户运行

RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser

健康检查

HEALTHCHECK –interval=30s –timeout=5s –retries=3 CMD wget –no-verbose –tries=1 –spider http://localhost:8080/actuator/health || exit 1

EXPOSE 8080

JVM参数:限制堆内存、开启GC日志

ENTRYPOINT [“java”, “-Xms256m”, “-Xmx512m”, “-XX:+UseG1GC”, “-XX:MaxGCPauseMillis=200”, “-Djava.security.egd=file:/dev/./urandom”, “-jar”, “app.jar”] docker-compose.yml:

yaml version: ‘3.8’

services: app: build: . container_name:

A

P

P

N

A

M

E

:

m

y

a

p

p

p

o

r

t

s

:

"

{APP_NAME:-myapp} ports: – "

APPNAME:myappports:"{APP_PORT:-8080}:8080" environment: – SPRING_PROFILES_ACTIVE=

S

P

R

I

N

G

P

R

O

F

I

L

E

S

A

C

T

I

V

E

:

p

r

o

d

D

B

U

R

L

=

{SPRING_PROFILES_ACTIVE:-prod} – DB_URL=

SPRINGPROFILESACTIVE:prodDBURL={DB_URL} – DB_USERNAME=

D

B

U

S

E

R

N

A

M

E

D

B

P

A

S

S

W

O

R

D

=

{DB_USERNAME} – DB_PASSWORD=

DBUSERNAMEDBPASSWORD={DB_PASSWORD} – REDIS_HOST=${REDIS_HOST:-redis} volumes: – ./logs:/app/logs restart: unless-stopped healthcheck: test: [“CMD”, “curl”, “-f”, “http://localhost:8080/actuator/health”] interval: 30s timeout: 5s retries: 5 start_period: 60s networks: – app-network

networks: app-network: driver: bridge 三、部署操作Tool——给AI的操作接口 java @Component public class DockerDeployTool {

@Tool(description = "执行完整的部署流程:1.编译打包 2.构建Docker镜像 3.停止旧容器 4.启动新容器 5.健康检查。" +
"任何一步失败都会自动回滚到上一个可用版本。" +
"部署前确保.gitignore已配置,避免提交敏感文件")
public String deploy(
@ToolParam(description = "项目路径,如/home/app/myproject") String projectPath,
@ToolParam(description = "环境:dev/test/prod") String env,
@ToolParam(description = "是否跳过测试(紧急修复时可跳过)") boolean skipTests) {

StringBuilder log = new StringBuilder();
String oldContainerId = null;

try {
// 1. 编译打包
log.append(execCommand(projectPath,
skipTests ? "mvn clean package -DskipTests -q" : "mvn clean package -q"));

// 2. 备份当前运行的容器ID
oldContainerId = execCommand(projectPath,
"docker ps -q –filter name=myapp").trim();

// 3. 构建新镜像(带版本标签,方便回滚)
String version = String.valueOf(System.currentTimeMillis());
log.append(execCommand(projectPath,
"docker build -t myapp:" + version + " -t myapp:latest ."));

// 4. 停旧容器
if (!oldContainerId.isEmpty()) {
log.append(execCommand(projectPath, "docker stop " + oldContainerId));
}

// 5. 启动新容器
log.append(execCommand(projectPath,
"docker compose up -d"));

// 6. 等待健康检查
Thread.sleep(10000);
String health = execCommand(projectPath,
"docker inspect –format='{{.State.Health.Status}}' myapp").trim();

if (!"healthy".equals(health)) {
throw new RuntimeException("健康检查失败,状态:" + health);
}

log.append("✅ 部署成功!新版本:" + version);
log.append("✅ 健康检查通过");

// 7. 清理旧镜像(保留最近3个版本)
execCommand(projectPath,
"docker images myapp –format '{{.Tag}}' | sort -r | tail -n +4 | xargs -r docker rmi");

return log.toString();

} catch (Exception e) {
log.append("❌ 部署失败:" + e.getMessage());

// 自动回滚
if (oldContainerId != null && !oldContainerId.isEmpty()) {
log.append("\\n🔄 正在回滚到上一个版本…");
execCommand(projectPath, "docker start " + oldContainerId);
log.append("✅ 已回滚");
}

return log.toString();
}
}

@Tool(description = "查看服务器磁盘和内存使用情况。" +
"Docker镜像和容器日志经常占满磁盘,需要定期清理。" +
"磁盘使用率超过80%时需要关注,超过90%需要紧急清理")
public String checkServerHealth() {
StringBuilder report = new StringBuilder("服务器健康报告:\\n\\n");

// 磁盘
report.append("【磁盘使用】\\n");
report.append(execCommand(null, "df -h / /var/lib/docker | tail -n +2"));

// 内存
report.append("\\n【内存使用】\\n");
report.append(execCommand(null, "free -h"));

// Docker磁盘占用
report.append("\\n【Docker磁盘占用】\\n");
report.append(execCommand(null, "docker system df"));

return report.toString();
}

@Tool(description = "清理Docker垃圾:未使用的镜像、停止的容器、悬挂的卷、构建缓存。" +
"建议每周执行一次,避免磁盘被占满。" +
"清理前确认没有需要保留的旧版本")
public String cleanDocker() {
StringBuilder log = new StringBuilder("Docker清理报告:\\n");

log.append("清理前:\\n");
log.append(execCommand(null, "docker system df"));

log.append("\\n开始清理…\\n");
log.append(execCommand(null, "docker system prune -f –volumes"));

log.append("\\n清理后:\\n");
log.append(execCommand(null, "docker system df"));

return log.toString();
}

@Tool(description = "查看应用实时日志。支持过滤关键词(如ERROR、WARN)。" +
"tailLines控制返回最近多少行,避免日志量过大")
public String viewLogs(
@ToolParam(description = "过滤关键词,如ERROR/WARN/Exception,留空查看全部")
String keyword,
@ToolParam(description = "显示最近N行,建议20-100") int tailLines) {

String cmd = "docker logs myapp –tail " + tailLines;
if (keyword != null && !keyword.isEmpty()) {
cmd += " 2>&1 | grep -i " + keyword;
}

return execCommand(null, cmd);
}

@Tool(description = "回滚到指定版本。version为docker image的tag。")
public String rollback(
@ToolParam(description = "目标版本号(image tag)") String version) {

StringBuilder log = new StringBuilder();

log.append(execCommand(null, "docker stop myapp"));
log.append(execCommand(null, "docker rm myapp"));
log.append(execCommand(null, "docker run -d –name myapp -p 8080:8080 myapp:" + version));

return "已回滚到版本:" + version + "\\n" + log.toString();
}

private String execCommand(String workingDir, String command) {
try {
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", command);
if (workingDir != null) {
pb.directory(new java.io.File(workingDir));
}
Process process = pb.start();

String output = new String(process.getInputStream().readAllBytes());
String error = new String(process.getErrorStream().readAllBytes());

process.waitFor(5, java.util.concurrent.TimeUnit.MINUTES);

return output + (error.isEmpty() ? "" : "\\n" + error);
} catch (Exception e) {
return "命令执行失败:" + e.getMessage();
}
}

} 四、AI Agent的部署全流程 你只需要说一句话:“帮我把项目部署到测试环境。”

Agent自动执行:编译打包(跳过测试或全量跑取决于环境)、构建Docker镜像、备份当前运行版本、停止旧容器、启动新容器、等待健康检查、健康检查通过→清理旧镜像、健康检查失败→自动回滚。

整个过程你喝着咖啡看日志就行,出错了还能自动回滚。

五、定时巡检 java @Component public class ServerCronJob {

@Autowired
private DockerDeployTool deployTool;

@Scheduled(cron = "0 0 9 * * ?") // 每天早上9点
public void dailyHealthCheck() {
String health = deployTool.checkServerHealth();

// 如果磁盘使用率超80%,发告警
if (health.contains("80%") || health.contains("90%")) {
sendAlert("服务器磁盘告警", health);
}
}

@Scheduled(cron = "0 0 3 ? * SUN") // 每周日凌晨3点
public void weeklyCleanup() {
deployTool.cleanDocker();
}

} 六、踩坑记录 坑1:mvn package慢得离谱。 小项目还好,大项目一次打包两分钟。如果每次都全量打包,CI/CD流水线会被拖慢。解决方法:本地开发用dev profile,跳过不必要的插件。

坑2:docker build的层层缓存。 Dockerfile里COPY命令顺序错了,每次改代码都会让所有层重建。正确顺序:先COPY pom.xml → RUN mvn dependency → 再COPY src → RUN mvn package。

坑3:健康检查的start_period设太短。 Spring Boot启动到完全就绪可能需要30-60秒。如果start_period只设10秒,容器还没启动完就被判定为unhealthy然后又被重启,进入死亡循环。

yaml healthcheck: test: [“CMD”, “curl”, “-f”, “http://localhost:8080/actuator/health”] interval: 30s timeout: 5s retries: 5 start_period: 60s # ← 给够启动时间! 七、总结 这套方案把部署从"手动20分钟易出错"变成了"一句话指令自动完成还带回滚"。三个核心组件:Docker Tool包装操作命令、健康检查确保新版本可用、自动回滚保证不出生产事故。

适合所有用Docker部署Spring Boot应用的团队。个人开发者用这套也能省下大量运维时间——把精力放在写代码上,部署交给AI。

有用的话点赞收藏。下一篇《AI Agent + Elasticsearch:MCP协议实现智能全文搜索》。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 部署实战;Docker + AI Agent:让AI一键部署Spring Boot到服务器,从打包到上线只要一条指令
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!