杰瑞科技汇

Docker部署Java应用有哪些关键步骤?

目录

  1. 为什么用 Docker 部署 Java 应用?
  2. 核心概念:Dockerfile
  3. 分步实战:构建并运行一个 Spring Boot 应用
    • 第一步:准备一个 Java 应用(以 Spring Boot 为例)
    • 第二步:编写 Dockerfile
    • 第三步:构建 Docker 镜像
    • 第四步:运行 Docker 容器
  4. Docker 镜像优化技巧
  5. 生产环境部署最佳实践
    • 使用 .dockerignore
    • 使用 Docker Compose(多容器应用)
    • 数据持久化
    • 日志管理

为什么用 Docker 部署 Java 应用?

在 Docker 出现之前,我们常说 "在我的电脑上是好的"(It works on my machine),这通常是因为开发、测试和生产环境的操作系统、Java 版本、依赖库、配置文件等存在差异。

Docker部署Java应用有哪些关键步骤?-图1
(图片来源网络,侵删)

Docker 通过以下方式解决了这个问题:

  • 环境一致性:将你的 Java 应用、其依赖(如 JAR 包)、运行时环境(如 JDK)以及配置文件打包到一个轻量级、可移植的 镜像 中,这个镜像在任何安装了 Docker 的机器上都能以相同的方式运行。
  • 隔离性:容器之间是相互隔离的,每个容器都有自己的文件系统、进程空间和网络栈,这避免了应用之间的冲突。
  • 可扩展性:可以通过简单的命令快速启动或停止多个容器实例,轻松实现应用的横向扩展。
  • 简化部署:部署过程从一个复杂的安装脚本变成了一个简单的 docker run 命令。
  • 版本控制:Docker 镜像就像 Git 仓库一样,可以被版本化、追踪和回滚。

核心概念:Dockerfile

Dockerfile 是一个文本文件,它包含了用户为镜像构建所有步骤的指令,Docker 会通过读取这个文件来自动构建镜像。

一个典型的 Dockerfile 包含以下指令:

  • FROM: 指定基础镜像,openjdk:17-jre-slim
  • COPY: 将文件从构建上下文复制到镜像中。
  • ADD: 类似 COPY,但还可以解压压缩文件或从 URL 获取文件。
  • WORKDIR: 在容器内设置一个工作目录,后续的 RUN, CMD, COPY 等指令都会在这个目录下执行。
  • RUN: 在镜像中执行命令,通常用于安装软件包或进行环境配置。
  • CMD: 容器启动时默认执行的命令。Dockerfile 中只能有一个 CMD,如果有多个,最后一个生效。
  • ENTRYPOINT: 配置容器启动时运行的命令。CMD 的参数会追加到 ENTRYPOINT 后面。ENTRYPOINT 不像 CMD 那么容易被命令行参数覆盖。
  • EXPOSE: 声明容器运行时监听的端口,但这只是一个声明,不会实际映射端口。
  • VOLUME: 定义一个数据卷,用于数据持久化。

分步实战:构建并运行一个 Spring Boot 应用

我们以一个打包好的 Spring Boot JAR 文件为例。

Docker部署Java应用有哪些关键步骤?-图2
(图片来源网络,侵删)

第一步:准备一个 Java 应用

假设你已经有一个 Spring Boot 项目,并且已经通过 mvn packagegradle build 命令打包生成了 my-java-app.jar 文件。

项目结构如下:

my-java-app/
├── src/
├── target/
│   └── my-java-app-0.0.1-SNAPSHOT.jar  <-- 这是我们的应用
├── pom.xml
└── ...其他文件

第二步:编写 Dockerfile

my-java-app 项目的根目录下,创建一个名为 Dockerfile 的文件(注意没有后缀名)。

直接运行 JAR(简单直接)

Docker部署Java应用有哪些关键步骤?-图3
(图片来源网络,侵删)

这是最简单的方式,适合快速原型和演示。

# 1. 指定基础镜像,选择一个精简版的 OpenJRE 17
# 你可以去 Docker Hub 查找最新的版本,如 eclipse-temurin, openjdk, adoptopenjdk 等
FROM openjdk:17-jre-slim
# 2. 设置工作目录,后续的操作都在这个目录下进行
WORKDIR /app
# 3. 将构建好的 JAR 文件复制到容器的 /app 目录下
# --chown=0:0 表示将文件所有者设置为 root (0:0),这是一个安全最佳实践
COPY --chown=0:0 target/my-java-app-0.0.1-SNAPSHOT.jar app.jar
# 4. (可选) 创建一个非 root 用户来运行应用,增加安全性
# RUN addgroup --system appuser && adduser --system --group appuser
# USER appuser
# 5. 声明容器监听的端口(这是应用内部监听的端口,不是宿主机端口)
EXPOSE 8080
# 6. 容器启动时执行的命令
# 使用 `java -jar` 来运行我们的 JAR 文件
# -Dserver.address=0.0.0.0 让应用监听所有网络接口,而不仅仅是 localhost
CMD ["java", "-jar", "app.jar", "-Dserver.address=0.0.0.0"]

使用多阶段构建(推荐,用于生产)

多阶段构建可以显著减小最终镜像的大小,因为它只保留运行应用所必需的文件,而将构建工具(如 Maven/Gradle)和源代码丢弃。

# 第一阶段:构建阶段
# 这个阶段使用完整的 JDK 和 Maven/Gradle 来编译和打包项目
FROM maven:3.8.6-openjdk-17 AS builder
WORKDIR /app
# 复制 pom.xml 和其他配置文件,利用 Docker 的缓存机制
# 如果这些文件没变,就不会重新下载依赖
COPY pom.xml .
# 如果是 Gradle 项目,使用 COPY build.gradle* . 和 settings.gradle*
COPY src ./src
# 运行 Maven 命令进行打包
# -DskipTests 跳过测试,加快构建速度
RUN mvn clean package -DskipTests
# 第二阶段:运行阶段
# 这个阶段只运行 JRE,镜像更小、更安全
FROM openjdk:17-jre-slim
WORKDIR /app
# 从构建阶段复制的 JAR 文件
# 注意这里的路径:builder 是第一阶段我们起的别名,target/... 是第一阶段的路径
COPY --from=builder /app/target/my-java-app-0.0.1-SNAPSHOT.jar app.jar
# 创建非 root 用户
RUN addgroup --system appuser && adduser --system --group appuser
USER appuser
EXPOSE 8080
CMD ["java", "-jar", "app.jar", "-Dserver.address=0.0.0.0"]

第三步:构建 Docker 镜像

打开你的终端,进入 my-java-app 项目根目录,运行以下命令:

# -t: 为镜像打一个标签,格式为 name:tag
# .: 指定构建上下文的路径,这里是当前目录,Dockerfile 也在这个目录下
docker build -t my-java-app:1.0 .

构建完成后,你可以用 docker images 命令查看新创建的镜像:

REPOSITORY      TAG       IMAGE ID       CREATED         SIZE
my-java-app     1.0       <image-id>     <time>          <size>

第四步:运行 Docker 容器

我们基于刚刚构建的镜像来运行一个容器。

# -d: 后台运行容器
# --name: 给容器起一个名字
# -p: 端口映射,格式为 <宿主机端口>:<容器端口>
#    将宿主机的 8080 端口映射到容器的 8080 端口
# my-java-app:1.0: 要运行的镜像名称和标签
docker run -d --name my-app-container -p 8080:8080 my-java-app:1.0

你的 Java 应用已经在 Docker 容器中运行了!你可以通过访问 http://localhost:8080 来验证它是否正常工作。

常用管理命令:

# 查看正在运行的容器
docker ps
# 停止容器
docker stop my-app-container
# 启动已停止的容器
docker start my-app-container
# 删除容器
docker rm my-app-container
# 查看容器日志
docker logs my-app-container

Docker 镜像优化技巧

  1. 使用多阶段构建:如上所述,这是减小镜像体积最有效的方法。

  2. 选择合适的基础镜像

    • openjdk:17-jre-slimeclipse-temurin:17-jre-alpineopenjdk:17 小得多。
    • alpine 版本基于 Alpine Linux,非常小,但使用 musl 而不是 glibc 库,某些 Native 库可能不兼容,需要先安装 apk add --no-cache ca-certificates 等依赖。
  3. 合并 RUN 指令:将多个 RUN 指令合并成一个,以减少镜像层数。

    # 不推荐
    RUN apt-get update
    RUN apt-get install -y curl
    # 推荐
    RUN apt-get update && apt-get install -y curl && \
        rm -rf /var/lib/apt/lists/* # 清理 apt 缓存
  4. 清理缓存:在 RUN 指令的最后清理掉不需要的文件(如 apt, yum, npm 的缓存)。

  5. 使用 .dockerignore:避免将不必要的文件(如 .git, target, node_modules)发送给 Docker 构建上下文,可以加快构建速度并减小镜像大小。


生产环境部署最佳实践

使用 .dockerignore

在项目根目录创建 .dockerignore 文件,内容如下:

# 忽略所有 Git 相关文件
.git
.gitignore
# 忽略构建产物和 IDE 文件
target/
.idea/
.vscode/
*.class
*.jar
*.war
*.zip
*.tar.gz
# 忽略日志和临时文件
logs/
tmp/
*.log

使用 Docker Compose

当你的应用需要依赖其他服务时(如数据库 Redis、消息队列 RabbitMQ),使用 Docker Compose 可以轻松管理多容器应用。

创建一个 docker-compose.yml 文件:

version: '3.8'
services:
  app:
    build: . # 使用当前目录下的 Dockerfile 构建镜像
    container_name: my-java-app-prod
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod # 设置生产环境配置
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydb
    depends_on:
      - db # 确保 db 服务先启动
    networks:
      - my-network
  db:
    image: postgres:13-alpine
    container_name: my-postgres-db
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres-data:/var/lib/postgresql/data # 数据持久化
    networks:
      - my-network
volumes:
  postgres-data:
networks:
  my-network:
    driver: bridge

你只需要一个命令就可以启动整个应用栈:

# 启动并后台运行所有服务
docker-compose up -d
# 查看所有服务状态
docker-compose ps
# 停止并删除所有服务、网络和数据卷
docker-compose down -v

数据持久化

数据库的数据不能存在容器内,因为容器被删除后数据会丢失,解决方案是使用 数据卷绑定挂载

docker-compose.yml 中,我们通过 volumes 定义了数据卷:

volumes:
  postgres-data: # 命名卷

日志管理

容器内的标准输出和标准错误会自动被 Docker 收集,你可以使用 docker logs 查看日志。

对于生产环境,建议将日志输出到标准输出/错误,然后使用 日志驱动 将它们发送到集中的日志管理系统,如 ELK Stack (Elasticsearch, Logstash, Kibana)、Splunk 或云服务商的日志服务。

docker-compose.yml 中可以这样配置:

services:
  app:
    # ...
    logging:
      driver: "json-file" # 默认驱动
      options:
        max-size: "10m"
        max-file: "3"

通过以上步骤,你已经掌握了使用 Docker 部署 Java 应用的核心流程和最佳实践。

核心流程回顾

  1. 准备应用:打好 JAR 包。
  2. 编写 Dockerfile:定义如何构建镜像,推荐使用多阶段构建。
  3. 构建镜像:使用 docker build 命令。
  4. 运行容器:使用 docker rundocker-compose up 命令。

最佳实践回顾

  • 安全:使用非 root 用户运行应用。
  • 轻量:使用多阶段构建和精简版基础镜像。
  • 可维护:使用 .dockerignoreDocker Compose
  • 可靠:为数据库等有状态服务配置数据持久化。

Docker 为 Java 应用的部署带来了革命性的变化,是现代 DevOps 和云原生应用不可或缺的工具,希望这份详细的教程对你有帮助!

分享:
扫描分享到社交APP
上一篇
下一篇