Docker 入门
- [Docker 基础概念与 Java 实战指南](#Docker 基础概念与 Java 实战指南)
-
- [一、Docker 基础概念](#一、Docker 基础概念)
-
- [1.1 什么是 Docker](#1.1 什么是 Docker)
- [1.2 核心概念](#1.2 核心概念)
-
- [1.2.1 镜像(Image)](#1.2.1 镜像(Image))
- [1.2.2 容器(Container)](#1.2.2 容器(Container))
- [1.2.3 仓库(Repository)与 Registry](#1.2.3 仓库(Repository)与 Registry)
- [1.2.4 Docker 架构](#1.2.4 Docker 架构)
- [1.2.5 数据卷(Volume)](#1.2.5 数据卷(Volume))
- [1.2.6 网络(Network)](#1.2.6 网络(Network))
- [1.2.7 Dockerfile](#1.2.7 Dockerfile)
- [1.2.8 Docker Compose](#1.2.8 Docker Compose)
- [二、Java 下 Docker 的常用使用](#二、Java 下 Docker 的常用使用)
-
- [2.1 基础镜像的选择](#2.1 基础镜像的选择)
- [2.2 编写 Dockerfile(以 Spring Boot 为例)](#2.2 编写 Dockerfile(以 Spring Boot 为例))
-
- [2.2.1 单阶段构建(简单但镜像较大)](#2.2.1 单阶段构建(简单但镜像较大))
- [2.2.2 多阶段构建(推荐,减小最终镜像体积)](#2.2.2 多阶段构建(推荐,减小最终镜像体积))
- [2.2.3 常用 Dockerfile 指令详解](#2.2.3 常用 Dockerfile 指令详解)
- [2.3 构建与运行 Java 镜像](#2.3 构建与运行 Java 镜像)
-
- [2.3.1 构建镜像](#2.3.1 构建镜像)
- [2.3.2 运行容器](#2.3.2 运行容器)
- [2.4 使用 Docker Compose 编排 Java 应用](#2.4 使用 Docker Compose 编排 Java 应用)
-
- [2.4.1 示例:Spring Boot + MySQL](#2.4.1 示例:Spring Boot + MySQL)
- [2.4.2 常用 Compose 命令](#2.4.2 常用 Compose 命令)
- [2.5 Java 应用 Docker 最佳实践](#2.5 Java 应用 Docker 最佳实践)
-
- [2.5.1 镜像层优化](#2.5.1 镜像层优化)
- [2.5.2 安全加固](#2.5.2 安全加固)
- [2.5.3 资源限制](#2.5.3 资源限制)
- [2.5.4 日志管理](#2.5.4 日志管理)
- [2.5.5 健康检查](#2.5.5 健康检查)
- [2.5.6 .dockerignore 文件](#2.5.6 .dockerignore 文件)
- [2.5.7 使用环境变量管理配置](#2.5.7 使用环境变量管理配置)
- [2.6 常见问题与调试](#2.6 常见问题与调试)
-
- [2.6.1 容器启动后立即退出](#2.6.1 容器启动后立即退出)
- [2.6.2 端口映射无效](#2.6.2 端口映射无效)
- [2.6.3 容器间通信失败](#2.6.3 容器间通信失败)
- [2.6.4 镜像体积过大](#2.6.4 镜像体积过大)
- [2.7 小结](#2.7 小结)
Docker 基础概念与 Java 实战指南
一、Docker 基础概念
1.1 什么是 Docker
Docker 是一个开源的容器化平台,它允许开发者将应用及其依赖打包到一个轻量级、可移植的容器中,然后在任何支持 Docker 的环境中运行。与传统的虚拟机相比,容器共享宿主机的操作系统内核,因此启动更快、资源占用更少。而虚拟机是包含完整的客户操作系统(Guest OS),模拟了整个OS系统,开销非常大。

1.2 核心概念
1.2.1 镜像(Image)
- 定义 :镜像是只读的模板,包含运行应用程序所需的一切(代码、运行时、系统工具、库、环境变量等)。
- 特点:镜像由多层文件系统(UnionFS)堆叠而成,每一层都是只读的,构建时通过层缓存加速。
- 命名 :
repository:tag,如openjdk:17-jdk-slim,tag 不指定时默认为latest。
1.2.2 容器(Container)
- 定义:容器是镜像的运行实例,在只读镜像层之上添加一个可写层。
- 生命周期:可以创建、启动、停止、删除。容器被删除后,可写层数据丢失(除非使用卷持久化)。
- 隔离性:利用 Linux 内核的 namespace(命名空间)和 cgroups(控制组)实现进程、网络、文件系统的隔离与资源限制。
1.2.3 仓库(Repository)与 Registry
- 仓库:用于存储镜像的集合,通常包含同一个镜像的不同版本(tag)。
- Registry :仓库服务端,例如 Docker Hub(公共)、阿里云镜像服务(私有/公有)。
docker pull和docker push与 Registry 交互。
1.2.4 Docker 架构
- Client(客户端) :用户通过
docker命令与 Docker 守护进程通信。 - Daemon(守护进程):负责管理镜像、容器、网络、卷等,监听客户端请求。
- Registry:存储镜像的远程服务。
1.2.5 数据卷(Volume)
- 目的:持久化和共享容器数据,解决容器可写层数据随容器删除而丢失的问题。
- 类型 :
- 卷(Volume) :由 Docker 管理,存储在宿主机特定目录(
/var/lib/docker/volumes/),推荐使用。 - 绑定挂载(Bind Mount):将宿主机任意目录挂载到容器,便于开发时实时同步代码。
- 临时挂载(tmpfs):仅存于宿主机内存,不持久化。
- 卷(Volume) :由 Docker 管理,存储在宿主机特定目录(
1.2.6 网络(Network)
- 默认网络:bridge(单机桥接网络)、host(与宿主机共享网络)、none(无网络)。
- 自定义网络 :通过
docker network create创建,支持容器间通过容器名通信(内置 DNS)。 - 常用驱动:bridge(默认)、overlay(跨主机)。
1.2.7 Dockerfile
- 定义:文本文件,包含一系列指令,用于自动化构建镜像。
- 关键指令 :
FROM、RUN、COPY、ADD、WORKDIR、EXPOSE、CMD、ENTRYPOINT、ENV、VOLUME。
1.2.8 Docker Compose
- 定义 :用于定义和运行多容器 Docker 应用的工具,通过
docker-compose.yml配置文件一次性管理多个服务。 - 核心概念:services(服务)、networks(网络)、volumes(卷)。
二、Java 下 Docker 的常用使用
2.1 基础镜像的选择
Java 应用的 Docker 镜像通常以官方 OpenJDK 或第三方构建的 JDK 为基础。常见选项:
| 镜像名称 | 特点 | 适用场景 |
|---|---|---|
openjdk:<version>-jdk-slim |
基于 Debian slim,体积较小,包含完整 JDK | 开发、构建 |
openjdk:<version>-jre-slim |
仅包含 JRE,体积更小 | 运行已编译的 Java 应用 |
eclipse-temurin:<version>-jdk-alpine |
基于 Alpine Linux,体积极小,但可能存在 glibc 兼容性问题 | 追求极致镜像大小 |
amazoncorretto:<version>-alpine |
Amazon 提供的免费 OpenJDK 发行版,长期支持 | 生产环境推荐 |
azul/zulu-openjdk-alpine |
Azul 提供的 OpenJDK 构建 | 备选方案 |
注意:Alpine 镜像使用 musl libc 而非 glibc,某些依赖本地库的 Java 应用可能需要额外安装 glibc 或选择 slim 版本。
2.2 编写 Dockerfile(以 Spring Boot 为例)
2.2.1 单阶段构建(简单但镜像较大)
shell
# 使用官方 OpenJDK 17 基础镜像
FROM openjdk:17-jdk-slim
# 设置工作目录
WORKDIR /app
# 将本地的 jar 包复制到容器中
COPY target/myapp-1.0.0.jar /app/app.jar
# 暴露应用端口
EXPOSE 8080
# 启动命令
CMD ["java", "-jar", "app.jar"]
2.2.2 多阶段构建(推荐,减小最终镜像体积)
shell
# 第一阶段:构建阶段(使用完整 JDK)
FROM maven:3.8.6-openjdk-17 AS builder
WORKDIR /build
# 复制 pom.xml 并下载依赖(利用层缓存)
COPY pom.xml .
RUN mvn dependency:go-offline
# 复制源代码并打包
COPY src ./src
RUN mvn clean package -DskipTests
# 第二阶段:运行阶段(使用精简 JRE)
FROM openjdk:17-jre-slim
WORKDIR /app
# 从构建阶段复制生成的 jar 包
COPY --from=builder /build/target/myapp-*.jar ./app.jar
# 创建非 root 用户运行(安全最佳实践)
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
2.2.3 常用 Dockerfile 指令详解
- FROM:指定基础镜像,尽量选择官方镜像且指定具体版本。
- WORKDIR :设置工作目录,后续
RUN,CMD,COPY均以此目录为相对路径。 - COPY / ADD:将本地文件复制到镜像。ADD 支持 URL 和自动解压 tar,通常优先使用 COPY。
- RUN :执行命令(如
mvn package),每个 RUN 会创建一层镜像层,应尽量合并(如使用&&连接多个命令)以减少层数。 - EXPOSE :声明容器运行时监听的端口,仅为文档说明,真正映射需在
docker run时用-p。 - CMD / ENTRYPOINT :指定容器启动时执行的命令。ENTRYPOINT 更适合固定命令,CMD 提供默认参数,二者常结合使用(例如
ENTRYPOINT ["java"],CMD ["-jar", "app.jar"])。 - ENV :设置环境变量(如
ENV JAVA_OPTS="-Xmx512m")。 - USER:切换用户,避免使用 root 运行应用。
- HEALTHCHECK:定义健康检查命令,用于判断容器是否健康。
2.3 构建与运行 Java 镜像
2.3.1 构建镜像
bash
# 在项目根目录(包含 Dockerfile 的位置)执行
docker build -t myapp:1.0.0 .
-t:指定镜像名称和标签。.:构建上下文路径,Docker 会将该路径下的所有文件发送给守护进程(可通过.dockerignore排除不必要文件)。
2.3.2 运行容器
bash
# 基本运行
docker run -d --name myapp -p 8080:8080 myapp:1.0.0
# 设置 JVM 参数
docker run -d --name myapp -p 8080:8080 -e JAVA_OPTS="-Xmx512m -Xms256m" myapp:1.0.0
# 挂载宿主机目录(例如日志或配置文件)
docker run -d --name myapp -p 8080:8080 -v /host/logs:/app/logs myapp:1.0.0
# 指定网络
docker run -d --name myapp --network mynetwork myapp:1.0.0
-d:后台运行。--name:容器名称。-p:宿主机端口:容器端口。-e:设置环境变量。-v:挂载卷或绑定挂载。--network:连接到自定义网络。
2.4 使用 Docker Compose 编排 Java 应用
当应用依赖外部服务(如 MySQL、Redis)时,使用 Docker Compose 可一键启动所有服务。
2.4.1 示例:Spring Boot + MySQL
项目结构:
myapp/
├── my_module
├─────────dockerfile
├── docker-compose.yml
└── ...(源代码等)
docker-compose.yml
yaml
version: '3.8'
services:
# MySQL 数据库服务
mysql:
image: mysql:8.0
container_name: mysql-db
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: mydb
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# Spring Boot 应用
app:
build: .
container_name: spring-app
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mydb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: appuser
SPRING_DATASOURCE_PASSWORD: apppass
- nacos-server-addr=172.17.0.1:8848
- nacos-namespace=UUID 格式的字符串作为 ID(e8b6f5a4-1234-4567-89ab-cdef01234567)
- nacos-group=DEFAULT_GRTOUP
- nacos-username=nacos
- nacos-password=nacos
# 其他 JVM 参数
JAVA_OPTS: "-Xmx512m"
depends_on:
mysql:
condition: service_healthy
networks:
- app-network
# 如果应用需要等待数据库完全就绪,可以添加启动前的命令
# command: sh -c "sleep 20 && java -jar app.jar"
networks:
app-network:
driver: bridge
volumes:
mysql-data:
2.4.2 常用 Compose 命令
bash
# 启动所有服务(后台)
docker-compose up -d
# 停止并删除所有服务、网络
docker-compose down
# 查看日志
docker-compose logs -f app
# 重新构建并启动
docker-compose up -d --build
# 仅构建镜像
docker-compose build
2.5 Java 应用 Docker 最佳实践
2.5.1 镜像层优化
- 合并 RUN 命令 :例如
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*,避免产生中间层。 - 利用构建缓存 :将变化频率低的指令(如
COPY pom.xml)放在前面,频繁变化的(如源码)放在后面。 - 多阶段构建:如前面示例,将构建工具和源码留在最终镜像之外,仅保留运行所需的 jar 包。
2.5.2 安全加固
- 使用非 root 用户 :在 Dockerfile 中创建专用用户并切换(如
USER appuser)。 - 定期更新基础镜像:及时拉取包含安全补丁的镜像。
- 避免敏感信息:不要将密码、密钥写入 Dockerfile,应通过环境变量或 secrets 注入。
2.5.3 资源限制
- 内存限制 :
docker run -m 512m --memory-swap 512m ...或 Compose 中mem_limit: 512m。 - CPU 限制 :
docker run --cpus=1.5或 Compose 中cpus: 1.5。
2.5.4 日志管理
- 将 Java 应用的日志输出到标准输出(stdout)和标准错误(stderr),由 Docker 收集(通过
docker logs查看)。 - 如果需要持久化日志,可以使用挂载卷或 Docker 日志驱动(如
json-file、syslog)。
2.5.5 健康检查
在 Dockerfile 中添加 HEALTHCHECK,或在 Compose 中配置 healthcheck,让编排系统自动监控容器状态。
shell
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
2.5.6 .dockerignore 文件
避免将不需要的文件(如 .git、target/、*.log、node_modules)发送到构建上下文,提高构建速度并减小镜像体积。
示例 .dockerignore:
.git
target/
*.log
.idea
*.iml
2.5.7 使用环境变量管理配置
Spring Boot 支持通过环境变量覆盖配置,推荐在 Compose 中设置 SPRING_ 系列变量,而非硬编码。
2.6 常见问题与调试
2.6.1 容器启动后立即退出
- 原因:主进程执行完毕即退出。检查 CMD/ENTRYPOINT 是否正确,或应用是否有未捕获异常。
- 调试:
docker logs <container>查看日志;或使用docker run -it交互式运行查看输出。
2.6.2 端口映射无效
- 检查
EXPOSE是否声明(非必须但建议),-p参数是否正确,容器内应用是否监听了对应端口。
2.6.3 容器间通信失败
- 使用自定义网络,并通过服务名(Compose)或容器名(
--link已废弃)访问。 - 确保数据库等服务的健康检查通过后再启动应用(使用
depends_on配合condition)。
2.6.4 镜像体积过大
- 使用
docker history <image>查看镜像层大小,定位大文件。 - 采用多阶段构建,删除不必要的依赖和中间文件。
2.7 小结
通过 Docker 容器化 Java 应用,可以保证开发、测试、生产环境的一致性,简化部署流程。关键点在于:
- 选择合适的基础镜像,并采用多阶段构建优化体积。
- 编写高效、安全的 Dockerfile。
- 利用 Docker Compose 管理多服务依赖。
- 遵循最佳实践(非 root 用户、资源限制、健康检查等)。
掌握这些技能后,你可以将 Java 应用轻松部署到任何支持 Docker 的环境中,实现快速交付和弹性伸缩。