使用Docker部署SpringBoot项目

关于怎么部署SpringBoot项目?最简单的莫过于使用命令类似于:

nohup java -Xms512m -Xmx1024m -jar xx.jar > ./logs/start.log 2>&1 &

解释:

这个命令用于在后台运行一个 Java 应用程序,并将输出重定向到一个日志文件start.log,nohup &需要配合使用。

另外还有我们之前学习的pm2部署SpringBoot项目,当然还有很多方式。

这里我们学习一下使用Docker部署SpringBoot项目。

<strong>第一步:</strong>编写Dockerfile文件

首先,你需要在Java项目的根目录下创建一个名为 Dockerfile 的文件(只能说一般情况下,不强行要求)。在这个文件中,你需要指定基础镜像、拷贝项目文件、设置工作目录并执行构建命令。

FROM openjdk:8
MAINTAINER chen
# 设置工作目录为 /app
#WORKDIR /app
COPY target/all-learning.jar /app.jar
# 将 JAR 文件复制到工作目录
#add target/all-learning.jar ./app.jar
CMD ["--server.port=8081"]
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "/app.jar"]

这里我们先联想一下,我们通过java -jar的方式部署jar项目需要准备啥子:

  1. linux服务器需要具备javase环境,需要安装jdk
  2. 上传打包好的jar包到服务器上,<font color="red">这一步需要注意保留jar的上一次备份</font>
  3. 指定参数执行java -jar命令

具体到我们的Dockerfile文件:

FROM openjdk:8:表示当前Docker镜像是基于官方的OpenJDK 8镜像构建,因此不需要在Docker镜像中额外安装OpenJDK 8。 如果本地已有该镜像,则直接使用本地缓存 -> Docker 会自动从远程仓库下载(简单的说SpringBoot项目执行依赖jdk)。

COPY target/all-learning.jar /app.jar:表示将构建上下文中的所有 .jar 文件复制到容器内的 /app.jar 路径。这里的路径不是指Dockerfile所在路径,而是指构建上下文路径,即运行 docker build 命令时指定的路径(简单的理解就是我们的项目都有一个自己的工作目录,比如/home/project/xx,我们一般也是会把jar上传到指定的工作目录)。

CMD ["--server.port=8081"]ENTRYPOINT ["java", "-jar", "/app.jar"]意思差不多,组合后的命令是java -jar /app.jar --server.port=8081

那这里就要引申出一些疑问出来了:

<p style="color: red">Dockerfile使用copy和add有什么区别?</p>

COPY 仅用于将文件或目录从构建上下文复制到容器内的指定路径;ADD 不仅可以复制文件和目录,还支持一些额外的功能,如自动解压归档文件。简单的来说就是add更加的牛逼,能用add就用add。

<p style="color: red">Dockerfile使用CMD和ENTRYPOINT 有什么区别?</p>

CMD:提供默认的执行参数或命令,这些参数可以在运行容器时被覆盖。Dockerfile 中有 CMD ["--server.port=8081"],可以通过 docker run xx--server.port=9090 来覆盖它。

ENTRYPOINT :设置容器启动时的固定命令,通常用于定义不可变的启动命令。:ENTRYPOINT 不会被命令行参数直接覆盖,但可以与 CMD 结合使用。CMD 的值会作为 ENTRYPOINT 的参数传递。

如果同时使用了 ENTRYPOINT 和 CMD,CMD 的值会被用作 ENTRYPOINT 的参数。CMD 的参数会直接附加到 ENTRYPOINT 命令的末尾。

<hr />

<strong>第二步:</strong>需要构建Docker镜像

docker build -t xx:v1 .

-t:用于给镜像打标签(tag)

xx:v1 :,表示镜像名称为 xx,版本标签为 v1

.:表示 Dockerfile 所在的上下文路径。这里是一个点号,意味着当前目录作为构建上下文,Docker 会在这个目录中查找名为 Dockerfile 的文件,并根据其指令来构建镜像。

<p>这个标签可以用来标识镜像的不同版本,所以我们在保留上一次jar的备份文件的时候可以使用这种方式,最新的基于latest。</p>

删除镜像通过命令:

docker rmi <镜像名称>:<标签>

<hr />

<strong>第三步:</strong>启动容器

docker run -d --name xx -p 9999:9999 xx:latest

-d:表示以分离模式(后台运行)启动容器。容器启动后不会占用当前终端。

-name xx:为启动的容器指定名称为 xx。你可以通过这个名称来引用或管理这个容器。(每个容器在 Docker 中都有一个唯一的 ID,但 ID 不便于记忆和使用。通过给容器指定一个名称,你可以更方便地引用和管理该容器。使用 docker stop xx 可以停止该容器,而不需要记住复杂的容器 ID,使用 docker logs xx 可以查看该容器的日志。)

-p 9999:8081:将主机的 9999 端口映射到容器的 8081端口。这样你可以在主机上通过访问 localhost:9999 来访问容器内的服务。

xx:latest:指定要运行的镜像名称为 xx,标签为 latest。

补充知识点:

<p style="color: red">关于应用程序实际监听的端口和Docker 运行命令中的端口映射:</p>

-p 9999:8081我写成-p 9999:9999就会报如下错误

curl: (56) Recv failure: Connection reset by peer

对于已经执行了docker run -d --name xx -p 9999:9999 xx:latest,不能再重复执行了,会报错指定的容器名称已经存在

docker: Error response from daemon: Conflict. The container name "/xx" is already in use by container "f832dc8dd0d70a5797d1e0d31ed296e51c0ca3462c56de0bf40f40ab4944db4b". You have to remove (or rename) that container to be able to reuse that name.

需要执行:

docker stop xx
docker rm xx

验证端口映射是否正常,服务器是否可以正常访问:

curl http://localhost:9999
image03.png

在Docker容器启动时,通过传入不同的环境变量,让Spring Boot微服务加载相应的配置文件,增强灵活性。

docker run -e SPRING_PROFILES_ACTIVE=prod -p 8080:8080 user-service:latest

这里,通过-e参数传入SPRING_PROFILES_ACTIVE=prod,使得Spring Boot微服务以生产环境配置运行。

-e 或 --env:用于设置环境变量。

SPRING_PROFILES_ACTIVE=prod:这是一个特定于 Spring Boot 的环境变量,用于指定激活的配置文件为 prod(生产环境配置)。

同时这里又有一个疑问了哟:

<p style="color: red">docker build和docker run是怎么执行Dockerfile文件的?</p>

ENTRYPOINT 和 CMD :每个指令会被记录到镜像中,但不会在 docker build 阶段执行。

docker run 用于基于镜像启动一个容器。它会使用镜像中定义的 ENTRYPOINT 和 CMD 来启动容器中的进程。

<hr />

我们常用的一些Docker命令:

Docker命令 解释
docker ps 查看正在docker run中的容器
docker inspect <容器名称或ID> 查看容器或镜像的详细信息
docker stop 停止容器
docker kill 立即停止容器
docker pull 从镜像仓库拉取镜像

这里解释一下docker stopDocker stop的区别:

docker stop可以优雅地停止容器:docker stop 会向容器内的主进程发送一个终止信号(默认是 SIGTERM),通知进程进行清理工作并正常退出。默认超时时间:docker stop 的默认超时时间是 10 秒。

docker kill立即停止容器:docker kill直接向容器的主进程发送强制终止信号(默认是 SIGKILL),强制终止容器的运行。

<font color="red">上面基本上都是流程化的步骤</font>,我们可以通过新建start.sh脚本的方式执行:

#!/bin/bash

# 基础配置(根据实际项目修改)
JAR_NAME="your-app.jar"     # 替换为实际jar包名称
DOCKER_IMAGE="myapp-image"  # Docker镜像名称
DOCKER_CONTAINER="myapp"    # 容器名称
PORT_MAPPING="8080:8080"    # 宿主机端口:容器端口

# 步骤1:检查Docker环境
if ! command -v docker &> /dev/null; then
    echo "❌ 未检测到Docker环境,请先安装Docker"
    exit 1
fi

# 步骤2:检查Jar包是否存在
if [ ! -f "$JAR_NAME" ]; then
    echo "❌ 错误:当前目录下未找到 $JAR_NAME 文件"
    exit 1
fi

# 步骤3:生成Dockerfile(动态生成避免手动维护)
cat > Dockerfile <<EOF
# 使用OpenJDK官方镜像作为基础
FROM openjdk:11-jre-slim

# 设置工作目录
WORKDIR /app

# 复制Jar包到镜像
COPY $JAR_NAME app.jar

# 暴露容器端口
EXPOSE ${PORT_MAPPING#*:}

# 设置JVM参数并启动应用
ENTRYPOINT ["java", "-jar", "-Djava.security.egd=file:/dev/./urandom", "app.jar"]
EOF

# 步骤4:构建Docker镜像
echo "🛠️ 正在构建镜像 $DOCKER_IMAGE..."
docker build -t $DOCKER_IMAGE . || {
    echo "❌ 镜像构建失败,请检查Dockerfile或构建日志"
    exit 1
}

# 步骤5:清理旧容器
echo "🧹 清理旧容器..."
docker stop $DOCKER_CONTAINER 2>/dev/null || true
docker rm $DOCKER_CONTAINER 2>/dev/null || true

# 步骤6:启动新容器
echo "🚀 启动容器 $DOCKER_CONTAINER..."
docker run -d \
    --name $DOCKER_CONTAINER \
    --restart=always \
    -p $PORT_MAPPING \
    $DOCKER_IMAGE || {
    echo "❌ 容器启动失败,请检查端口是否被占用"
    exit 1
}

# 步骤7:验证部署
echo "✅ 部署完成!"
echo "🔍 查看容器状态:docker ps -f name=$DOCKER_CONTAINER"
echo "📜 查看日志:docker logs -f $DOCKER_CONTAINER"

docker-compose(针对的是多应用)

从逻辑概念上来讲,Docker有三部分组成:镜像+仓库+容器,对于正规公司而言,Docker镜像的制作和推送 一般是由程序员完成的,启动运行容器 一般是由运维人员完成的。一个在线的生产应用,它会有多个应用程序共同组成,所以我们要管理的容器可能不止一个,而且为了实现我们系统容量的提升,我们很可能需要对它进行水平扩展,比如部署一个mysql,需要5台服务器,意味着每个服务都要运营一个mysql server的容器,还要想办法把它组合起来,独立管理的话效率是非常低的,需要有一个工具程序能够把一个生产环境中的应用程序的多个组件,联合起来统一进行启动或者停止,这个就是容器编排系统。其中docker-compose 适用于管理单个主机上的多容器应用程序,但当应用程序规模扩大,涉及多个主机和更复杂的部署需求时,这个时候就需要使用到Kubernetes。

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过 YAML 文件,你可以配置应用程序的服务、网络和卷,并使用单个命令来创建并启动所有服务。这使得复杂的应用部署变得简单易行。

那这里我们学习一下使用docker-compose部署上线的项目:

第一步:

安装docker-compose

第二步:

创建docker-compose.yml文件

version: '3'
services:
  web:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: example
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

version: '3':指定 docker-compose 文件的版本

services:定义一组服务(容器)

web:定义一个名为 web 的服务

image: nginx:latest: 指定用于 web 服务的 Docker 镜像

volumes:将主机目录挂载到容器内的目录,将主机的 ./html 目录挂载到容器的 /usr/share/nginx/html 目录

第三步:

启动应用

docker-compose up -d
image01.png

当然我们可以通过 -f 选项指定一个其它名称的配置文件

docker-compose -f docker-compose-dev.yml up
docker-compose -f docker-compose-base.yml -f docker-compose-dev.yml config

我们可以通过多次指定 -f 选项的方式配置不同的环境,并且共用一份基础的配置文件。

第四步:

验证部署

image02.png

使用docker compose ps需要注意当前目录是docker-compose.yml文件所在的目录:

image04.png

在微服务架构中,需要按顺序启动容器,比如说非功能性的建设需要优先启动:可以通过 depends_on 来解决有依赖关系的容器的启动顺序问题。

关于docker-compose的作用:

服务依赖关系 :使用 depends_on 可以设置服务启动的顺序,但要注意它只能保证容器启动顺序,不能保证应用程序已经准备就绪。

环境变量 :除了在配置文件中直接定义环境变量,还可以使用 .env 文件。

数据持久化 :使用 volumes 可以实现数据持久化,即使容器被删除,数据依然保存在宿主机上。

常见的docker compose命令:

启动服务:

docker compose start

# 重启特定服务
docker-compose restart <服务名称>
# 启动特定服务
docker-compose up -d <服务名称>
# 停止特定服务
docker-compose stop <服务名称>

删除服务:

docker compose down

查看服务状态

docker compose ps

<p style="color: red">另外需要特别注意的一点:在多Docker Compose项目中,要确保不同 Docker Compose 项目中的容器能够相互通信。</p>

在 Docker Compose 中,每个 docker-compose.yml 文件默认创建一个独立的桥接网络(Bridge Network)。不同 docker-compose.yml 文件中的容器默认情况下位于不同的网络中,无法直接相互通信。

可以类比:在公司内部,网络通常被划分为不同的网段(Subnets),每个网段内的设备可以相互通信,而不同网段之间的通信需要通过路由器或防火墙进行配置。

附上一个详细的docker-compose.yml文件加强理解:

version: '3'

services:
  mysql:
    image: mysql:8.0.31
    container_name: mysql
    environment:
      # 时区上海
      TZ: Asia/Shanghai
      # root 密码
      MYSQL_ROOT_PASSWORD: root
      # 初始化数据库(后续的初始化sql会在这个库执行)
      MYSQL_DATABASE: ry-vue
    ports:
      - "3306:3306"
    volumes:
      # 数据挂载
      - /docker/mysql/data/:/var/lib/mysql/
      # 配置挂载
      - /docker/mysql/conf/:/etc/mysql/conf.d/
    command:
      # 将mysql8.0默认密码策略 修改为 原先 策略 (mysql8.0对其默认策略做了更改 会导致密码无法匹配)
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    privileged: true
    network_mode: "host"

  nginx-web:
    image: nginx:1.22.1
    container_name: nginx-web
    environment:
      # 时区上海
      TZ: Asia/Shanghai
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # 证书映射
      - /docker/nginx/cert:/etc/nginx/cert
      # 配置文件映射
      - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
      # 页面目录
      - /docker/nginx/html:/usr/share/nginx/html
      # 日志目录
      - /docker/nginx/log:/var/log/nginx
    privileged: true
    network_mode: "host"

  redis:
    image: redis:6.2.7
    container_name: redis
    ports:
      - "6379:6379"
    environment:
      # 时区上海
      TZ: Asia/Shanghai
    volumes:
      # 配置文件
      - /docker/redis/conf:/redis/config:rw
      # 数据文件
      - /docker/redis/data/:/redis/data/:rw
    command: "redis-server /redis/config/redis.conf"
    privileged: true
    network_mode: "host"

  minio:
    image: minio/minio:RELEASE.2023-03-24T21-41-23Z
    container_name: minio
    ports:
      # api 端口
      - "9000:9000"
      # 控制台端口
      - "9001:9001"
    environment:
      # 时区上海
      TZ: Asia/Shanghai
      # 管理后台用户名
      MINIO_ROOT_USER: ruoyi
      # 管理后台密码,最小8个字符
      MINIO_ROOT_PASSWORD: ruoyi123
      # https需要指定域名
      #MINIO_SERVER_URL: "https://xxx.com:9000"
      #MINIO_BROWSER_REDIRECT_URL: "https://xxx.com:9001"
      # 开启压缩 on 开启 off 关闭
      MINIO_COMPRESS: "off"
      # 扩展名 .pdf,.doc 为空 所有类型均压缩
      MINIO_COMPRESS_EXTENSIONS: ""
      # mime 类型 application/pdf 为空 所有类型均压缩
      MINIO_COMPRESS_MIME_TYPES: ""
    volumes:
      # 映射当前目录下的data目录至容器内/data目录
      - /docker/minio/data:/data
      # 映射配置目录
      - /docker/minio/config:/root/.minio/
    command: server --address ':9000' --console-address ':9001' /data  # 指定容器中的目录 /data
    privileged: true
    network_mode: "host"

  ruoyi-server1:
    image: ruoyi/ruoyi-server:4.8.2
    container_name: ruoyi-server1
    environment:
      # 时区上海
      TZ: Asia/Shanghai
      SERVER_PORT: 8080
    volumes:
      # 配置文件
      - /docker/server1/logs/:/ruoyi/server/logs/
      # skywalking 探针
#      - /docker/skywalking/agent/:/ruoyi/skywalking/agent
    privileged: true
    network_mode: "host"

  ruoyi-server2:
    image: "ruoyi/ruoyi-server:4.8.2"
    container_name: ruoyi-server2
    environment:
      # 时区上海
      TZ: Asia/Shanghai
      SERVER_PORT: 8081
    volumes:
      # 配置文件
      - /docker/server2/logs/:/ruoyi/server/logs/
      # skywalking 探针
#      - /docker/skywalking/agent/:/ruoyi/skywalking/agent
    privileged: true
    network_mode: "host"

  ruoyi-monitor-admin:
    image: ruoyi/ruoyi-monitor-admin:4.8.2
    container_name: ruoyi-monitor-admin
    environment:
      # 时区上海
      TZ: Asia/Shanghai
    volumes:
      # 配置文件
      - /docker/monitor/logs/:/ruoyi/monitor/logs
    privileged: true
    network_mode: "host"

  ruoyi-xxl-job-admin:
    image: ruoyi/ruoyi-xxl-job-admin:4.8.2
    container_name: ruoyi-xxl-job-admin
    environment:
      # 时区上海
      TZ: Asia/Shanghai
    volumes:
      # 配置文件
      - /docker/xxljob/logs/:/ruoyi/xxljob/logs
    privileged: true
    network_mode: "host"

使用idea一键搞定项目发

配置远程部署方式:

第一步:进行ssh配置

image05.png

<hr />

第二步:连接docker守护进程

image06.png

简单理解一下docker的守护进程:

Docker daemon(或称为 Docker 守护进程)是运行在宿主机上的一个持续运行的服务,负责管理 Docker 容器的创建、运行、停止等操作。它是 Docker 引擎的核心组件之一。

容器管理: Docker daemon 负责管理容器的生命周期,包括创建、运行、停止、删除等操作。它接收来自 Docker 客户端的命令,并根据命令进行相应的操作,例如根据指定的镜像创建容器,启动容器的进程等。

镜像管理: Docker daemon 负责管理 Docker 镜像,它可以从 Docker Hub 或其他镜像仓库中下载镜像,并根据需要构建、打包、发布和分发镜像。它还负责缓存镜像,以便在创建容器时可以快速获取需要的镜像。

网络管理: Docker daemon 负责管理容器的网络。它将为每个容器分配一个唯一的 IP 地址,并为容器提供网络连接,使得容器可以与其他容器或宿主机进行通信。

存储管理: Docker daemon 负责管理容器的存储,包括容器的文件系统、数据卷和容器的持久化存储等。它可以根据指定的存储驱动程序将容器的数据保存在宿主机上的文件系统中,并为容器提供数据卷,以便对容器的存储进行管理。

我这里就简单的理解为Docker的守护进程是Docker的客户端,可以执行各种Docker的操作。(Docker的客户也是直接与Docker的守护进程通信)

<hr />

第三步:正常编写dockerfile文件

第四步:配置远程部署

image07.png

结尾

giao!!!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容