Multi container apps
刚开始写 Dockerfile 就有一个问题迎面而来,我需要把 MySQL 写在里面吗?它应该跑在哪里?查了官方文档和资料后,得到的答案是每个容器最好只有一个进程,即 Spring Boot 项目一个容器,数据库用另一个容器,后面我是采用 container networking 将两者联系起来。下面我总结了为什么每个容器最好只有一个进程。
- 依赖问题。不同的进程可能需要不同的依赖项。运行多个进程需要一个进程管理器,增加了容器启动/关闭的复杂性。
- 更新问题。单独的容器可以独立地进行版本管理和更新,更新版本时不需要停止数据库。
- 维护问题。如果一个容器中运行多个进程,容器可能会因为其中一个进程的失败而失败,还无法确定是哪一个。
- 重用问题。单独的容器可以更容易地重用(数据库不能跟着一起扩展),还可以通过 configmap 或环境变量来挂载特定的配置。
- 生产问题。在生产中通常会使用数据库托管服务,肯定不希望将数据库和自己的应用程序一起提供。
参考: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
|