基于Docker的SpringBoot构建实践

在容器化愈演愈烈的大背景下,本文来介绍下对于SpringBoot服务如何通过Docker的方式进行构建打包

abstract.png

构建Docker镜像

POM配置

首先通过Maven插件 spring-boot-maven-plugin 来构建SpringBoot服务的Jar包,其中可以通过finalName标签实现对Jar包重命名。然后利用Maven插件 dockerfile-maven-plugin 配置镜像信息及镜像构建。POM相关配置如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
...
<groupId>com.aaron</groupId>
<artifactId>SpringBootDocker</artifactId>
<packaging>jar</packaging>
<version>1.1</version>
<description>基于Spring Boot的Docker构建实践</description>

<build>

<!--设置Jar包的名称-->
<finalName>SpringBootDockerDemo</finalName>

<plugins>

<!--SpringBoot 打包-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.1</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<!--配置Jar包的主启动类-->
<mainClass>com.aaron.SpringBootDocker.DockerApplication</mainClass>
</configuration>
</plugin>

<!--Docker镜像构建-->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<!--配置仓库名/镜像名-->
<repository>aaron1995/spring_boot_docker_demo</repository>
<!--配置Tag -->
<tag>${project.version}</tag>
<!--设置镜像构建过程中的变量值-->
<buildArgs>
<jarFileName>${project.build.finalName}</jarFileName>
</buildArgs>
</configuration>
</plugin>

</plugins>
</build>
...

Dockerfile

现在我们在POM文件所在的同级目录中建立一个Dockerfile文件。这里说明下由于SpringBoot内嵌的Tomcat默认使用/tmp作为工作目录,故这里我们将其设置为镜像的挂载目录,以便镜像使用者可按需进行持久化。Dockerfile内容如下所示,可以看到还是非常简单的

1
2
3
4
5
6
7
8
9
10
11
12
# 基础镜像环境Java 8
FROM java:8
# 设置镜像的挂载目录
VOLUME /tmp
# 定义一个名为jarFileName的变量
ARG jarFileName
# 复制target目录中的Jar包到镜像中,并重命名
COPY target/${jarFileName}.jar app.jar
# 设置镜像的默认执行命令
ENTRYPOINT java -jar app.jar
# 声明Web服务使用的端口
EXPOSE 4001

镜像构建

至此,对于一个SpringBoot服务而已,其构建镜像所需的相关配置均已完成。我们直接点击Maven工具栏的package即可完成生成Jar包、镜像构建

figure 1.jpg

服务编排

通常一个SpringBoot服务会依赖多个中间件环境,Redis、MQ、DB等。为此将SpringBoot服务与其他中间件服务一同进行编排Compose,显然会大大方便开发、调试、管理等工作。这里我们的SpringBoot服务依赖了Redis、MySQL。 故首先在POM文件所在的同级目录中添加一个docker-compose.yml文件。其内容如下所示。其中我们通过自定义网络实现为各容器分配静态IP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Compose 版本
version: '3.9'

# 定义Docker服务
services:

# Web服务
Web-Service:
image: aaron1995/spring_boot_docker_demo:1.1
container_name: Web-Service
ports:
- "8080:4001"
networks:
# 使用名为dev_net并设置容器IP
dev_net:
ipv4_address: 121.121.121.10
# 指定Web服务的依赖, 实现先启动所依赖的服务, 再启动自己
depends_on:
- Redis-Service-2
- MySQL-Service-2

# Redis服务
Redis-Service-2:
image: redis:6.2.3-alpine3.13
container_name: Redis-Service-2
command: redis-server --requirepass 123456
ports:
- "7001:6379"
networks:
# 使用名为dev_net并设置容器IP
dev_net:
ipv4_address: 121.121.121.11

# Docker服务 1
MySQL-Service-2:
image: mysql:5.7
container_name: MySQL-Service-2
ports:
- "7002:3306"
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: db1
networks:
# 使用名为dev_net并设置容器IP
dev_net:
ipv4_address: 121.121.121.12

# 定义网络
networks:
# 定义一个名为dev_net的网络
dev_net:
ipam:
config:
# 设置网段
- subnet: 121.121.121.0/24

既然Web服务与中间件服务都编排在一起了。那容器间的网络通信该如何进行呢?具体有两种方式:容器IP、容器名。SpringBoot的application.yml部分配置如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 4001

spring:
application:
name: HelloDocker
redis:
database: 0
# 使用Redis容器的静态IP进行访问
host: 121.121.121.11
port: 6379
password: 123456
datasource:
# 使用容器名进行访问
url: jdbc:mysql://MySQL-Service-2:3306/db1?allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver

现在直接通过IDEA运行该docker compose即可,如下所示。服务部署成功

figure 2.jpg

验证测试

服务构建、部署成功后,还需在MySQL容器中执行SQL脚本以完成建库建表等初始化操作。一切准备就绪后,现在来进行验证测试。该SpringBoot服务的Controller接口功能如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@RestController
@RequestMapping("Hello")
public class TestController {

@Autowired
private RedisTemplate redisTemplate;

@Autowired
private PeopleMapper peopleMapper;

@GetMapping("/welcome")
public String welcome() {
return "Hello Spring Boot, By Docker";
}

@GetMapping("/add2Redis")
public String add2Redis(@RequestParam String key, @RequestParam String value) {
redisTemplate.opsForValue().set(key, value);
return "add data 2 redis: success";
}

@GetMapping("/getByRedis")
public String getByRedis(@RequestParam String key) {
String value = (String)redisTemplate.opsForValue().get(key);
return "["+key+"]: " + value;
}


@GetMapping("/add2DB")
public String add2DB(@RequestParam String name, @RequestParam String sex) {
PeopleInfo peopleInfo = PeopleInfo.builder()
.name(name)
.sex(sex)
.job("Teacher")
.build();
peopleMapper.insert(peopleInfo);
return "add data 2 DB: success";
}

@GetMapping("/getByDB")
public String getByDB(@RequestParam String name) {
Map map = new HashMap();
map.put("name", name);
List<PeopleInfo> list = peopleMapper.selectByMap(map);
return "["+name+"]: " + list;
}

}

测试结果如下所示,符合预期

figure 3.jpg

Note

  • docker compose中还可以使用已存在的网络,docker-compose.yml文件中通过如下方式进行声明
1
2
3
4
5
6
7
8
9
# Compose 版本
version: '3.9'

...

networks:
# 声明名为web_net的网络是一个已存在的网络
web_net:
external: true
  • docker compose中depends_on选项只能保证先启动所依赖服务再启动自身服务。但并不会等待依赖服务完全启动完毕后再启动自身

  • 如果期望对SpringBoot服务容器进行调试,可以在Dockerfile中对Jar包启动命令添加-agentlib:jdwp相关配置,并通过EXPOSE声明调试端口。然后在创建该服务容器时,需要对外映射相应的调试端口。这里选用5005为调试端口,Dockerfile示例如下

1
2
3
4
# Jar包启动命令添加-agentlib:jdwp相关配置
ENTRYPOINT java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar app.jar
# 声明远程调试端口
EXPOSE 5005

最后在IDEA中通过remote实现远程调试即可

figure 4.jpg

0%