news 2026/10/10 6:38:50

Docker 学习总结(下):数据卷、Compose 与部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker 学习总结(下):数据卷、Compose 与部署实战

一、从跑一个容器到跑一堆容器

上篇学完,我已经能把 hm-service 打成镜像、用docker run跑起来了。但黑马商城是微服务项目,订单、商品、网关好几个服务,外加 MySQL、Redis 这些中间件,每个都手敲一遍docker run,参数又长又容易抄错,改一次配置就得全部重来。

所以下篇解决两件事:容器里的数据怎么不丢(数据卷),多个容器怎么一起管(Compose),最后把 hm-service 完整部署一遍。


二、数据卷:容器的"外置硬盘"

最早起 MySQL 容器的时候忘了挂-v。中途 restart 了几次都没事,数据还在,我就以为没问题了。后来rm重跑,库和表全没了。

当时我盯着空荡荡的数据库,脑子里只有一个想法:我明明没删数据啊。愣了几秒才反应过来——数据一直在容器里,容器没了,数据就没了。

restart 不会丢数据,rm才会。这一点我后来才弄清楚:restart 只是重启容器,可写层还在;rm才是把容器和它的可写层一起删掉。

数据卷就是为这个场景准备的,把宿主机目录挂载进容器:

dockerrun-vmysql-data:/var/lib/mysql mysql

-v后面的格式是"宿主机路径(或卷名):容器路径"。容器往这个路径里写的数据,其实落到了宿主机上。容器删了数据还在,重建一个容器挂同一个目录,数据接着用。

用法上大概分两类:MySQL 数据目录、Redis 持久化文件这类必须保住的,挂命名卷或固定目录;配置文件、日志目录这类经常改的,用绑定挂载,宿主机上改完容器里立刻生效,不用重新打镜像。

命名卷和绑定挂载的区别也弄清楚了:docker volume create出来的命名卷由 Docker 统一管理,位置藏在/var/lib/docker/volumes下面,省心但不好直接摸到;绑定挂载就是自己指定宿主机路径,位置透明。数据求稳用命名卷,配置求好选用绑定挂载。

从那以后起中间件之前,我会先问自己一句:这容器里有什么数据是不能丢的。


三、Docker Compose:用 YAML 管理多容器

服务一多,一个个docker run就撑不住了:命令越敲越长,启动顺序没法保证,参数也没个记录。Compose 的思路很直接——把所有docker run收进一个docker-compose.yml:

services:hm-service:build:.ports:-"8081:8080"environment:-DB_HOST=host.docker.internaldepends_on:mysql:condition:service_healthyrestart:alwaysmysql:image:mysql:8volumes:-mysql-data:/var/lib/mysqlhealthcheck:test:["CMD","mysqladmin","ping"]interval:5sretries:10volumes:mysql-data:

我挑几个容易和docker run搞混的字段说一下:

  • services:下面每个键就是一个容器
  • image/build:用现成镜像写image,要从 Dockerfile 构建写build
  • ports:等价于-p,“宿主机:容器”
  • environment:等价于-e,塞环境变量
  • depends_on:声明启动顺序。默认只保证"mysql 容器先启动",配合condition: service_healthy才能真正等到 MySQL 就绪
  • volumes:数据卷,用法和-v一样;顶层的volumes用来声明命名卷
  • restart:容器挂了自动拉起,always表示除非手动停,否则一直重启
  • healthcheck:定期检查容器里的服务是否真的可用

剩下的字段用到的时候查文档就行,不用背。

depends_on 只管启动,不管就绪

一开始只写了depends_on: - mysql,以为这样 hm-service 就会等 MySQL 准备好了再启动。结果 hm-service 起来之后还是报连接失败——因为它只保证"mysql 容器启动了",不保证"mysql 服务能连了"。

后来在 mysql 上加了 healthcheck,再用condition: service_healthy,才能真正等到 MySQL 可以接受连接了再启动 hm-service:

depends_on:mysql:condition:service_healthy

healthcheck 里的test是检查命令,mysqladmin ping能通就说明 MySQL 活着。interval是检查间隔,retries是失败重试次数,超过次数 Docker 就认为容器不健康。

还有一个点:restart: always是生产部署的标配。学习阶段可能感觉不到它的价值,但容器因为意外挂了、服务器重启了,这行配置能让服务自动恢复,不用人手动去拉。部署实战里,这行配置比任何优化都实在。

常用命令

命令作用
docker compose up -d后台启动所有服务
docker compose down停止并删除容器(默认保留命名卷)
docker compose logs -f实时查看日志

Compose 不是"另一个 Docker",它只是把散落的 docker 命令文件化。yml 里每个字段几乎都能对应到上篇学过的某个命令。


四、部署实战:把 hm-service 跑起来

这次把上篇零散学的东西串了一遍。先mvn package -DskipTests打出 jar,然后基于openjdk:17-jdk-slim写 Dockerfile,docker build构建,最后:

dockerrun-d--namehm-service-p8081:8080-eDB_HOST=host.docker.internal hm-service:1.0

起容器。端口映射 8081:8080,浏览器访问宿主机 8081,流量转进容器的 8080。

构建和启动都没出问题,结果服务一起来就抛反射错误——MyBatis-Plus 序列化 lambda 表达式时反射访问 SerializedLambda,抛出InaccessibleObjectException:

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.lang.Class java.lang.invoke.SerializedLambda.capturingClass accessible: module java.base does not "opens java.lang.invoke" to unnamed module @27abe2cd at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) at com.baomidou.mybatisplus.core.toolkit.SetAccessibleAction.run(SetAccessibleAction.java:18) at com.baomidou.mybatisplus.core.toolkit.SerializedLambdaMeta.<clinit>(SerializedLambdaMeta.java:19) at com.baomidou.mybatisplus.core.toolkit.LambdaUtils.extract(LambdaUtils.java:55)

查了一下,根源在 JDK 9 引入的模块系统:JDK 16 之后强封装变成默认行为,java.base不再对未命名模块开放java.lang.invoke这些内部包,MyBatis-Plus 反射读 SerializedLambda 内部字段时正好被拦住。平时在本地 IDEA 里跑感觉不到,IDE 往往自带各种宽容参数;换成干净的容器,就现原形了。

报错信息里写清了是哪个包没开,对着加--add-opens就行:

ENTRYPOINT ["java", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED", "-jar", "/app.jar"]

改完重新 build,服务顺利起来。

那一刻我有点后怕。如果不是容器逼着我把环境暴露出来,我可能一直不知道自己的项目依赖了 IDE 的宽容参数。"在我电脑上能跑"这句话,在容器面前毫无意义。


五、小结

下篇补上了数据卷、Compose、hm-service 部署实战,外加一个印象很深的 JDK 17 反射坑。连同上篇的镜像、容器、Dockerfile 和网络,Docker 基础部分到这里闭环了。

两篇写完回头看,Docker 最核心的东西其实就两个:分层和隔离。镜像分层叠加,容器各自隔离。Compose 管的是把这一堆隔离的东西组合起来,数据卷管的是隔离边界上的持久化。上篇的镜像、容器、网络,下篇的数据卷、Compose,说到底都是围绕这两个词在转。

难的真不是命令,是理解背后的设计思想。命令忘了随时能查文档,思想通了,遇到没见过的报错也知道往哪儿查。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 6:38:27

Java调用Jenkins API实战:从触发构建到获取日志的完整指南

干我们这行的&#xff0c;谁没被“手动构建”这件事折磨过&#xff1f;明明代码已经提交了&#xff0c;还得登录Jenkins页面&#xff0c;找到对应的Job&#xff0c;小心翼翼地填参数&#xff0c;点一下“立即构建”&#xff0c;然后眼巴巴盯着进度条&#xff0c;生怕构建失败了…

作者头像 李华
网站建设 2026/10/10 6:37:50

婚礼策划网站WordPress建站方案:从部署到获客全攻略

1. 婚礼策划行业为什么需要一套专属的 WordPress 方案做婚礼策划这行&#xff0c;我接触过不少工作室和独立策划师。大家普遍有个误区&#xff1a;觉得做个网站就是放几套案例照片、留个联系方式&#xff0c;随便找个模板套一下就行。结果真上线后&#xff0c;发现要么加载慢得…

作者头像 李华
网站建设 2026/10/10 6:36:05

EOS 8.3.2移动端隐藏底部“流程发起”按钮的完整指南

直接说结论&#xff1a;能去&#xff0c;而且不需要动业务代码&#xff0c;甚至不需要重新发版。EOS 8.3.2 移动端底部那个“流程发起”按钮&#xff0c;十有八九不是你们业务系统画的&#xff0c;而是微前端框架或者移动端壳子自带的快捷入口。这种坑我踩过好几次&#xff0c;…

作者头像 李华
网站建设 2026/10/10 6:35:39

Java集合框架底层原理与性能优化:从ArrayList到HashMap

Java里的集合框架&#xff0c;很多开发者从学习第一天就开始用&#xff0c;ArrayList存数据、HashMap做缓存&#xff0c;写着写着就成了肌肉记忆。但真正问你几个问题——ArrayList扩容到底怎么扩的&#xff1f;HashMap在JDK 8里引入红黑树是为什么&#xff1f;遍历的时候删元素…

作者头像 李华
网站建设 2026/10/10 6:35:31

纯CSS侧边伸缩导航栏:复选框Hack与:target方案详解及避坑

简介&#xff1a;这是一份基于原生 HTML 与 CSS 实现的侧边伸缩导航栏网页源码&#xff0c;适合前端初学者或需要快速搭建后台管理界面侧边菜单的开发者。资源不依赖复杂框架&#xff0c;重点演示按钮控制展开/关闭、子菜单显隐、过渡动画及响应式适配等核心交互。压缩包共 9 个…

作者头像 李华