一、 Docker 基础知识速览
1. 核心三要素
- 镜像(Image):只读的模板,包含运行应用所需的代码、运行时、库和配置。相当于面向对象中的"类"。
- 容器(Container):镜像的运行实例。容器被创建时,会在镜像层之上添加一个可写层。相当于面向对象中的"对象"。
- 仓库(Registry):集中存储和分发镜像的服务。如 Docker Hub(公有)、Harbor(私有)。
2. 底层核心原理
- Namespace(命名空间):实现"视图隔离"。让容器拥有独立的 PID(进程)、Network(网络)、Mount(挂载)、IPC(进程间通信)等,使容器看起来像一台独立的机器。
- Cgroups(控制组):实现"资源限制"。限制容器能使用的 CPU、内存、磁盘 I/O 等物理资源上限,防止单个容器耗尽宿主机资源。
- UnionFS(联合文件系统):实现"镜像分层"。将多个目录(层)挂载到同一个虚拟文件系统下。镜像层是只读的,容器层是可写的,通过写时复制(CoW)机制提高存储和启动效率。
二、 Docker 常见核心命令大全
1. 镜像管理命令
bash
# 从仓库拉取镜像
docker pull nginx:latest
# 查看本地镜像列表
docker images
docker image ls
# 搜索仓库中的镜像
docker search redis
# 删除本地镜像(-f 强制删除)
docker rmi nginx:latest
docker rmi -f <image_id>
# 为镜像打标签(常用于推送到私有仓库)
docker tag myapp:1.0 registry.mycompany.com/myapp:1.0
# 将镜像推送到仓库
docker push registry.mycompany.com/myapp:1.0
2. 容器生命周期命令
bash
# 启动容器(最常用组合:后台运行、端口映射、命名、自动重启)
docker run -d -p 8080:80 --name myweb --restart unless-stopped nginx
# 查看运行中的容器(-a 查看所有包括已停止的)
docker ps
docker ps -a
# 启动 / 停止 / 重启容器
docker start <container_name_or_id>
docker stop <container_name_or_id>
docker restart <container_name_or_id>
# 删除容器(-f 强制删除运行中的容器)
docker rm <container_name_or_id>
docker rm -f <container_name_or_id>
3. 容器运维与排查命令
bash
# 查看容器日志(-f 实时跟踪,--tail 指定行数)
docker logs -f --tail 200 <container_name_or_id>
# 进入正在运行的容器内部(推荐方式,退出不会导致容器停止)
docker exec -it <container_name_or_id> /bin/bash
# 若基础镜像为 alpine,则使用 sh
docker exec -it <container_name_or_id> /bin/sh
# 查看容器详细信息(JSON格式,常用于排查网络、挂载、配置)
docker inspect <container_name_or_id>
# 查看容器实时资源占用(CPU、内存、网络I/O)
docker stats <container_name_or_id>
# 拷贝文件(宿主机与容器之间)
docker cp ./local_file.txt <container_name>:/app/
docker cp <container_name>:/app/remote_file.txt ./
4. 系统与清理命令
bash
# 查看 Docker 磁盘占用情况
docker system df
# 一键清理无用资源(停止的容器、未被使用的网络、悬空镜像)
docker system prune
# 深度清理(包括所有未被任何容器使用的镜像和卷)
docker system prune -a --volumes
三、 Java 后端专属 Docker 实战指南
Java 后端在容器化时,有其特殊的关注点,如 JVM 内存识别、时区问题、多阶段构建等。
1. 标准的 Java 多阶段构建 Dockerfile
多阶段构建可以大幅减小最终镜像体积,是 Java 项目的最佳实践。
dockerfile
# ================= 第一阶段:构建阶段 =================
# 使用包含 Maven 和 JDK 的镜像进行编译
FROM maven:3.8.4-openjdk-17-slim AS builder
WORKDIR /build
# 先拷贝 pom.xml 下载依赖,利用 Docker 缓存机制
COPY pom.xml .
RUN mvn dependency:go-offline -B
# 拷贝源码并打包
COPY src ./src
RUN mvn clean package -DskipTests
# ================= 第二阶段:运行阶段 =================
# 使用轻量级的 JRE 镜像运行(体积更小,更安全)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 设置时区为东八区(Alpine 镜像必须处理时区问题)
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata
# 从构建阶段拷贝产物
COPY --from=builder /build/target/*.jar app.jar
# 暴露端口(仅为声明,实际映射在 docker run 时指定)
EXPOSE 8080
# 使用 exec 格式,确保 Java 进程是 PID 1,能正确接收 SIGTERM 信号实现优雅停机
ENTRYPOINT ["java", "-jar", "app.jar"]
2. Java 容器启动高级命令(生产环境常用)
在启动 Java 容器时,通常需要传递 JVM 参数、挂载日志目录、设置环境变量。
bash
docker run -d \
--name my-spring-boot-app \
-p 8080:8080 \
-v /data/logs/myapp:/app/logs \
-e SPRING_PROFILES_ACTIVE=prod \
-e TZ=Asia/Shanghai \
--restart unless-stopped \
--memory="1g" \
--cpus="2.0" \
my-spring-boot-app:1.0.0 \
java -XX:MaxRAMPercentage=75.0 -XX:+UseZGC -jar app.jar
参数解析(Java 后端重点关注):
-v /data/logs/myapp:/app/logs:将容器内的日志目录挂载到宿主机,防止容器删除后日志丢失,也方便 ELK/Filebeat 采集。-e SPRING_PROFILES_ACTIVE=prod:通过环境变量指定 Spring Boot 的运行 Profile。--memory="1g":限制容器最大内存为 1G。java -XX:MaxRAMPercentage=75.0:核心 JVM 参数。让 JVM 自动识别容器的内存限制,并将最大堆内存设置为容器限制内存的 75%(留出 25% 给堆外内存和操作系统)。避免在 Java 8 早期版本中 JVM 识别不到 Cgroups 限制而导致 OOM 被系统 Kill。
3. Java 后端常见 Docker 踩坑与排查
- 问题一:容器内存 OOM 被 Kill(退出码 137)
- 原因 :JVM 堆内存 + 堆外内存(Metaspace、直接内存、线程栈等)总和超过了 Docker 的
--memory限制。 - 解决 :不要将
-Xmx设置得和容器限制内存一样大。推荐使用-XX:MaxRAMPercentage=70.0代替硬编码的-Xmx。
- 原因 :JVM 堆内存 + 堆外内存(Metaspace、直接内存、线程栈等)总和超过了 Docker 的
- 问题二:docker stop 无法优雅停机,每次都要等 10 秒
- 原因 :Dockerfile 中使用了 Shell 格式(如
CMD java -jar app.jar),导致 PID 1 是/bin/sh,Java 进程是子进程,无法接收到 Docker 发送的SIGTERM信号。 - 解决 :必须使用 Exec 格式(如
ENTRYPOINT ["java", "-jar", "app.jar"]),或者在 Shell 格式中使用exec java -jar app.jar。
- 原因 :Dockerfile 中使用了 Shell 格式(如
- 问题三:容器内日志时间与宿主机相差 8 小时
- 原因:基础镜像(尤其是 Alpine)默认是 UTC 时区。
- 解决 :在 Dockerfile 中配置时区(如上文 Dockerfile 所示),或在
docker run时添加-e TZ=Asia/Shanghai并挂载-v /etc/localtime:/etc/localtime:ro。