记一次诡异的 Docker 容器"串包"故障排查
容器名叫
tj-trade,docker ps 也正常 Up,微服务调用却一直报UnknownHostException: trade-service。进去一看日志,好家伙——它运行的居然是user-service的代码。
1. 故障现象
Spring Cloud 微服务架构,Nacos 做注册中心,Jenkins 手动逐个构建部署。
某天发现 tj-course 调 trade-service 时报错:
feign.RetryableException: trade-service executing GET http://trade-service/...
Caused by: java.net.UnknownHostException: trade-service
No servers available for service: trade-service
但 docker ps 显示 tj-trade 容器正在运行:
CONTAINER ID IMAGE STATUS PORTS
93ce8914e791 tj-trade:latest Up 32 minutes 0.0.0.0:8088->8088/tcp
端口对,状态对,看起来一切正常。那问题出在哪?
2. 排查过程
第一直觉:看 Nacos 注册
curl http://192.168.150.101:8848/nacos/v1/ns/instance/list?serviceName=trade-service
返回:
{"name":"DEFAULT_GROUP@@trade-service", "hosts": []}
Nacos 里没有 trade-service 的实例。 容器在跑,但没有成功注册。
第二直觉:看容器日志
docker logs tj-trade --tail 50
关键日志出现:
com.tianji.user.UserApplication : The following 1 profile is active: "dev"
Tomcat initialized with port(s): 8082
nacos registry, DEFAULT_GROUP user-service 192.168.150.101:8082 register finished
Application 'user-service' is running!
真相大白。 tj-trade 这个容器里运行的是 user-service 的 jar 包。它向 Nacos 注册的是 user-service,所以 trade-service 在注册中心里根本不存在。
3. 根因分析
项目结构
tjxt-parent/
├── tj-user/ # user-service
│ └── target/tj-user.jar
├── tj-trade/ # trade-service
│ └── target/tj-trade.jar
├── tj-course/ # course-service
│ └── ...
├── docker-compose.yml
└── Dockerfile # 通用 Dockerfile,放在项目根目录
通用 Dockerfile(有隐患)
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/*.jar app.jar # ⚠️ 通配符,复制 target 下第一个匹配的 jar
ENTRYPOINT ["sh", "-c", "java -jar app.jar"]
Jenkins 构建步骤(简化)
// 每个服务 job 大致相同,只是模块名不同
stage('Build') {
sh 'mvn clean package -pl tj-trade -am -DskipTests'
sh 'docker build -t tj-trade:latest .' // ← build context 是根目录
sh 'docker-compose up -d tj-trade'
}
串包场景还原
因为 Jenkins 上要手动挨个点部署 11 个微服务,在快速操作时,时序变成了这样:
时间轴 →
mvn clean mvn package docker build
tj-user: ██████████████████████████████████████████████
target/ 里有 tj-user.jar
mvn clean docker build(跑早了!)
tj-trade: ██████████████████████████████████████████████
↑ 此时 target/ 还没生成 tj-trade.jar
但 Dockerfile 的 COPY target/*.jar
读到了上一个构建残留的 tj-user.jar
核心问题是三个因素叠加:
| 因素 | 说明 |
|---|---|
| Docker build context 是项目根目录 | COPY target/*.jar 看的是根 target,不是模块 target |
| Maven 多模块聚合构建 | -pl tj-trade -am 也会编译依赖模块,根 target 可能混入其他 jar |
| 快速点击 + workspace 没隔离 | 上一个 job 的产物还没被 clean 掉,下一个 job 就启动了 |
4. 解决方案
方案一:使用模块专属 Dockerfile(推荐)
在每个子模块目录下放独立的 Dockerfile:
tj-trade/
├── src/
├── target/
│ └── tj-trade.jar
├── Dockerfile # 模块专属
└── pom.xml
# tj-trade/Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/tj-trade.jar app.jar # 精确指定文件名
ENTRYPOINT ["sh", "-c", "java -jar app.jar"]
Jenkins 构建脚本调整 build context:
sh 'docker build -t tj-trade:latest ./tj-trade'
方案二:Jenkins workspace 全量 clean
stage('Clean') {
sh 'git clean -fdx' // 清掉所有非 git 追踪的文件
sh 'mvn clean'
}
stage('Build') {
sh 'mvn package -pl tj-trade -am -DskipTests'
sh 'docker build -t tj-trade:latest ./tj-trade'
}
方案三:在 Dockerfile 里加校验(最稳妥)
# tj-trade/Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/tj-trade.jar app.jar
# 启动前校验:确认 jar 包里是正确的启动类
RUN jar tf app.jar | grep -q "com/tianji/trade/TradeApplication" \
|| (echo "ERROR: Wrong jar file!" && exit 1)
ENTRYPOINT ["sh", "-c", "java -jar app.jar"]
5. 如何快速定位类似问题
如果遇到容器在跑但服务调用失败,按这个顺序排查:
1. docker ps → 容器状态正常?
2. docker logs <容器> --tail 100 → 看启动类和注册名是否对
3. Nacos 控制台 → 服务是否注册,实例数对不对
4. docker exec <容器> ls /app → 容器里的 jar 文件名
关键线索:日志里的启动类名和注册服务名。只要这两个跟你预期的不一样,99% 是镜像打错了包。
6. 总结
这个问题的本质是 构建产物与容器镜像之间缺乏精确的一一对应关系。
COPY target/*.jar这种通配符写法在小项目没问题,多模块项目就成了定时炸弹- Jenkins 手动逐个构建时,"点得快" 只是加速了这颗炸弹爆炸
- 让每个模块有自己的 Dockerfile,COPY 精确到具体文件名,是最简单有效的解法
对于学习项目来说,这些坑反而是最宝贵的经验——生产环境的 CI/CD 之所以要做 workspace 隔离、artifact 指纹校验,就是为了防这种事情。
踩坑于:Spring Cloud + Docker + Jenkins 学习环境 容器:tj-trade 镜像内跑着 tj-user 的代码