網頁

2020/12/19

Docker run Spring Boot + MySQL containers

本範例在本機分別啟動Spring Boot container與MySQL container並建立連結。


前導知識:


範例環境:

  • macOS Catalina
  • Docker 19.03.12
  • Java 8
  • Maven
  • Spring Boot 2.3.2.RELEASE
  • Spring Boot Data JPA
  • Lombok

注意本篇不用Docker Compose,而是用Docker CLI docker run個別啟動服務。但營運上最佳實踐應該用Docker Compose來配置多容器間的啟動與連結。



啟動MySQL container

請參考「Docker 安裝MySQL」啟動MySQL container。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
741ccdb437ed        mysql:8             "docker-entrypoint.s…"   3 months ago        Up 2 hours          0.0.0.0:3306->3306/tcp, 33060/tcp   mysql8

登入帳號為root,密碼為12345,資料庫名稱為mydb

mydb建立employee資料表及新增資料。

-- 建立資料表
CREATE TABLE `mydb`.`employee` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NULL,
  `age` INT NULL,
  PRIMARY KEY (`id`));

-- 新增資料
INSERT INTO `mydb`.`employee` (`name`, `age`) VALUES ('John', '22');
INSERT INTO `mydb`.`employee` (`name`, `age`) VALUES ('Iris', '19');

輸入docker network inspect bridge檢視MySQL container在Docker預設的bridge network的IP位址。

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "73f7694d85e03a253ec075da7a92a9b7b75c63c80a283eecc19acca12c7be364",
        "Created": "2020-12-16T14:02:15.804443063Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "741ccdb437ed3aaddce067a62f21c3b65f6ea94629c37d6f5e1014cd675c9619": {
                "Name": "mysql8",
                "EndpointID": "5001d955c9e3a2d80791e2451af072fc06c802d5dd6ce2295091da9bdb41feb9",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

在回傳JSON格式的Containers屬性可找到mysql8 container的IPv4Address172.17.0.2,這將設定為Spring Boot application.yml配置檔中的spring.datasource.url的jdbc url路徑。



建立Spring Boot專案

參考「建立Spring Boot專案(IntelliJ, Eclipse)」建立Spring Boot專案。

範例用Spring Data JPA來存取MySQL資料庫,所以要在pom.xml加入spring-boot-start-data-jpa及MySQL JDBC driver mysql-connector-java依賴函式庫。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

Spring Boot配置檔application.yml設定資料庫datasource資訊。

application.yml

server:
  servlet:
    context-path: /demo
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://172.17.0.2:3306/mydb
    username: root
    password: 12345
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    database-platform: org.hibernate.dialect.MySQL8Dialect
    hibernate:
      ddl-auto: update

spring.datasource.url的IP設為MySQL container在bridge network中的IP位址,因為Spring Boot會運行在container而非本機,容器間是彼此獨立的虛擬機並透過Docker bridge network連結,每個容器有自己在bridge network的IP,所以連線位址不能設為localhost。 (bridge這字一直讓我想到死亡擱淺)

+-----------------------------------------------+
| host                                          |
|  +------------------+   +------------------+  |
|  | spring-boot-demo |   |      mysql8      |  |
|  |    172.17.0.3    |   |    172.17.0.2    |  |
|  +------------------+   +------------------+  |
|            ^                      ^           |
|            |                      |           |
|            v                      v           |
|       +--------------------------------+      |
|       |     Docker bridge network      |      |
|       |           (docker0)            |      |
|       +--------------------------------+      |
|                       ^                       |
+-----------------------|-----------------------+
                        v
               +------------------+
               | external network |
               +------------------+

建立映射employee資料表的JPA entity類Employee及存取層EmployeeRepository

Employee

package com.abc.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Employee implements Serializable {
    private static final Long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String name;

    private Integer age;

}


EmployeeRepository

package com.abc.demo.repository;

import com.abc.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

建立DemoController提供API/employee/all呼叫EmployeeRepository查詢資料。

DemoController

package com.abc.demo.controller;

import com.abc.demo.entity.Employee;
import com.abc.demo.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DemoController {

    @Autowired
    private EmployeeRepository employeeRepository;

    @GetMapping("/employee/all")
    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

}

參考github


建立Spring Boot image

這邊是利用Jib maven plugin來建構Spring Boot image。在pom.xml加入Jib maven plugin。<image>為image的名稱。

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>2.7.0</version>
    <configuration>
        <to>
            <image>spring-boot-demo</image>
        </to>
    </configuration>
</plugin>

設定完以上後在Spring Boot專案根目錄(pom.xml所在目錄)執行mvn compile jib:dockerBuild開始建構image。

~/../spring-boot-demo$ mvn compile jib:dockerBuild
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.abc:spring-boot-demo >----------------------
[INFO] Building spring-boot-demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ spring-boot-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ spring-boot-demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- jib-maven-plugin:2.7.0:dockerBuild (default-cli) @ spring-boot-demo ---
[WARNING] 'mainClass' configured in 'maven-jar-plugin' is not a valid Java class: ${start-class}
[INFO]
[INFO] Containerizing application to Docker daemon as spring-boot-demo...
[WARNING] Base image 'gcr.io/distroless/java:8' does not use a specific image digest - build may not be reproducible
[INFO] Using base image with digest: sha256:50ffbd05df754f787277c8aaa178a586c69d95da29edc318b8c9cbc21ede0cd1
[INFO]
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, com.abc.demo.DemoApplication]
[INFO]
[INFO] Built image to Docker daemon as spring-boot-demo
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.795 s
[INFO] Finished at: 2020-12-19T21:22:11+08:00
[INFO] ------------------------------------------------------------------------

建構好的image會放在本機的Docker daemon中。輸入docker images檢視可看到建構好的spring-boot-demo image。

$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
mysql                              8                   0d64f46acfd1        4 months ago        544MB
spring-boot-demo                   latest              fe16282f0786        51 years ago        169MB


啟動Spring Boot container

輸入docker run -p 8080:8080 --name demo spring-boot-demo:latest啟動Spring Boot container。

$ docker run -p 8080:8080 --name demo spring-boot-demo:latest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.2.RELEASE)

2020-12-19 15:09:22.741  INFO 1 --- [           main] c.a.d.DemoApplication                    : Starting DemoApplication on bfc70ca42ae8 with PID 1 (/app/classes started by root in /)
2020-12-19 15:09:22.756  INFO 1 --- [           main] c.a.d.DemoApplication                    : No active profile set, falling back to default profiles: default
2020-12-19 15:09:23.722  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFERRED mode.
2020-12-19 15:09:23.847  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 103ms. Found 1 JPA repository interfaces.
2020-12-19 15:09:24.804  INFO 1 --- [           main] o.s.b.w.e.t.TomcatWebServer              : Tomcat initialized with port(s): 8080 (http)
2020-12-19 15:09:24.822  INFO 1 --- [           main] o.a.c.c.StandardService                  : Starting service [Tomcat]
2020-12-19 15:09:24.823  INFO 1 --- [           main] o.a.c.c.StandardEngine                   : Starting Servlet engine: [Apache Tomcat/9.0.37]
2020-12-19 15:09:24.936  INFO 1 --- [           main] o.a.c.c.C.[.[.[/demo]                    : Initializing Spring embedded WebApplicationContext
2020-12-19 15:09:24.936  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2103 ms
2020-12-19 15:09:25.287  INFO 1 --- [           main] o.s.s.c.ThreadPoolTaskExecutor           : Initializing ExecutorService 'applicationTaskExecutor'
2020-12-19 15:09:25.360  INFO 1 --- [         task-1] o.h.j.i.u.LogHelper                      : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-12-19 15:09:25.420  WARN 1 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-12-19 15:09:25.482  INFO 1 --- [         task-1] o.h.Version                              : HHH000412: Hibernate ORM core version 5.4.18.Final
2020-12-19 15:09:25.880  INFO 1 --- [         task-1] o.h.a.c.Version                          : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-12-19 15:09:26.060  INFO 1 --- [           main] o.s.b.w.e.t.TomcatWebServer              : Tomcat started on port(s): 8080 (http) with context path '/demo'
2020-12-19 15:09:26.065  INFO 1 --- [           main] DeferredRepositoryInitializationListener : Triggering deferred initialization of Spring Data repositories…
2020-12-19 15:09:26.215  INFO 1 --- [         task-1] c.z.h.HikariDataSource                   : HikariPool-1 - Starting...
2020-12-19 15:09:26.989  INFO 1 --- [         task-1] c.z.h.HikariDataSource                   : HikariPool-1 - Start completed.
2020-12-19 15:09:27.033  INFO 1 --- [         task-1] o.h.d.Dialect                            : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2020-12-19 15:09:28.245  INFO 1 --- [         task-1] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-12-19 15:09:28.263  INFO 1 --- [         task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-12-19 15:09:28.831  INFO 1 --- [           main] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-12-19 15:09:28.843  INFO 1 --- [           main] c.a.d.DemoApplication                    : Started DemoApplication in 6.981 seconds (JVM running for 8.203)


測試

開啟瀏覽器在url輸入http://localhost:8080/demo/employee/all可看到以下結果。




沒有留言:

張貼留言