Appearance
Docker 详解:容器化
Docker 是微服务部署的基石。它把应用及其依赖打包成镜像,在任何 Linux 环境下都能一致运行,彻底解决"我电脑上能跑"的问题。
一、为什么需要 Docker?
1.1 没有 Docker 时的部署之痛
开发:"我这跑得好好的啊"
运维:"服务器上就是报错,JDK 版本不对"
问题根源:
开发环境:JDK 17 + MySQL 8.0 + Redis 7.0
测试环境:JDK 11 + MySQL 5.7 + Redis 5.0
生产环境:JDK 17 + MySQL 8.0 + Redis 6.0
每个环境配置不同,到处是"环境不一致"的坑。1.2 Docker 如何解决
Docker 镜像 = 应用 + 依赖 + 配置 + 操作系统库,全部打包在一起
┌─────────────────────────────────────┐
│ Docker 镜像 │
│ ┌──────────────────────────────┐ │
│ │ 应用 jar 包(order-service) │ │
│ ├──────────────────────────────┤ │
│ │ JDK 17 │ │
│ ├──────────────────────────────┤ │
│ │ Alpine Linux(5MB) │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
开发环境 → 构建一次镜像 → 测试/预发/生产环境 → 统一运行1.3 Docker vs 虚拟机
虚拟机: Docker:
┌──────────────────┐ ┌──────────────────┐
│ App A │ App B │ │ App A │ App B │
├────────┴─────────┤ ├────────┴─────────┤
│ Guest OS (1GB) │ │ Docker Engine │
├──────────────────┤ ├──────────────────┤
│ Hypervisor │ │ Host OS (共享) │
├──────────────────┤ ├──────────────────┤
│ Host OS │ │ 物理服务器 │
├──────────────────┤ └──────────────────┘
│ 物理服务器 │
└──────────────────┘ 启动:秒级
启动:分钟级 占用:MB 级别
占用:GB 级别 隔离:进程级(轻量)
隔离:完全隔离(重) 数量:单机可跑上百个
数量:单机几十个二、核心概念
2.1 三大核心对象
┌──────────────────────────────────────────────────────────────┐
│ Docker 三大核心对象 │
│ │
│ 镜像(Image) 容器(Container) 仓库(Registry) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 静态的模板 │ │ 运行的实例 │ │ 存放镜像的地方 │ │
│ │ Java 类 │ │ Java 对象 │ │ 代码仓库 │ │
│ │ │ │ │ │ │ │
│ │ docker build │ ──→ │ docker run │ ──→ │ docker push │ │
│ │ 构建镜像 │ │ 启动容器 │ │ 推送到仓库 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 类比: │
│ 镜像 = 光盘(ISO 文件) │
│ 容器 = 播放中的光盘(运行实例) │
│ 仓库 = 光盘架(存放光盘的地方) │
│ │
│ 一个镜像可以启动多个容器,每个容器相互隔离 │
└──────────────────────────────────────────────────────────────┘2.2 镜像分层
Docker 镜像由多层组成,每层只存储差异:
┌──────────────────────────────────────┐
│ 容器层(可写) │ ← 容器运行时写入
├──────────────────────────────────────┤
│ 第 4 层:COPY app.jar │ ← 应用代码
├──────────────────────────────────────┤
│ 第 3 层:RUN mvn package │ ← 构建
├──────────────────────────────────────┤
│ 第 2 层:RUN apt-get install jdk │ ← 依赖安装
├──────────────────────────────────────┤
│ 第 1 层:FROM ubuntu:22.04 │ ← 基础镜像
└──────────────────────────────────────┘
优点:
✅ 镜像层共享:多个镜像共用同一基础层,节省磁盘
✅ 增量更新:只更新变化的层,拉取镜像更快
✅ 版本管理:每层都有唯一 ID,可追溯三、常用命令
3.1 镜像操作
bash
# 搜索镜像
docker search openjdk
# 拉取镜像
docker pull openjdk:17-slim
# 查看本地镜像
docker images
# 删除镜像
docker rmi <image-id>
# 构建镜像
docker build -t order-service:1.0 .
# 打标签
docker tag order-service:1.0 registry.example.com/order-service:1.0
# 推送镜像
docker push registry.example.com/order-service:1.03.2 容器操作
bash
# 启动容器
docker run -d \
--name order-service \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-v /host/logs:/app/logs \
order-service:1.0
# 查看运行中的容器
docker ps
# 查看所有容器(包括已停止)
docker ps -a
# 查看容器日志
docker logs -f order-service
# 进入容器
docker exec -it order-service /bin/bash
# 停止容器
docker stop order-service
# 删除容器
docker rm order-service
# 查看容器资源使用
docker stats order-service3.3 常用参数
| 参数 | 说明 | 示例 |
|---|---|---|
-d | 后台运行 | docker run -d ... |
-p | 端口映射(宿主机:容器) | -p 8080:8080 |
-e | 环境变量 | -e JAVA_OPTS="-Xmx512m" |
-v | 挂载卷(宿主机:容器) | -v /data:/app/data |
--name | 容器名称 | --name order-service |
--network | 指定网络 | --network my-net |
--restart | 重启策略 | --restart=always |
四、Dockerfile
4.1 核心指令
dockerfile
# ============ 基础镜像 ============
FROM openjdk:17-slim
# ============ 元数据 ============
LABEL maintainer="[email protected]"
LABEL version="1.0"
# ============ 工作目录 ============
WORKDIR /app
# ============ 复制文件 ============
COPY target/order-service.jar app.jar
# ============ 运行命令(构建时) ============
RUN mkdir -p /app/logs && chmod 755 /app/logs
# ============ 环境变量 ============
ENV SPRING_PROFILES_ACTIVE=prod
ENV JAVA_OPTS="-Xms256m -Xmx512m"
# ============ 声明端口(文档作用) ============
EXPOSE 8080
# ============ 启动命令 ============
ENTRYPOINT ["java", "${JAVA_OPTS}", "-jar", "app.jar"]4.2 关键指令对比
| 指令 | 执行时机 | 用途 |
|---|---|---|
| RUN | 构建时(build) | 安装软件、创建目录 |
| CMD | 运行时(run) | 默认启动命令,可被覆盖 |
| ENTRYPOINT | 运行时(run) | 容器入口,不可被覆盖 |
| COPY | 构建时 | 复制文件到镜像 |
| ADD | 构建时 | 复制 + 自动解压 tar/URL 下载 |
dockerfile
# CMD vs ENTRYPOINT 区别
# CMD:可被 docker run 后的参数覆盖
CMD ["java", "-jar", "app.jar"]
# docker run image java -jar other.jar ← CMD 被覆盖
# ENTRYPOINT:固定入口,docker run 后的参数追加到后面
ENTRYPOINT ["java", "-jar", "app.jar"]
# docker run image --server.port=9090 ← 追加参数,不会覆盖4.3 多阶段构建
dockerfile
# ============ 阶段 1:构建 ============
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# ============ 阶段 2:运行 ============
FROM openjdk:17-slim
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# 最终镜像大小:只有 JRE + jar,不包含 Maven 和源码
# 从 ~600MB 缩减到 ~200MB4.4 Spring Boot Dockerfile 最佳实践
dockerfile
# 使用分层 JAR,加速镜像构建
FROM openjdk:17-slim AS builder
WORKDIR /app
COPY target/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM openjdk:17-slim
WORKDIR /app
# 分层复制(依赖层变化少,可复用缓存)
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]五、Docker Compose
Docker Compose 用于定义和运行多容器应用。一个 YAML 文件描述所有服务,一条命令全部启动。
5.1 微服务 Compose 示例
yaml
version: '3.8'
services:
# ============ 基础设施 ============
mysql:
image: mysql:8.0
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: order_db
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
nacos:
image: nacos/nacos-server:v2.3.0
container_name: nacos
environment:
MODE: standalone
ports:
- "8848:8848"
- "9848:9848"
# ============ 业务服务 ============
order-service:
build: ./order-service
container_name: order-service
ports:
- "8081:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/order_db
SPRING_REDIS_HOST: redis
SPRING_CLOUD_NACOS_DISCOVERY_SERVER-ADDR: nacos:8848
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
gateway:
build: ./gateway
container_name: gateway
ports:
- "8080:8080"
environment:
SPRING_CLOUD_NACOS_DISCOVERY_SERVER-ADDR: nacos:8848
depends_on:
- nacos
volumes:
mysql-data:
redis-data:常用命令:
bash
# 启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 查看状态
docker-compose ps
# 停止所有服务
docker-compose down
# 重新构建并启动
docker-compose up -d --build
# 只启动某个服务
docker-compose up -d order-service六、镜像优化
6.1 优化策略
| 策略 | 效果 | 示例 |
|---|---|---|
| 选最小基础镜像 | 从 600MB → 150MB | openjdk:17-slim 替代 ubuntu + jdk |
| 多阶段构建 | 构建工具不进最终镜像 | Maven 只放在 builder 阶段 |
| 合并 RUN 指令 | 减少镜像层数 | RUN apt update && apt install -y curl && rm -rf /var/lib/apt/lists/* |
| .dockerignore | 排除不必要文件 | 排除 .git, target, *.log |
| 分层缓存 | 加速构建 | 先 COPY pom.xml 下载依赖,再 COPY 源码 |
6.2 镜像大小对比
优化前(1 个镜像):
FROM ubuntu:22.04 → 77MB
RUN apt install openjdk-17 → +200MB
COPY target/*.jar → +50MB
= 327MB
优化后(多阶段 + slim):
阶段 1:maven:3.9 构建 jar
阶段 2:
FROM openjdk:17-slim → 150MB
COPY --from=builder jar → +50MB
= 200MB
极致优化(jlink + Alpine):
自制 JRE(jlink 裁剪) → 80MB
Alpine Linux → 5MB
jar → 50MB
= ~135MB七、面试要点
Q1:Docker 镜像、容器、仓库的关系?
镜像(Image)是静态模板,容器(Container)是镜像的运行实例,仓库(Registry)是镜像的存储分发中心。类比:镜像是 Java 类,容器是 Java 对象,仓库是 jar 包仓库。
Q2:为什么 Docker 比虚拟机快?
Docker 共享宿主机内核,是进程级隔离;虚拟机需要模拟完整 OS,是硬件级隔离。Docker 启动只需创建进程,毫秒级;虚拟机需要启动 OS,分钟级。
Q3:Dockerfile 中 COPY 和 ADD 的区别?
COPY 只做文件复制,ADD 有额外功能(自动解压 tar、URL 下载)。官方推荐优先使用 COPY,除非需要自动解压功能。
Q4:CMD 和 ENTRYPOINT 的区别?
CMD 提供默认命令,可被 docker run 后的参数覆盖;ENTRYPOINT 是固定入口,参数追加而不会覆盖。通常用 ENTRYPOINT 指定启动命令,CMD 指定默认参数。
Q5:如何减小 Docker 镜像大小?
选最小基础镜像(alpine/slim)、多阶段构建、合并 RUN 指令减少层数、使用 .dockerignore、清理包管理器缓存(rm -rf /var/lib/apt/lists/*)。
Q6:Docker Compose 和 Docker Swarm / K8s 的区别?
Docker Compose 是单机多容器编排,适合开发测试;Docker Swarm 是 Docker 原生集群;K8s 是生产级容器编排平台,功能最全面,是事实标准。
