Docker 学习记录 2: 在本地容器化 Spring Boot 项目

在学习了 c 语言 Dockerfile 和 Multi-stage 的基础上,学习 Multi container,使用 Docker 网络实现了在本地容器化 Spring Boot + MySQL 项目。

Multi container apps

刚开始写 Dockerfile 就有一个问题迎面而来,我需要把 MySQL 写在里面吗?它应该跑在哪里?查了官方文档和资料后,得到的答案是每个容器最好只有一个进程,即 Spring Boot 项目一个容器,数据库用另一个容器,后面我是采用 container networking 将两者联系起来。下面我总结了为什么每个容器最好只有一个进程

  1. 依赖问题。不同的进程可能需要不同的依赖项。运行多个进程需要一个进程管理器,增加了容器启动/关闭的复杂性。
  2. 更新问题。单独的容器可以独立地进行版本管理和更新,更新版本时不需要停止数据库。
  3. 维护问题。如果一个容器中运行多个进程,容器可能会因为其中一个进程的失败而失败,还无法确定是哪一个。
  4. 重用问题。单独的容器可以更容易地重用(数据库不能跟着一起扩展),还可以通过 configmap 或环境变量来挂载特定的配置。
  5. 生产问题。在生产中通常会使用数据库托管服务,肯定不希望将数据库和自己的应用程序一起提供。

参考:Multi container apps 官方文档

Is it a good practice to have the database within the same container as the app?

Network

创建一个 Docker 网络 todolist-mysql,用于 ToDolist 项目和数据库两个容器之间的通信。

1
 docker network create todolist-mysql

启动 MySQL 容器

将容器连接到之前创建的 todolist-mysql 网络,设置用户名和密码,并在启动时创建一个名为 todolistdb 的数据库。

1
docker run --name mysqldb --network todolist-mysql -e MYSQL_ROOT_PASSWORD=securepswd -e MYSQL_DATABASE=todolistdb -d mysql:8

查看 MySQL 容器的状态。

1
2
3
❯ docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS                   PORTS                 NAMES
8efb34cea930   mysql:8        "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes             3306/tcp, 33060/tcp   mysqldb

在 MySQL 容器中启动一个交互式的 Bash shell,查看数据库是否创建成功。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ docker exec -it 8efb34cea930 bash
bash-5.1# mysql -uroot -psecurepswd
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| todolistdb         |
+--------------------+

容器化 Spring Boot 项目

配置 application.properties

首先在 application.properties 中修改连接数据库的配置,改成从环境变量中读取需要连接的数据库及其用户名和密码,避免出现修改数据库后需要重新编译的问题,还不会将密码暴露在镜像中。

1
2
3
4
5
spring.application.name=ToDolist
spring.datasource.url=jdbc:mysql://${MYSQL_URL:mysqldb}/${MYSQL_DB:todolistdb}
spring.datasource.username=${MYSQL_USERNAME:root}
spring.datasource.password=${MYSQL_PASSWORD:securepswd}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

Dockerfile

最开始是先在本地打包了 JAR 文件,写的 Dockerfile 如下。

1
2
3
4
FROM openjdk:17
COPY ./target/ToDolist-0.0.1-SNAPSHOT.jar ToDolist-0.0.1-SNAPSHOT.jar

ENTRYPOINT ["java", "-jar","/ToDolist-0.0.1-SNAPSHOT.jar"]

后来发现在实际生产中不会在一个外部环境中就完成构建过程,应该采用多阶段构建。

Docker hub 中找到对应版本的 Maven 和 JRE,第一阶段使用 Maven 镜像作为基础镜像,复制 pom.xml 文件并预下载依赖,复制源代码并编译出 JAR 文件,第二阶段复制编译好的文件并运行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
FROM maven:3-amazoncorretto-17-alpine as build

WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline

COPY . .
RUN mvn clean package

FROM eclipse-temurin:17-jre-noble

COPY --from=build /app/target/*.jar ToDolist.jar

ENTRYPOINT ["java", "-jar","/ToDolist.jar"]

这时就可以回答 在服务器上部署 Spring Boot Application 中遗留的问题。

JRE 和 JDK 的区别是什么?

JRE 是 Java 运行时环境,是运行已编译的 Java 程序所需的一切的包。

JDK 是 Java 开发工具包,它包括 JRE,还有编译器(如 javac)和工具(如 javadoc, jdb),能够创建和编译程序。

因此,在第二阶段只需要 JRE 就能够运行程序,能够减小最终镜像的大小。

构建镜像

1
 docker build -t todolist .

运行容器

1
docker run --network todolist-mysql --name todolist-container -p 8080:8080 -d todolist
1
docker logs -f todolist-container
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy