聊聊Docker - 核心概念速通,镜像、容器、仓库一次弄懂(整理分享)

最近在做优化的时候涉及到了这块内容,觉得值得写下来,方便以后翻阅。

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用轻松的方式讲清楚繁琐的问题。 🎯 本文将围绕Docker这个话题展开,希望能为各位带来一些启发或实用的参考。 🌱 无论各位是刚入门的新手,还是正在进阶的开发者,希望各位都能有所收获!

文章目录

三、容器(Container):镜像的“运行时实例” —— 一个受控的进程沙盒 🧪 四、仓库(Registry):镜像的“Maven Central” —— 团队协作的枢纽 🌐 4.3 私有仓库实践:为什么企业必须用它? 🔐4.4 镜像标签(Tag)的语义化实践:别再用 latest 五、Java 开发者必知:Docker 与 Spring Boot 的深度协同 🌱 5.2 容器内 Java 应用调优:别让 JVM “瞎猜” 🧠5.3 容器网络实战:Spring Boot 连接 MySQL 和 Redis 🌐 六、避坑指南:Java 工程师踩过的 5 个经典 Docker 坑 🚧 七、总结:一张图收束全部核心概念 🧩 八、下一步行动清单:学完就用 🚀

Docker - 核心概念速通,镜像、容器、仓库一次弄懂 🐳

“Docker 不是虚拟机,但它让部署像搭积木一样轻松。” —— 这句话背后,藏着三个看似轻松却常被混淆的核心概念:镜像(Image)、容器(Container) 和 仓库(Registry)。 本文不堆砌术语,不罗列命令,而是用 Java 开发者最熟悉的视角——从 mvn clean package 到 java -jar app.jar,再到 docker run myapp:1.2.0——层层剥开 Docker 的本质逻辑。全程配有可运行的 Java 示例、清晰的 Mermaid 流程图、真实可用的外链资源,以及关键原理的直觉化类比。读完,你将真正理解: ✅ 为什么 docker build 不等于“编译”,而更像“制作标准化安装包”? ✅ 为什么 docker run 启动的是“进程快照”,而非“操作系统副本”? ✅ 为什么 docker push 推送的不是代码,而是可复现的执行环境契约?

一、先问一个灵魂问题:没有 Docker,Java 应用部署有多“脆弱”? 💥

假设你正在维护一个 Spring Boot 微服务:user-service。它依赖:

  • Java 17(OpenJDK)
  • Maven 3.8+
  • MySQL 8.0.33(含特定字符集配置)
  • Redis 7.0(启用 TLS)
  • 环境变量 SPRING_PROFILES_ACTIVE=prodDB_URL=jdbc:mysql://db:3306/userdb
你本地开发一切正常 ✅ 测试环境上线后报错 ❌

Caused by: java.sql.SQLException: Unknown system variable 'default_authentication_plugin'

排查注意到:测试环境 MySQL 是 5.7,而你的 application.yml 中用了 8.0+ 特性。

运维同事说:“换 MySQL 版本太重,我们加个兼容层吧。”
你回复:“那我本地也得降级?CI/CD 流水线怎么改?”
……最终,大家在 Slack 里发了 47 条消息,耗时 3 小时,临时打了补丁。

⚠️ 这就是典型的 环境不一致(Environment Drift)

开发 → 测试 → 预发 → 生产,每个环节的 OS、库版本、配置路径、甚至时区都可能不同。 Java 的 “Write Once, Run Anywhere” 在 JVM 层成立,但在 JVM 之下的操作系统层,它失效了。

Docker 要解决的,正是这个“JVM 之下”的不确定性。它不替代 Java,而是为 Java 提供一个可移植、可声明、可验证的运行基座


二、镜像(Image):一份“只读的、自包含的、可复现的执行环境说明书” 📜

2.1 镜像是什么?—— 类比 Java 的 .jar 文件 🔍

想象你写了一个极简的 Spring Boot Web 应用:

// src/main/java/com/example/hello/HelloController.java
package com.example.hello;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping("/api/hello")
    public String hello() {
        return "Hello from Dockerized Spring Boot! 🌟";
    }
}

mvn clean package 后生成 target/hello-spring-boot-0.0.1-SNAPSHOT.jar
这个 JAR 文件包含了:

  • 编译好的 .class 字节码
  • META-INF/MANIFEST.MF(声明主类、依赖等)
  • BOOT-INF/lib/ 下所有依赖 JAR
  • application.properties 配置(打包进去了)
✅ 它是一个自包含(self-contained) 的交付物:只要有 java -version >= 17,就能运行。

Docker 镜像,就是这个理念在操作系统层的延伸。
它不是一个“操作系统 ISO”,而是一份分层的、只读的、声明式的环境说明书,描述了:

  • 基础操作系统(如 openjdk:17-jre-slim
  • 必需的二进制依赖(如 curl, jq
  • 应用文件(你的 hello-spring-boot-*.jar
  • 启动命令(ENTRYPOINT ["java", "-jar", "/app.jar"]
  • 环境变量、端口暴露、干活目录等元数据
它和 JAR 一样: 🔹 不可变(Immutable):构建后内容固定,无法修改(只能 docker build 新镜像) 🔹 可传输(Transferable):可 push 到远程仓库,pull 到任意机器 🔹 可验证(Verifiable):通过 sha256 摘要唯一标识,确保“所见即所得”
✅ 关键认知:镜像 ≠ 虚拟机镜像。VM 镜像包含完整内核 + 用户空间;Docker 镜像只包含用户空间(rootfs),共享宿主机内核。轻量百倍,启动毫秒级。

2.2 构建一个 Java 镜像:Dockerfile 实战 🛠️

创建项目根目录下的 Dockerfile

# 第一行:指定基础镜像(官方 OpenJDK 17 运行时,Debian Slim 版)
FROM openjdk:17-jre-slim

# 维护者信息(非必需,但推荐)
LABEL maintainer="dev@mycompany.com"

# 创建非 root 用户提升安全性(重要!)
RUN groupadd -g 1001 -f appgroup && useradd -s /bin/bash -u 1001 -g appgroup appuser

# 设置工作目录
WORKDIR /app

# 复制本地 JAR 到镜像中(注意:使用相对路径,避免 COPY 整个 target 目录)
COPY target/hello-spring-boot-0.0.1-SNAPSHOT.jar app.jar

# 暴露应用端口(仅文档作用,实际防火墙需另行配置)
EXPOSE 8080

# 设置运行用户(安全最佳实践)
USER appuser

# 定义启动命令(ENTRYPOINT 更适合封装,CMD 可被覆盖)
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]

💡 解析关键指令:

指令作用类比 JavaFROM指定父镜像,构成镜像的第一层extends 父类(但这里是组合,非继承)COPY将本地文件复制进镜像文件系统maven-assembly-plugin 打包资源EXPOSE声明容器监听端口(不自动映射)@SpringBootApplication 上的 @EnableAutoConfiguration(声明能力,非强制生效)ENTRYPOINT容器启动时执行的默认命令(不易被覆盖)public static void main(String[]) —— 程序入口点

⚠️ 注意:不要写 RUN java -jar app.jar!那是运行时行为,镜像构建阶段应只做“准备”,不“执行”。

构建镜像(确保已 mvn clean package):

docker build -t hello-spring-boot:1.0.0 .

输出类似:

[+] Building 12.5s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 366B                                       0.0s
 => [internal] load .dockerignore                                            0.0s
 => => transferring context: 2B                                              0.0s
 => [internal] load metadata for docker.io/library/openjdk:17-jre-slim     1.2s
 => [1/5] FROM docker.io/library/openjdk:17-jre-slim@sha256:...            10.1s
 => => resolve docker.io/library/openjdk:17-jre-slim@sha256:...              0.0s
 => [2/5] RUN groupadd -g 1001 -f appgroup && useradd ...                   0.5s
 => [3/5] WORKDIR /app                                                       0.0s
 => [4/5] COPY target/hello-spring-boot-0.0.1-SNAPSHOT.jar app.jar          0.1s
 => [5/5] EXPOSE 8080                                                        0.0s
 => exporting to image                                                       0.2s
 => => exporting layers                                                      0.2s
 => => writing image sha256:9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1 0.0s
 => => naming to docker.io/library/hello-spring-boot:1.0.0                  0.0s

✅ 镜像构建完成!现在它是一个静态的、可分发的实体。

2.3 镜像的分层存储机制:为什么 docker build 很快? 🧱

Docker 镜像由只读层(Read-Only Layers)叠加而成。每条 Dockerfile 指令(FROM, RUN, COPY, EXPOSE)都会生成一层。这些层被缓存并复用。

Mermaid 图表直观展示分层结构:

Image hello-spring-boot:1.0.0

Base Layer
openjdk:17-jre-slim

  • Debian rootfs
  • OpenJDK 17 JRE

Layer 2
RUN groupadd & useradd
  • 创建 appgroup/appuser

Layer 3
WORKDIR /app
  • 创建 /app 目录

Layer 4
COPY app.jar
  • 添加 15MB 的 jar 包

Layer 5
EXPOSE 8080
  • 元数据:声明端口

Layer 6
ENTRYPOINT ...
  • 元数据:定义启动命令

✅ 分层优势:

  • 高效复用:若只改 app.jarCOPY 指令),只有 Layer 4 及之后层重建,前面 3 层直接复用缓存。
  • 节省空间:多个镜像共享相同基础层(如 openjdk:17-jre-slim),物理磁盘只存一份。
  • 安全审计:可逐层检查 RUN apt-get update 是否引入风险包。
🔗 想深入理解分层?访问 Docker 官方文档:Images and layers(权威、实时更新、无登录墙)

三、容器(Container):镜像的“运行时实例” —— 一个受控的进程沙盒 🧪

3.1 容器是什么?—— 类比 java -jar app.jar 的进程 🔗

当你执行:

java -jar hello-spring-boot-0.0.1-SNAPSHOT.jar

发生了什么?

  • JVM 启动一个 java 进程(PID=1234)
  • 加载 app.jar 和所有依赖到内存
  • 绑定 localhost:8080(或失败,因端口被占)
  • 进程生命周期:从 main() 开始,到 System.exit() 或异常终止
Docker 容器,就是这个过程的增强版: 它把 java -jar app.jar 这个命令,放到一个隔离的、资源受限的、网络独立的 Linux 命名空间(Namespace)和控制组(Cgroup) 中运行。
✅ 容器 = 镜像 + 可写层(Container Layer) + 隔离运行时环境(Namespaces + Cgroups)
  • 可写层(Top Layer):容器启动时,在镜像最上层叠加一个可读写层。所有运行时改动(如 touch /tmp/log.txt, echo "ok" > status)都发生在此层。容器停止后,此层默认丢弃(除非用 -v 挂载卷)。
  • 命名空间(Namespaces):提供视图隔离
pid:进程 PID 隔离 → 容器内 ps aux 只见到自己的进程
  • net:网络栈隔离 → 容器有自己 lo, eth0,IP 独立
  • mnt:文件系统挂载点隔离 → df -h 只显示容器视角的磁盘
控制组(Cgroups):提供资源限制
  • --memory=512m:限制最大内存使用
  • --cpus=1.5:限制最多使用 1.5 个 CPU 核心
  • --pids-limit=100:限制最多 100 个进程

3.2 启动一个容器:docker run 的全貌 🚀

运行刚构建的镜像:

docker run \
  --name hello-app \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_URL=jdbc:mysql://mysql-host:3306/userdb \
  --rm \
  hello-spring-boot:1.0.0

参数详解:

参数作用类比 Java--name hello-app为容器指定易记名称Thread.currentThread().setName("hello-worker")-p 8080:8080将宿主机 8080 端口映射到容器 8080 端口ServerSocket server = new ServerSocket(8080)(但绑定的是容器网络)-e KEY=VALUE向容器注入环境变量System.setProperty("spring.profiles.active", "prod")--rm容器退出后自动删除(清理可写层)try { ... } finally { cleanup(); }(自动资源回收)

✅ 启动后,你会见到 Spring Boot 的标准日志:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.0)

2024-04-10T10:22:33.123Z  INFO 1 --- [           main] c.e.h.HelloSpringBootApplication       : Started HelloSpringBootApplication in 2.1 seconds (process running for 2.5)

此时,打开浏览器访问 http://localhost:8080/api/hello → 显示 "Hello from Dockerized Spring Boot! 🌟"

3.3 容器 vs 虚拟机:一张图看懂本质差异 🆚

宿主机操作系统
Linux Kernel 6.5

Docker 容器方案

直接调用系统调用

直接调用

直接调用

Linux Kernel

  • 进程调度
  • 内存管理
  • 网络协议栈
  • 设备驱动

Docker Engine
(守护进程 dockerd)

Container Layer
(可写层,/tmp, /var/log)

Image Layers
(只读层:OpenJDK + app.jar)

虚拟机方案

Hypervisor
(如 VirtualBox/KVM)

Guest OS Kernel
(完整 Linux Kernel)

Java App
(运行在 Guest OS 上)

核心结论:

维度虚拟机(VM)Docker 容器启动速度秒级(启动 Guest OS)毫秒级(启动进程)资源占用GB 级(完整 OS 内存 + 磁盘)MB 级(仅应用 + 运行时)隔离强度强(硬件级,完全独立内核)中(OS 级,共享宿主机内核)适用场景运行不同 OS(Windows/Linux)、强安全隔离需求微服务、CI/CD、开发环境一致性

🔗 对隔离原理好奇?推荐阅读 Red Hat 官方文章:How containers work(图文并茂,原理清晰)

3.4 容器生命周期管理:不只是 run 和 stop 🔄

容器是短暂的(Ephemeral)。掌握其状态流转至关重要:

created

docker rm -f

docker run/start

docker start

docker pause

docker unpause

exit code / SIGTERM

docker start

docker rm

exec ENTRYPOINT/CMD

running

healthcheck passes

healthcheck fails 3x

healthcheck recovers

healthy

unhealthy

paused

exited

removed

waiting_for_start

starting

Healthcheck is defined in Dockerfile:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1

常用命令实战:

# 查看正在运行的容器
docker ps

# 查看所有容器(包括已退出)
docker ps -a

# 查看容器日志(实时跟踪)
docker logs -f hello-app

# 进入容器执行命令(调试用,生产慎用)
docker exec -it hello-app /bin/sh

# 发送信号优雅停止(触发 Spring Boot 的 Shutdown Hook)
docker stop hello-app

# 强制杀死(类似 kill -9)
docker kill hello-app

# 删除已停止容器
docker rm hello-app

# 删除镜像(需先删容器)
docker rmi hello-spring-boot:1.0.0

💡 生产最佳实践:

  • 永远使用 --rm 或明确 docker rm 清理,避免磁盘被僵尸容器占满。
  • HEALTHCHECK 定义健康探针,让编排工具(如 Kubernetes)清楚何时重启。
  • 容器内永不运行多个主进程(如 nginx + java),应拆分为多个容器(nginx-container + app-container)。

四、仓库(Registry):镜像的“Maven Central” —— 团队协作的枢纽 🌐

4.1 仓库是什么?—— 类比 Maven 仓库的核心定位 📦

Maven 项目 pom.xml 中:


    org.springframework.boot
    spring-boot-starter-web
    3.2.0

Maven 如何找到这个 JAR?
→ 查询 https://repo.maven.apache.org/maven2/(中央仓库)
→ 下载 org/springframework/boot/spring-boot-starter-web/3.2.0/...jar
→ 本地缓存 ~/.m2/repository/

Docker Registry 就是这个逻辑的镜像版:
它是一个 HTTP 服务,用于:

  • 存储 镜像的分层数据(tar 包 + JSON 元数据)
  • 索引 镜像标签(tag),如 hello-spring-boot:1.0.0, hello-spring-boot:latest
  • 认证 用户权限(谁可以 push?谁可以 pull?)
  • 分发 镜像到全球任何一台装了 Docker 的机器
✅ 仓库 ≠ Docker Hub。Docker Hub 是一个公共 Registry 实例(由 Docker Inc. 运营),就像 Maven Central 是 Apache 运营的公共 Maven 仓库。你完全可以搭建私有 Registry(如 Harbor、AWS ECR、阿里云 ACR)。

4.2 使用 Docker Hub:公共镜像的获取与推送 🚪

▶ 获取公共镜像(docker pull)

# 拉取官方 OpenJDK 镜像(无需指定 registry,缺省为 Docker Hub)
docker pull openjdk:17-jre-slim

# 拉取 Nginx(Web 服务器,常作反向代理)
docker pull nginx:alpine

# 拉取 MySQL(数据库)
docker pull mysql:8.0

这些镜像都托管在 Docker Hub —— 全球最大的公共容器镜像仓库。你可以在这里搜索、查看 Dockerfile、阅读文档。

🔗 访问 Docker Hub 官网(可直接搜索 openjdk,查看官方镜像详情页)
▶ 推送私有镜像(docker push)

要推送,需先登录:

docker login
# 输入 Docker ID 和密码(非邮箱!)

之后给镜像打上符合 username/repository:tag 格式的标签:

# 查看当前镜像 ID
docker images | grep hello-spring-boot

# 假设 IMAGE ID 是 9a8b7c6d5e4f
docker tag 9a8b7c6d5e4f your-docker-id/hello-spring-boot:1.0.0

# 推送到 Docker Hub
docker push your-docker-id/hello-spring-boot:1.0.0

✅ 推送成功后,任何人只要执行:

docker pull your-docker-id/hello-spring-boot:1.0.0

就能获得完全一致的镜像——无论他在东京、法兰克福还是圣保罗。这就是“一次构建,处处运行”的基石。

4.3 私有仓库实践:为什么企业必须用它? 🔐

Docker Hub 有明显局限:

  • ❌ 免费账户仅支持 1 个私有仓库
  • ❌ 无细粒度权限控制(如:研发组只能 pull,运维组可 push
  • ❌ 无漏洞扫描(CVE 检测)
  • ❌ 网络延迟高(国内访问慢)
企业级方案:Harbor(CNCF 毕业项目,开源免费)
  • ✅ Web UI 管理镜像、项目、用户
  • ✅ RBAC 权限模型(project-admin, developer, guest
  • ✅ 集成 Trivy 扫描镜像漏洞
  • ✅ 支持 Helm Chart 仓库(统一管理容器和 K8s 应用)
部署 Harbor 只需几行命令(基于 Docker Compose):

# 下载离线安装包(官网提供)
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz

# 解压并编辑 harbor.yml(配置域名、证书、存储路径)
tar xvf harbor-offline-installer-v2.10.0.tgz
cd harbor
vi harbor.yml  # 设置 hostname: harbor.mycompany.com

# 安装(自动拉取所需镜像并启动)
sudo ./install.sh

🔗 获取 Harbor 最新安装指南:Harbor 官方文档

4.4 镜像标签(Tag)的语义化实践:别再用 latest ❌

latest 是 Docker 默认标签,但它是个“移动目标”:

docker pull nginx      # 等价于 docker pull nginx:latest
# 下次 pull,可能已是全新大版本(如 1.25 → 1.26),导致兼容性问题!

✅ 推荐策略(参考 Semantic Versioning 2.0):

标签类型示例用途安全性精确版本hello-spring-boot:1.0.0生产环境部署✅ 最高(不可变)次要版本hello-spring-boot:1.0CI/CD 流水线自动构建⚠️ 中(1.0.x 自动更新)核心版本hello-spring-boot:1基础设施测试(如兼容性验证)⚠️ 低(1.x.y 自动更新)Git Commithello-spring-boot:abc1234追溯构建来源✅ 高(唯一)时间戳hello-spring-boot:20240410-1422日常构建快照✅ 高(唯一)

Dockerfile 中,永远显式指定基础镜像版本:

# ✅ GOOD:锁定 OpenJDK 版本
FROM openjdk:17.0.2-jre-slim

# ❌ BAD:使用 latest,下次构建可能失败
# FROM openjdk:latest


五、Java 开发者必知:Docker 与 Spring Boot 的深度协同 🌱

5.1 Spring Boot 3.x 原生支持容器化:jlink 与 layers.idx 🎯

Spring Boot 2.3+ 引入 BuildpacksLayered JAR,大幅优化容器体验。

▶ 方案一:使用 Buildpacks(零 Dockerfile)

Maven 插件自动构建 OCI 镜像:


            org.springframework.boot
            spring-boot-maven-plugin

                    paketobuildpacks/builder-jammy-base
                    your-registry.com/myapp:${project.version}

执行:

mvn spring-boot:build-image

✅ 自动生成优化镜像(基于 distroless,无 Shell,更小更安全),无需手写 Dockerfile

▶ 方案二:分层 JAR(Layered JAR)加速构建

Spring Boot 2.3+ 将 app.jar 拆分为多层:

unzip -l target/hello-spring-boot-0.0.1-SNAPSHOT.jar | head -20

输出含:

META-INF/
META-INF/MANIFEST.MF
BOOT-INF/
BOOT-INF/classes/
BOOT-INF/lib/               ← 依赖层(变化少,可缓存)
BOOT-INF/layers.idx         ← 层定义文件

layers.idx 内容示例:

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/resources/"

配合 Dockerfile 利用分层:

# 利用 Spring Boot 分层,实现极速 rebuild
FROM eclipse/jetty:11-jre17-slim

# 复制分层 JAR
ARG JAR_FILE=target/hello-spring-boot-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar

# 提取分层(关键!)
RUN java -Djarmode=layertools -jar app.jar extract

# 按层复制(依赖层复用率高)
FROM eclipse/jetty:11-jre17-slim
COPY --from=0 dependencies/ ./
COPY --from=0 spring-boot-loader/ ./
COPY --from=0 snapshot-dependencies/ ./
COPY --from=0 application/ ./

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

✅ 效果:当只改业务代码(application 层),docker build 仅需复制最后一层,秒级完成。

5.2 容器内 Java 应用调优:别让 JVM “瞎猜” 🧠

在容器中,JVM 默认行为可能出错:

  • Runtime.getRuntime().availableProcessors() 返回宿主机 CPU 数,而非容器限制值
  • -Xmx 未设置时,JVM 用宿主机内存的 1/4,远超容器 --memory=512m 限制 → OOM Kill!
✅ 正确做法(Spring Boot 2.2+ 自动适配):

# application.yml
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus

# JVM 启动参数(推荐写在 ENTRYPOINT 或 docker run -e JAVA_TOOL_OPTIONS)
JAVA_TOOL_OPTIONS: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

  • -XX:+UseContainerSupport:启用容器感知(JDK 10+ 默认开启)
  • -XX:MaxRAMPercentage=75.0:JVM 堆最大为容器内存限制的 75%
验证是否生效:

docker run --rm -m 512m hello-spring-boot:1.0.0 java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
# 输出应为约 384MB (512 * 0.75),而非数 GB

5.3 容器网络实战:Spring Boot 连接 MySQL 和 Redis 🌐

典型微服务架构:

[Spring Boot App]  [Nginx]
       ↓
[MySQL]  [Redis]  [RabbitMQ]

在容器中,服务间通信靠 Docker 网络,而非 localhost

▶ 步骤 1:创建自定义网络(推荐)

docker network create myapp-network

▶ 步骤 2:启动 MySQL(带初始化脚本)

docker run -d \
  --name mysql-db \
  --network myapp-network \
  -e MYSQL_ROOT_PASSWORD=root123 \
  -e MYSQL_DATABASE=userdb \
  -v $(pwd)/init.sql:/docker-entrypoint-initdb.d/init.sql \
  -p 3306:3306 \
  mysql:8.0

▶ 步骤 3:启动 Redis

docker run -d \
  --name redis-cache \
  --network myapp-network \
  -p 6379:6379 \
  redis:7-alpine

▶ 步骤 4:启动 Spring Boot App(使用服务名作为 host)

docker run -d \
  --name hello-app \
  --network myapp-network \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=docker \
  -e SPRING_DATASOURCE_URL=jdbc:mysql://mysql-db:3306/userdb \
  -e SPRING_REDIS_HOST=redis-cache \
  hello-spring-boot:1.0.0

✅ 关键点:mysql-dbredis-cache 是容器名,Docker 内置 DNS 会将其解析为对应 IP。无需硬编码 IP!

🔗 学习 Docker 网络原理:Docker 官方网络教程

六、避坑指南:Java 工程师踩过的 5 个经典 Docker 坑 🚧

❌ 坑 1:在容器里用 localhost 连接其他容器

现象Connection refused
原因localhost 指向容器自身,而非宿主机或其他容器
解法:用 --network + 容器名(如 mysql-db),或用 host.docker.internal(Mac/Win,Linux 需 --add-host=host.docker.internal:host-gateway

❌ 坑 2:docker build 时 mvn clean package 太慢

现象:每次构建都重新下载 Maven 依赖
解法:利用 Docker BuildKit 的 --mount=type=cache

# 开启 BuildKit:export DOCKER_BUILDKIT=1
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
COPY pom.xml .
# 缓存 ~/.m2/repository
RUN --mount=type=cache,target=/root/.m2/repository \
    mvn dependency:go-offline
COPY . .
RUN mvn clean package -DskipTests

FROM openjdk:17-jre-slim
COPY --from=builder /app/target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

❌ 坑 3:日志不输出到 docker logs

现象docker logs hello-app 为空
原因:Spring Boot 默认将日志写入 ./logs/ 文件,而非 stdout
解法:在 application.yml 中配置:

logging:
  level:
    root: INFO
  pattern:
    console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: "logs/app.log"  # 仍可写文件,但 console 必须开启

❌ 坑 4:容器时间与宿主机不同步

现象:JWT Token 报 Invalid JWT timestamp
原因:容器内时钟未同步
解法:启动时挂载宿主机时间:

docker run -v /etc/localtime:/etc/localtime:ro ...

❌ 坑 5:java.io.tmpdir 空间不足

现象java.lang.InternalError: XXX 或 PDF 生成失败
原因:Docker 默认 /tmp 很小(64MB)
解法:启动时指定更大 tmpdir:

docker run -e JAVA_TOOL_OPTIONS="-Djava.io.tmpdir=/tmp-large" \
  -v /tmp-large:/tmp-large ...


七、总结:一张图收束全部核心概念 🧩

Ecosystem

编写

docker build

docker run

docker commit

docker push

docker pull

开发者

Dockerfile

镜像 Image

  • 只读分层
  • 声明式环境
  • sha256 唯一标识

容器 Container
  • 可写顶层
  • Namespaces 隔离
  • Cgroups 限流
  • 进程沙盒

新镜像
(不推荐,破坏不可变性)

仓库 Registry

  • 存储中心
  • 权限控制
  • 漏洞扫描

Docker Hub
public

Harbor
private

AWS ECR
cloud

✅ 终极一句话总结:

镜像是“Java 的 .jar”,容器是“java -jar 的进程”,仓库是“Maven Central”。 Docker 没有发明新概念,它只是把 Java 工程师早已熟稔的“封装-分发-运行”范式,完美迁移到了操作系统层。

八、下一步行动清单:学完就用 🚀

1. ✅ 立刻实践:把你当前的 Spring Boot 项目,按本文流程写一个 Dockerfiledocker builddocker run 起来。
2. ✅ 升级构建:尝试 mvn spring-boot:build-image,对比传统 Dockerfile 的体积与构建速度。
3. ✅ 接入仓库:注册 Docker Hub,docker push 你的镜像,并让同事 docker pull 验证。
4. ✅ 学习编排:用 docker-compose.yml 编排 MySQL + Redis + 你的 App,一键启动整套环境。
5. ✅ 深入原理:阅读 Linux NamespacesCgroups v2 官方文档,理解底层魔法。

🌟 记住:Docker 的价值不在技术本身,而在于它消除了环境摩擦,让开发者专注业务逻辑。当你不再为“在我机器上是好的”而争论,真正的高效协作才开始。

祝你 Docker 之旅顺畅,代码世界澄澈如初 🐳✨
—— 写于一个容器正在后台安静运行的清晨


🙌

以上就是这次整理的全部内容,希望对你有所启发。如果有不同见解,欢迎在评论区交流讨论。

评论 (0)

暂无评论