📚 本系列系统梳理了 Java 开发的详细知识点,从基础语法到工程实践层层递进,内容详实成体系,建议先收藏再慢慢阅读,方便日后随时回顾查阅。

前言

写完 Mini-SSP 后会遇到一个现实问题:项目依赖 MySQL、Redis、还有好几个服务,在自己电脑上配好了,换台机器又要重新装一遍环境。Docker 就是为了解决"在我电脑上能跑,在你电脑上跑不起来"这个经典问题。这篇文章用最直白的方式讲清楚容器是什么、常用命令、怎么打包自己的应用。

1. 什么是容器?

1.1 一个比喻

把你的应用想象成一件货物。

没有容器的年代:你要把货物从 A 地运到 B 地,但 A 地用卡车、B 地用轮船、中转站用火车,每换一种交通工具都要重新打包、重新适配,非常麻烦。

集装箱(Container)出现后:货物先装进标准尺寸的集装箱,不管是卡车、轮船还是火车,都按统一规格搬运集装箱,里面装什么、怎么摆放完全不用关心。

Docker 的 Container(容器)就是软件世界的集装箱——把应用和它需要的一切(代码、运行时、依赖库、配置)打包进一个标准化的"箱子",这个箱子在任何装了 Docker 的机器上都能原样运行。

1.2 解决的核心问题

传统部署:
  开发机:装 JDK 17、MySQL 8.0、Redis 7、配一堆环境变量 → 能跑
  测试机:重新装一遍,版本差一点 → 可能跑不起来
  生产机:再装一遍,漏了个配置 → "在我电脑上明明好好的啊"

Docker 部署:
  把 JDK、应用、依赖全打包成一个镜像
  开发机、测试机、生产机:docker run 同一个镜像 → 处处一致

1.3 容器 vs 虚拟机

很多人会把容器和虚拟机搞混,它们都能"隔离环境",但原理完全不同:

虚拟机(VM):

容器(Container):

维度虚拟机容器
隔离级别完整操作系统进程级隔离
体积几个 GB几十到几百 MB
启动速度分钟级秒级
资源占用高(每个都跑一套 OS)低(共享内核)
隔离强度相对弱(共享内核)

简单说:虚拟机是"模拟一台完整的电脑",容器是"在同一台电脑上隔离出独立的运行空间"。容器更轻、更快,所以现代后端部署几乎都用容器。


2. 三个核心概念

理解 Docker 只需要先搞懂三个词的关系:

概念是什么类比
Dockerfile一个文本文件,写明"怎么打包"菜谱
镜像(Image)按 Dockerfile 打包出来的只读模板按菜谱做好的半成品(冷冻装)
容器(Container)镜像运行起来的实例把半成品加热后端上桌的那盘菜

关键关系:

  • 一个镜像可以运行出多个容器(同一份冷冻半成品,可以加热成多盘菜)
  • 镜像是只读的,容器是可读写的(菜谱和半成品不变,但每盘菜可以单独加调料)
  • 镜像可以分享(推到 Docker Hub,别人 docker pull 就能用,就像把菜谱和半成品寄给别人)

3. 常用命令

3.1 镜像相关

# 从 Docker Hub 拉取镜像
docker pull mysql:8.0           # 拉取 MySQL 8.0 镜像
docker pull redis:7-alpine      # alpine 是精简版 Linux,镜像更小

# 查看本地有哪些镜像
docker images

# 删除镜像
docker rmi mysql:8.0

# 根据 Dockerfile 构建镜像(. 表示当前目录)
docker build -t my-app:1.0 .    # -t 给镜像起名:标签

3.2 容器相关

# 运行容器(最常用)
docker run -d -p 8080:8080 --name my-ssp my-app:1.0
# -d        后台运行(detached)
# -p 8080:8080  端口映射,宿主机8080 → 容器8080
# --name    给容器起个名字,方便后续操作

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

# 查看所有容器(包括已停止的)
docker ps -a

# 停止 / 启动 / 重启容器
docker stop my-ssp
docker start my-ssp
docker restart my-ssp

# 删除容器(要先停止)
docker rm my-ssp

# 查看容器日志
docker logs my-ssp
docker logs -f my-ssp           # -f 持续跟踪输出(类似 tail -f)

# 进入正在运行的容器内部(排查问题用)
docker exec -it my-ssp bash
# -it 表示交互式终端,进去后就像在容器内的 Linux 里操作

3.3 端口映射详解

-p 8080:8080 这个参数最容易迷糊,记住格式是 宿主机端口:容器端口

容器之间是隔离的,不做端口映射,外部就访问不到容器里的服务。


4. 把 Spring Boot 应用打包成镜像

4.1 编写 Dockerfile

在项目根目录创建一个名为 Dockerfile 的文件(没有后缀):

# 第一阶段:用 Maven 镜像编译打包
FROM eclipse-temurin:17-jdk-alpine AS build
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests

# 第二阶段:用更小的 JRE 镜像运行
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/mini-ssp-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

逐行解释:

指令作用
FROM基于哪个基础镜像(站在巨人肩膀上,不用从零搭 Linux)
WORKDIR设置容器内的工作目录(后续命令都在这个目录执行)
COPY把宿主机的文件复制进镜像
RUN构建镜像时执行的命令(这里是编译打包)
EXPOSE声明容器监听的端口(仅作说明,实际映射靠 -p
ENTRYPOINT容器启动时执行的命令(这里是启动 jar)

4.2 为什么要分两个阶段?

上面的 Dockerfile 用了"多阶段构建",目的是减小最终镜像体积

第一阶段(build):需要完整 JDK + Maven 来编译,镜像很大(几百 MB)
                   产物只有一个 jar 包

第二阶段(运行):只需要 JRE 来跑 jar,不需要 JDK 和 Maven
                 从第一阶段只复制 jar 包过来(COPY --from=build)

最终镜像 = 第二阶段的内容(只有 JRE + jar),编译工具全部丢弃

这样最终镜像可能从 600MB 降到 200MB,部署更快、占用更少。

4.3 构建和运行

# 构建镜像
docker build -t mini-ssp:1.0 .

# 运行
docker run -d -p 8080:8080 --name ssp mini-ssp:1.0

# 查看日志确认启动成功
docker logs -f ssp

5. Docker Compose:一键启动多个服务

5.1 解决的问题

Mini-SSP 需要 MySQL、Redis、还有 SSP 应用本身,如果用 docker run 一个个启动,要敲好几条命令,还要处理它们之间的网络连接和启动顺序。Docker Compose 用一个 YAML 文件描述所有服务,一条命令全部启动。

5.2 docker-compose.yml

version: '3.8'
services:

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mini_ssp
    ports:
      - "3306:3306"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  ssp:
    build: .                    # 用当前目录的 Dockerfile 构建
    ports:
      - "8080:8080"
    depends_on:                 # 等 mysql 和 redis 启动后再启动
      - mysql
      - redis
    environment:
      # 注意:容器之间用服务名互相访问,不是 localhost
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mini_ssp
      SPRING_DATA_REDIS_HOST: redis

5.3 关键点:容器间用服务名通信

这是新手最容易踩的坑:

在宿主机上:MySQL 是 localhost:3306
在 ssp 容器里:MySQL 不是 localhost,而是服务名 mysql:3306

原因:每个容器有自己独立的网络空间,容器里的 localhost 指的是
容器自己,不是宿主机。Docker Compose 会自动让同一个 compose
文件里的服务可以用"服务名"互相访问。

所以 SSP 连接 MySQL 的地址要写 jdbc:mysql://mysql:3306/...,这里的 mysql 就是上面定义的服务名。

5.4 常用命令

# 启动所有服务(-d 后台运行)
docker-compose up -d

# 构建并启动(代码改了需要重新构建时)
docker-compose up -d --build

# 查看所有服务状态
docker-compose ps

# 查看日志
docker-compose logs -f ssp

# 停止并删除所有容器
docker-compose down

# 停止但不删除
docker-compose stop

6. 容器的原理(简单理解)

容器能做到"隔离"和"轻量",靠的是 Linux 内核的三个机制:

6.1 Namespace(命名空间)—— 负责隔离

让每个容器以为自己独占整个系统:

进程隔离:容器里 ps 只能看到自己的进程,看不到宿主机和其他容器的进程
网络隔离:每个容器有自己的 IP、端口、网卡
文件隔离:容器里看到的文件系统是独立的

就像每个容器都戴着"眼罩",只能看到属于自己的那部分。

6.2 Cgroups(控制组)—— 负责限制资源

控制每个容器能用多少 CPU、内存:

# 限制容器最多用 512MB 内存、1 个 CPU
docker run -m 512m --cpus 1 my-app

防止某个容器吃光宿主机所有资源,影响其他容器。

6.3 联合文件系统(UnionFS)—— 负责分层

镜像是一层一层叠起来的,每层只记录和上一层的差异:

分层的好处:

  • 复用:多个镜像都基于同一个 JRE 层,这一层只存一份
  • 缓存:构建镜像时,没改动的层直接复用缓存,只重新构建变化的层,速度快

这也解释了为什么 Dockerfile 里"把不常变的放前面、常变的放后面"——前面的层能命中缓存,加快构建。


7. 小结

主题关键要点
容器是什么把应用和依赖打包成标准"集装箱",到处一致运行
容器 vs 虚拟机容器共享宿主机内核,更轻更快;虚拟机模拟完整 OS
三个概念Dockerfile(菜谱)→ 镜像(半成品)→ 容器(端上桌的菜)
常用命令build 构建、run 运行、ps 查看、logs 看日志、exec 进入
端口映射-p 宿主机端口:容器端口
Dockerfile多阶段构建减小镜像体积(编译用 JDK,运行用 JRE)
Docker Compose一个 YAML 启动多服务,容器间用服务名通信
底层原理Namespace 隔离、Cgroups 限资源、UnionFS 分层

对 Mini-SSP 来说,Docker 化之后只需要 docker-compose up 一条命令,就能在任何装了 Docker 的机器上拉起整套环境(MySQL + Redis + SSP + Mock DSP),不用再手动装一堆东西。


下一篇预告:Docker 进阶——数据卷、网络配置与镜像优化


🎯 如果这篇文章对你有帮助,别忘了点赞、收藏、关注三连!关注我,让你在 Java 学习的道路上不迷路,持续为你带来成体系的 Java 干货~

更多推荐