Skip to content

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.0

3.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-service

3.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 缩减到 ~200MB

4.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 → 150MBopenjdk: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 是生产级容器编排平台,功能最全面,是事实标准。