Docker部署全栈项目:五、SpringBoot Jar
一、创建 SpringBoot 项目
1. 创建接口
@EntityScan("cn.luokaiii.api.entity")
@RestController
@SpringBootApplication
public class ApiApplication {
private final UserMongoService mongoService;
private final UserMySQLService mySQLService;
private final StringRedisTemplate stringRedisTemplate;
@Autowired
public ApiApplication(UserMongoService mongoService,
UserMySQLService mySQLService,
StringRedisTemplate stringRedisTemplate) {
this.mongoService = mongoService;
this.mySQLService = mySQLService;
this.stringRedisTemplate = stringRedisTemplate;
}
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
@PostMapping
public ResponseEntity createByMySql(@RequestBody Person person) {
final Person save = mySQLService.save(person);
final User user = mongoService.save(new User(person.getId().toString(), person.getName(), 22));
stringRedisTemplate.opsForValue().set(person.getName(), person.getSex().toString());
return ResponseEntity.ok(new UPDTO(save, user, null));
}
@GetMapping
public ResponseEntity getByMySql(@RequestParam String username) {
final Person person = mySQLService.findFirstByName(username);
final User user = mongoService.findFirstByName(username);
final String redis = stringRedisTemplate.opsForValue().get(username);
return ResponseEntity.ok(new UPDTO(person, user, redis));
}
}
2. 配置参数
连上我们之前配置的MongoDB、MySQL、Redis容器。
这里使用配置的 docker-compose 中的服务名称。这样服务会自动连接,而
localhost
或者127.0.0.1
则表示luokaiii-api
当前容器内部的端口号,肯定会连接失败的。
# MongoDB 连接
spring.data.mongodb.uri=mongodb://mongoadmin:password@mongo:27017/demo?authSource=admin
# MySQL 连接
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://mariadb:3306/db_user?useUnicode=true&characterEncoding=GBK&useSSL=false
# Redis 连接
spring.redis.host=redis
spring.redis.port=6379
spring.redis.password=password
3. 测试
执行如下 Post 命令:
# Post 请求
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "id":2, "name":"zhangsan", "sex":1 }' 'http://localhost:8080/'
# 返回结果
{"mysql":{"id":2,"name":"zhangsan","sex":1},"mongo":{"id":"2","name":"zhangsan","age":22},"redis":null}
执行如下 Get 命令:
# Get 请求
$ curl -i -X GET 'http://localhost:8080?username=zhangsan'
# 返回结果
{"mysql":{"id":2,"name":"zhangsan","sex":1},"mongo":{"id":"2","name":"zhangsan","age":22},"redis":"1"}
二、构建 Docker 镜像
1. pom.xml 中增加 docker 插件
完整的 pom.xml
如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 当前项目的版本、名称等信息 -->
<groupId>cn.luokaiii</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api</name>
<description>java api</description>
<!-- JDK版本 -->
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Data MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Web 服务器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL驱动连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Docker Maven 插件 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.2.0</version>
<configuration>
<!-- 构建出的镜像名 -->
<imageName>luokaiii-api</imageName>
<!-- 基础依赖的镜像 -->
<baseImage>openjdk:8-slim</baseImage>
<entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 构建镜像
# mvn clean package 重新打包, -Dmaven.test.skip 跳过测试,docker:build 构建镜像
$ mvn clean package -Dmaven.test.skip=true docker:build
正常的生产环境,应该有严格的单元测试。
查看 Docker Images ($ docker images
),应该有如下几个镜像了:
带上之前的镜像,一共有如下几个:luokaiii-api
,mongo:4.0.12
,mariadb:10.4.8
,openjdk:8-slim
, redis:4.0.14
3. 测试
a) 启动 luokaiii-api
镜像
这里我们直接使用了 Docker-Compose 来启动该镜像,因为 luokaiii-api
依赖了如 mongodb
,mariadb
,redis
等其他镜像容器。
a.1 在父级目录下创建 docker-compose.yml 文件
不再开放mongo、mysql、redis的外部连接,统一改为容器内部通信,只对外提供一个 8080 端口用于访问。
docker-compose.yml
文件如下:
# Docker-Compose 的版本
version: '3'
# 服务声明
services:
# MongoDB 服务
mongo:
container_name: 'luokaiii-mongo'
image: 'mongo:4.0.12'
volumes:
- mongodata:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: mongoadmin
MONGO_INITDB_ROOT_PASSWORD: password
restart: always
expose: # 暴露端口,但不映射到宿主机
- '27017'
# MariaDB 服务
mariadb:
container_name: 'luokaiii-mysql'
image: 'mariadb:10.4.8'
volumes:
- './mysql/data/:/var/lib/mysql'
- './mysql/conf/local.cnf:/etc/mysql/conf.d/local.cnf'
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: db_user
command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci']
restart: always
expose:
- '3306'
# Redis 服务
redis:
container_name: 'luokaiii-redis'
image: 'redis:4.0.14'
volumes:
- './redis/data:/data'
command: redis-server --requirepass password
restart: always
expose:
- '6379'
# Java API 服务
api:
container_name: 'luokaiii-api'
depends_on: # 依赖其他容器(如DB连接等)
- mongo
- redis
- mariadb
image: 'luokaiii-api:latest'
ports: # 对外暴露 8080 端口
- '8080:8080'
restart: always
# 容器能够使用的数据卷
volumes:
mongodata:
a.2 修改之前的 application.properties
将 localhost
全部改为对应 Compose
的服务名称,这样 容器在运行时会动态加载对应的服务及端口。
# MongoDB 连接
spring.data.mongodb.uri=mongodb://mongoadmin:password@mongo:27017/demo?authSource=admin
# MySQL 连接
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://mariadb:3306/db_user?useUnicode=true&characterEncoding=GBK&useSSL=false
# Redis 连接
spring.redis.host=redis
spring.redis.port=6379
spring.redis.password=password
a.3 运行 docker-compose
# 后台运行所有容器
$ docker-compose -d up
# 停止所有容器
# docker-compose down
# 停止单个容器
# docker-compose stop luokaiii-api
# 运行单个容器(停止状态)
# docker-compose start luokaiii-api
# 运行单个容器(被删除后)
# docker-compose run --name luokaiii-api luokaiii-api
a.4 依次查看容器的日志
控制台打印的日志太过混乱,这里我们一个一个的查看:
# 查看 MongoDB 运行情况
$ docker logs luokaiii-mongo
# 查看 MariaDB 运行情况
$ docker logs luokaiii-mysql
# 查看 Redis 运行情况
$ docker logs luokaiii-redis
# 查看 JAVA Server 运行情况
$ docker logs luokaiii-api
b. 生成数据表
进入 luokaiii-mysql
容器,进入 MySQL,生成 person 表。 发送请求即可。
遇到的问题
1. MongoDB无法挂载data目录
按照正常来说,mongo
服务的编排应该如下所示:
services:
mongo:
container_name: 'luokaiii-mongo'
image: 'mongo:4.0.12'
volumes:
- ./mongo/db:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: mongoadmin
MONGO_INITDB_ROOT_PASSWORD: password
restart: always
expose:
- '27017'
......
问题:
这样的挂载,却会抛出文件无法打开、无法读取、无法写入的问题。详见 Github Issue。
原因:
Docker 中的 Mongo 容器无法使用部分文件。
解决方案:
手动创建一个共享卷,即如下改动:
services:
mongo:
container_name: 'luokaiii-mongo'
image: 'mongo:4.0.12'
volumes:
- mongodata:/data/db # 这里改为下面声明的卷
environment:
MONGO_INITDB_ROOT_USERNAME: mongoadmin
MONGO_INITDB_ROOT_PASSWORD: password
restart: always
expose:
- '27017'
......
volumes:
mongodata:
$ docker volume list
能够查看所有共享的卷
$ docker volume inspect <name>
能够查看具体的卷信息(如实际地址等)
2. MySQL 初始密码未生效
问题:
在使用 docker-compose up
启动完所有程序后,luokaiii-api
项目抛出 “无法连接 MySQL 数据库的异常”。
而我们在 docker-compose.yml
中已经指定了具体的初始密码:
MYSQL_ROOT_PASSWORD: password
原因:
经排查发现,是 luokaiii-mysql
容器的密码未初始化。
解决方案:
依次运行以下代码:
# 进入 MySQL 容器(Windows需要加上 `winpty`)
$ docker exec -it luokaiii-mysql bash
# 匹配环境变量
root@xx/# env | grep -i password
MYSQL_ROOT_PASSWORD=password
# 尝试无密码登录 - 失败
root@xx/# mysql -Uroot
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
# 尝试使用密码登录,成功????为什么???
root@xx/# mysql -Uroot -ppassword
详见 Github Issue。
首次操作即可,往后未出现该bug。
参考资料:
- Docker-maven-plugin
- Docker Hub OpenJDK
- https://stackoverflow.com/questions/45729326/how-to-change-the-default-character-set-of-mysql-using-docker-compose
- 运维之美
- https://github.com/docker-library/mysql/issues/557