Docker部署全栈:五、Docker-Compose部署SpringBoot

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),应该有如下几个镜像了:

图:Docker Images

带上之前的镜像,一共有如下几个:luokaiii-apimongo:4.0.12mariadb:10.4.8openjdk:8-slimredis:4.0.14

3. 测试

a) 启动 luokaiii-api 镜像

这里我们直接使用了 Docker-Compose 来启动该镜像,因为 luokaiii-api 依赖了如 mongodbmariadbredis 等其他镜像容器。

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

MongoDB运行情况

# 查看 MariaDB 运行情况
$ docker logs luokaiii-mysql

MariaDB运行情况

# 查看 Redis 运行情况
$ docker logs luokaiii-redis

Redis 运行情况

# 查看 JAVA Server 运行情况
$ docker logs luokaiii-api

Java运行状况

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。

参考资料:

  1. Docker-maven-plugin
  2. Docker Hub OpenJDK
  3. https://stackoverflow.com/questions/45729326/how-to-change-the-default-character-set-of-mysql-using-docker-compose
  4. 运维之美
  5. https://github.com/docker-library/mysql/issues/557

项目地址

文章作者: koral
文章链接: http://luokaiii.github.io/2019/09/20/读书笔记/《Docker全栈项目部署》/5.SpringBoot/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自