我第一次把项目跑在 Docker 里

我第一次把项目跑在 Docker 里
踱鸽&水晶蟹我第一次把项目跑在 Docker 里
2020 年下半年,我开始认真研究 Docker。不是主动学的,是被逼的。
项目要迁到新服务器,运维说你们自己搞部署,给你们 Docker 环境。我当时 Docker 基本等于没用过,只知道是”跑容器的东西”。
那两周折腾下来,算是真正理解了容器化到底解决了什么问题。
一、之前的部署方式
容器化之前,我们的部署流程大概是这样的:
- 本地打好 jar 包
- 用 FTP 或 scp 传到服务器
- 手动备份旧包,停掉旧进程
- 启动新包:
nohup java -jar app.jar > app.log 2>&1 & - 等个一两分钟,curl 一下接口看是否正常
每次上线都在走这几步。环境多了就是噩梦——开发环境、测试环境、生产环境,配置文件不一样,JDK 版本要核对,端口要确认,有时候某台机器上依赖的某个 so 库版本不对,就各种奇怪的问题。
“在我机器上能跑”这个问题我是真的遇到过,不是网上才有的梗。测试环境跑好好的,上了生产就报错,排查半天发现是两台服务器的 JDK 小版本不一样。
二、第一次接触 Docker 的契机
就是上面说的那次服务器迁移。
运维直接说:新服务器我装好 Docker 了,其他环境你们自己用容器跑。我当时内心其实有点抗拒——现在的方式虽然麻烦,但我熟悉,改了又要学新东西。
但也没得选,只能硬着头皮上。
第一印象是:比想象中简单。Docker 的概念其实不复杂,镜像就像一个打包好的运行环境,容器就是用这个镜像跑起来的实例。花了一个下午看文档,跑了几个 nginx 容器练手,基本思路就通了。
三、第一个 Dockerfile 的样子
然后开始给自己的项目写 Dockerfile。第一版大概长这样:
|
写完跑了一下,能用。但后来发现了几个问题:
java:8这个镜像很老,官方已经废弃了,应该用openjdk:8-jre-slimADD在这里用COPY更合适(ADD 会尝试解压,COPY 更直观)CMD java -jar app.jar写成这样,信号不会传递给 Java 进程,kill 容器时进程不会优雅退出,应该用ENTRYPOINT ["java", "-jar", "app.jar"]
改了几个版本之后,最终稳定下来的写法:
|
TZ=Asia/Shanghai 这行是必须加的,不然容器里的时区默认是 UTC,日志时间全错。
四、它真正解决了我哪个痛点
最直接的体感是:部署流程变干净了。
以前部署要:传包 → 备份 → 停进程 → 起进程,出了问题要回滚还得找旧包手动操作。现在的流程:
|
回滚也变简单了:把镜像 tag 换成旧版本号,重新 run 一遍就行。
更大的变化是:**”在我机器上能跑”这个问题基本消失了**。镜像里包含了运行时环境(JDK 版本、依赖的系统库),只要镜像是同一个,在哪台机器上跑出来的结果就一样。
五、新的问题
容器化解决了环境一致性,但也带来了新的问题:
日志在哪? 进入容器 docker exec -it myapp bash 才能看,或者提前把日志目录挂载到宿主机(-v /mydata/logs:/app/logs)。我现在统一用挂载,日志文件直接在宿主机看,不用进容器。
配置文件怎么管? 开发、测试、生产用不同的配置,最开始我是在镜像里打了不同的 profile,后来改成用环境变量 -e SPRING_PROFILES_ACTIVE=prod 传进去,配置文件通过挂载或配置中心(Nacos)拉取。
镜像太大怎么办? 第一版镜像打出来有 700MB+,主要是 JDK 基础镜像的问题。换成 jre-slim 后降到 200MB 左右,如果对体积要求高可以用 distroless 基础镜像。
这些问题一一解决下来,慢慢就理解了为什么还需要 Docker Compose——多个容器之间的依赖关系、网络配置,单靠 docker run 管理起来很混乱。再往后,项目多了、机器多了,又开始有了 K8s 的需求。容器化这条路,其实走起来是一步接一步的。


