【摘要】 将CodePlus从本地IDE运行的三服务Demo,转变为可在云上稳定对外服务的过程,是一次充满挑战的“成人礼”。本文将以实战视角,回顾我们如何将前端、主后端、沙箱服务进行容器化编排,解决服务依赖、网络通信、配置管理等生产环境难题,并总结出一份宝贵的部署“避坑指南”。
**【正文】
** 开发环境一切正常,但一旦打包部署,各种诡异问题便接踵而至。这是我们从“项目完成”到“服务上线”过程中最深刻的体会。
1. 容器化:为每个服务打造专属“集装箱”
我们为三个核心服务分别编写了Dockerfile,原则是构建最小化镜像。
后端主服务:基于
openjdk:17-slim,将依赖打包和代码分层,利用Docker层缓存加速构建。沙箱服务:这是重点。由于需要执行
javac/java命令,基础镜像必须包含完整的JDK。我们选择了openjdk:17,并额外设置了容器内的用户权限,避免以root运行。前端:使用多阶段构建,基于
node:18-alpine编译,最终产物由nginx:alpine提供服务。
2. 服务依赖与启动顺序
这是第一个大坑。三个服务有严格依赖:前端依赖后端API,后端启动时需要连接沙箱的MCP服务。我们最初使用docker-compose的depends_on,但这只保证容器启动顺序,不保证服务就绪。后端启动时,如果沙箱的MCP SSE端点尚未监听,spring-ai-starter-mcp-client-webflux的自动配置会因连接失败而报错,导致后端启动失败。
解决方案:我们在后端的
application.yml中为MCP客户端配置了更长的连接超时和重试机制。同时,编写了一个简单的wait-for-it.sh脚本,在docker-compose中让后端容器等待沙箱服务的8091端口真正可用后再启动Spring应用。这确保了服务间依赖的稳定建立。
3. 网络通信与配置注入
在容器网络中,服务间通信需使用Docker分配的服务名,而非localhost。
关键配置:我们将后端的
spring.ai.mcp.client.sse.connections.code-sandbox.url从http://localhost:8091改为http://sandboxtool:8091(sandboxtool是docker-compose中定义的服务名)。前端的api.js中,后端API地址也相应改为http://backend:8081/api,并通过Nginx反向代理对外暴露。敏感信息管理:DashScope API Key、数据库密码等,我们坚决不写入代码或配置文件。通过Docker的
environment指令或.env文件注入环境变量。在application.yml中使用${DASHSCOPE_API_KEY:}语法读取,做到了配置与代码的完全分离。
4. 健康检查与监控
为每个服务添加了healthcheck配置。例如,后端检查/actuator/health,前端检查/index.html。这使运维平台能感知服务状态。我们还简单集成了Spring Boot Actuator,暴露了基础的健康、信息和指标端点,为后续监控打下基础。
5. 个人实践与反思
部署过程是对系统架构理解的一次大考。我们遇到的最棘手问题是沙箱的安全性在生产环境中的不足。虽然本地测试时四层防御(黑名单、隔离、超时)工作良好,但一旦部署在公有云上,我们对宿主机安全性的担忧与日俱增。文档中提到的“使用Docker容器加强隔离”从建议变成了必须完成的任务。
为此,我主导了沙箱服务的安全加固升级:
特权去除:在
Dockerfile中创建非root用户运行沙箱进程。资源限制:在
docker-compose中为沙箱服务设置cpus: \'0.5\'、mem_limit: \'512m\',防止某个提交耗尽主机资源。只读文件系统:将沙箱容器内除
/tmp外的目录挂载为只读,防止恶意代码篡改系统文件。网络隔离:为沙箱服务配置
network_mode: service:backend,使其与后端共享网络命名空间,但无法直接访问外网,彻底杜绝“挖矿”等风险。
这次部署经历让我深刻认识到,开发完成只是第一步,让服务在复杂多变的网络和生产环境中稳定、安全地运行,是另一个维度的挑战,也是工程师价值的真正体现。