引言
作为 Java 开发者,你是否遇到过这些问题:
-
本地开发环境正常,部署到服务器就出错?
-
团队成员的开发环境不一致导致协作困难?
-
应用部署流程复杂,需要配置各种环境变量和依赖?
Docker 正是为解决这些问题而生。本文将带你从零开始掌握 Docker,并学会如何将 Java 应用容器化。
什么是 Docker?
Docker 是一个开源的容器化平台,它允许开发者将应用程序及其所有依赖项打包到一个标准化的单元(容器)中。容器是轻量级的、独立的可执行包,包含运行软件所需的一切:代码、运行时、系统工具、系统库和设置。
Docker vs 虚拟机
| 特性 | Docker 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级 | 分钟级 |
| 性能 | 接近原生 | 有性能损耗 |
| 资源占用 | MB 级别 | GB 级别 |
| 隔离性 | 进程级别 | 系统级别 |
核心概念
1. 镜像(Image)
镜像是一个只读的模板,包含运行应用所需的文件系统、代码、运行时、库和配置。可以把它想象成 Java 中的类。
2. 容器(Container)
容器是镜像的运行实例,就像 Java 中类的实例对象。容器可以被启动、停止、删除和暂停。
3. Dockerfile
Dockerfile 是一个文本文件,包含构建 Docker 镜像的所有指令。
4. Docker Hub
Docker Hub 是官方的镜像仓库,就像 Maven 的中央仓库,你可以从中拉取现成的镜像。
5. 数据卷(Volume)
用于持久化容器数据,即使容器被删除,数据卷中的数据也不会丢失。
安装 Docker
Windows 和 macOS
-
访问 Docker 官网
-
下载并安装 Docker Desktop
-
启动 Docker Desktop
Linux (Ubuntu/Debian)
# 更新包索引
sudo apt-get update
# 安装依赖
sudo apt-get install ca-certificates curl gnupg lsb-release
# 添加 Docker 官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 设置稳定版仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
# 验证安装
docker --version
验证安装
docker run hello-world
如果看到 "Hello from Docker!" 消息,说明安装成功。
Docker 常用命令
镜像相关命令
# 搜索镜像
docker search openjdk
# 拉取镜像
docker pull openjdk:17
# 列出本地镜像
docker images
# 删除镜像
docker rmi <镜像ID>
# 构建镜像
docker build -t myapp:1.0 .
# 给镜像打标签
docker tag myapp:1.0 myusername/myapp:1.0
# 推送镜像到 Docker Hub
docker push myusername/myapp:1.0
容器相关命令
# 运行容器
docker run -d --name mycontainer openjdk:17
# 运行容器并映射端口
docker run -d -p 8080:8080 --name myapp myapp:1.0
# 查看运行中的容器
docker ps
# 查看所有容器(包括已停止的)
docker ps -a
# 停止容器
docker stop mycontainer
# 启动已停止的容器
docker start mycontainer
# 重启容器
docker restart mycontainer
# 删除容器
docker rm mycontainer
# 强制删除运行中的容器
docker rm -f mycontainer
# 进入容器
docker exec -it mycontainer bash
# 查看容器日志
docker logs mycontainer
# 实时查看日志
docker logs -f mycontainer
# 查看容器详细信息
docker inspect mycontainer
# 查看容器资源使用情况
docker stats
数据卷相关命令
# 创建数据卷
docker volume create mydata
# 列出数据卷
docker volume ls
# 查看数据卷详情
docker volume inspect mydata
# 删除数据卷
docker volume rm mydata
# 清理未使用的数据卷
docker volume prune
系统清理命令
# 删除所有停止的容器
docker container prune
# 删除所有未使用的镜像
docker image prune
# 删除所有未使用的数据卷
docker volume prune
# 清理整个系统(慎用)
docker system prune -a
为 Java 应用创建 Dockerfile
示例 1:简单的 Spring Boot 应用
假设你有一个 Spring Boot 应用,打包后生成 app.jar。
# 使用官方 OpenJDK 作为基础镜像
FROM openjdk:17-jdk-slim
# 设置工作目录
WORKDIR /app
# 复制 jar 文件到容器
COPY target/app.jar app.jar
# 暴露应用端口
EXPOSE 8080
# 运行应用
ENTRYPOINT ["java", "-jar", "app.jar"]
示例 2:多阶段构建(推荐)
多阶段构建可以减小最终镜像的大小。
# 第一阶段:构建应用
FROM maven:3.8-openjdk-17 AS build
WORKDIR /app
# 复制 pom.xml 并下载依赖
COPY pom.xml .
RUN mvn dependency:go-offline
# 复制源代码并构建
COPY src ./src
RUN mvn clean package -DskipTests
# 第二阶段:运行应用
FROM openjdk:17-jdk-slim
WORKDIR /app
# 从构建阶段复制 jar 文件
COPY --from=build /app/target/*.jar app.jar
# 暴露端口
EXPOSE 8080
# 设置 JVM 参数
ENV JAVA_OPTS="-Xmx512m -Xms256m"
# 运行应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
示例 3:优化的生产级 Dockerfile
FROM openjdk:17-jdk-slim AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
FROM openjdk:17-jre-slim
# 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
# 复制 jar 文件
COPY --from=build /app/target/*.jar app.jar
# 更改文件所有者
RUN chown -R appuser:appuser /app
# 切换到非 root 用户
USER appuser
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
构建和运行 Java 应用
构建镜像
# 在 Dockerfile 所在目录执行
docker build -t myapp:1.0 .
# 指定 Dockerfile 路径
docker build -f Dockerfile.prod -t myapp:1.0 .
运行容器
# 基本运行
docker run -d -p 8080:8080 --name myapp myapp:1.0
# 带环境变量运行
docker run -d \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e DATABASE_URL=jdbc:mysql://db:3306/mydb \
--name myapp \
myapp:1.0
# 挂载配置文件
docker run -d \
-p 8080:8080 \
-v /path/to/config:/app/config \
--name myapp \
myapp:1.0
# 限制资源使用
docker run -d \
-p 8080:8080 \
--memory="512m" \
--cpus="1.0" \
--name myapp \
myapp:1.0
Docker Compose:管理多容器应用
对于包含多个服务的应用(如 Java 应用 + MySQL + Redis),使用 Docker Compose 更方便。
创建 docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=secret
- SPRING_REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- app-network
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=mydb
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-network
redis:
image: redis:7-alpine
networks:
- app-network
volumes:
mysql-data:
networks:
app-network:
driver: bridge
使用 Docker Compose
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f app
# 停止服务
docker-compose stop
# 停止并删除容器
docker-compose down
# 停止并删除容器和数据卷
docker-compose down -v
# 重新构建并启动
docker-compose up -d --build
.dockerignore 文件
类似于 .gitignore,.dockerignore 可以排除不需要复制到镜像中的文件。
target/
.git/
.idea/
*.iml
.DS_Store
*.log
node_modules/
最佳实践
1. 使用小体积的基础镜像
# 推荐使用 slim 或 alpine 版本
FROM openjdk:17-jre-slim
# 或
FROM openjdk:17-jre-alpine
2. 利用构建缓存
将变化较少的指令放在前面:
# 先复制依赖配置文件
COPY pom.xml .
RUN mvn dependency:go-offline
# 再复制源代码
COPY src ./src
RUN mvn package
3. 使用多阶段构建
减小最终镜像大小,不包含构建工具。
4. 不要以 root 用户运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
5. 使用健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1
6. 设置合理的 JVM 参数
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
7. 使用环境变量配置
不要在镜像中硬编码配置,使用环境变量。
8. 标签化管理镜像版本
docker build -t myapp:1.0.0 .
docker build -t myapp:latest .
实战案例:完整的 Spring Boot 项目
项目结构
myapp/
├── src/
├── pom.xml
├── Dockerfile
├── .dockerignore
└── docker-compose.yml
Dockerfile
FROM maven:3.8-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
FROM openjdk:17-jre-slim
RUN addgroup --system appgroup && adduser --system --group appuser
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
RUN chown appuser:appgroup app.jar
USER appuser
EXPOSE 8080
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
docker-compose.yml
version: '3.8'
services:
app:
build: .
container_name: myapp
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb?useSSL=false&serverTimezone=UTC
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=root123
depends_on:
mysql:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
mysql:
image: mysql:8.0
container_name: mysql
environment:
- MYSQL_ROOT_PASSWORD=root123
- MYSQL_DATABASE=mydb
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
mysql-data:
networks:
app-network:
driver: bridge
部署步骤
# 1. 构建并启动
docker-compose up -d --build
# 2. 查看日志
docker-compose logs -f
# 3. 测试应用
curl http://localhost:8080/api/test
# 4. 停止服务
docker-compose down
常见问题
1. 容器无法访问?
检查端口映射:docker ps 查看端口是否正确映射。
2. 容器频繁重启?
查看日志:docker logs <container-name> 找出错误原因。
3. 镜像构建慢?
使用国内镜像源,在 Dockerfile 中添加:
RUN mvn clean package -DskipTests -Dmaven.repo.local=/root/.m2/repository
4. 容器内存溢出?
限制内存并设置 JVM 参数:
docker run -m 512m -e JAVA_OPTS="-Xmx400m" myapp
总结
Docker 为 Java 开发者提供了强大的容器化能力,能够:
-
统一开发、测试和生产环境
-
简化应用部署流程
-
提高资源利用率
-
实现微服务架构
掌握 Docker 已成为现代 Java 开发者的必备技能。从简单的 Hello World 开始,逐步深入到多阶段构建、Docker Compose 和生产级优化,你将能够自信地将任何 Java 应用容器化。
参考资源
Happy Dockerizing! 🐳