SpringBoot use JNR on Container

Springboot on Containerの続きの内容です。

構成図の通り、SpringBoot上でJNRを使用し、Cライブラリ(.so)を使ってみようと思います。

1. Technical Elements

  • Spring Boot
    • Spring Framework ベースのアプリケーションを手軽に作成することができるフレームワーク
  • Docker
    • コンテナ仮想化を用いた OS レベルの仮想環境の構築ツール
  • GoogleContainerTools/jib: 🏗
    • jib-gradle-plugin
    • Java アプリケーションを実行する Docker コンテナイメージを簡単に作成するためのツール
  • GCP(Google Cloud Platform)
    • Google が提供しているクラウドコンピューティングサービス
      • Google 検索や YouTube などでも同じインフラストラクチャーが利用されている。
  • jnr/jnr-ffi
    • Javaから簡単にCのライブラリを呼び出せるフレームワーク

2. Coding & Settings of Each Technical Element

2.1. SpringBoot and JNR

2.1.1. RestController.java

 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
@RestController
@Slf4j
public class RestController {

    @GetMapping("/jnr")
    public void jnr(){
        var stopWatch = new StopWatch();

        stopWatch.start("load");
        var nativeCaller = LibraryLoader.create(NativeCaller.class).load("jnr-native-side_release-0.0.1");
        stopWatch.stop();

        stopWatch.start("exec");
        IntStream.range(1, 10000).forEach(value -> {
            var result = nativeCaller.euclid(value, value * 2);
            if (value % 1000 == 0) {
                log.info("result : " + result);
            }
        });
        stopWatch.stop();

        log.info(stopWatch.prettyPrint());

        // Environment variable reading test
        log.info("hoge value = {}", System.getenv("hoge"));
    }
}

2.1.2. NativeCaller.java

1
2
3
public interface NativeCaller {
    int euclid(int m, int n);
}

2.1.3. euclid.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "euclid.h"

extern "C" int euclid(int m, int n);
int euclid(int m, int n)
{
    int tmp, r;
    if(n > m){
        tmp = m;
        m = n;
        n = tmp;
    }
    r = m % n;
    if(r == 0) return n;
    return euclid(n, r);
}

2.2. jib-gradle-plugin

2.2.1. build.gradle

 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
plugins {
  // for docker
  id "com.google.cloud.tools.jib" version "2.1.0"
}

dependencies {
  // jnr
  implementation 'com.github.jnr:jnr-ffi:2.1.11'
  implementation 'org.ow2.asm:asm:7.3.1'
  implementation 'org.ow2.asm:asm-commons:7.3.1'
  implementation 'org.ow2.asm:asm-analysis:7.3.1'
  implementation 'org.ow2.asm:asm-tree:7.3.1'
  implementation 'org.ow2.asm:asm-util:7.3.1'
}

// 省略

jib {
  // Copies files from 'src/main/clib' to '/'(default)
  // from : src/main/clib/jnr-native-side_release/0.0.1/*.so
  // to   : /jnr-native-side_release/0.0.1/*.so
  extraDirectories.paths = ['src/main/clib']
  // Set docker image name
  to.image = 'springio/gs-spring-boot-docker-jnr'
  container.environment = [
          // Set jvm option
          JAVA_TOOL_OPTIONS:"-Djava.library.path=/jnr-native-side_release/0.0.1",
          // Set test environment value
          hoge:"test"
      ]
}

3. Build and Run

3.1. Build

1
$ gradle jibDockerBuild

3.2. Run

1
$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker-jnr

4. Trouble

jib.extraDirectories.pathsで指定したファイルが、コンテナ上のどこにあるのか分からない。

コンテナの中を実際に見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ mkdir tmp
$ docker container ls
CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS              PORTS                    NAMES
d48bfc8bfad8        springio/gs-spring-boot-docker-jnr   "java -cp /app/resou…"   3 minutes ago       Up 3 minutes        0.0.0.0:8080->8080/tcp   loving_morse
$ docker export d48bfc8bfad8  > ./tmp/gs-spring-boot-docker-jnr.tar
$ tar -xvf ./tmp/gs-spring-boot-docker-jnr.tar 
$ ll
total 217372
drwxr-xr-x.  5   (ユーザ名)        50 Jan  1  1970 app
drwxr-xr-x.  2   (ユーザ名)         6 Jan  1  1970 bin
drwxr-xr-x.  2   (ユーザ名)         6 Jan  1  1970 boot
drwxr-xr-x.  4   (ユーザ名)        43 Apr  7 08:24 dev
drwxr-xr-x. 12   (ユーザ名)      4096 Apr  7 08:24 etc
-rw-rw-r--.  1   (ユーザ名) 222581248 Apr  7 08:41 gs-spring-boot-docker-jnr.tar
drwxr-xr-x.  3   (ユーザ名)        21 Jan  1  1970 home
drwxr-xr-x.  3   (ユーザ名)       172 Jan  1  1970 jnr-native-side_release
drwxr-xr-x.  3   (ユーザ名)        30 Jan  1  1970 lib
drwxr-xr-x.  2   (ユーザ名)        34 Jan  1  1970 lib64
drwxr-xr-x.  2   (ユーザ名)         6 Jan  1  1970 proc
drwx------.  2   (ユーザ名)         6 Jan  1  1970 root
drwxr-xr-x.  2   (ユーザ名)         6 Jan  1  1970 run
drwxr-xr-x.  2   (ユーザ名)         6 Jan  1  1970 sbin

5. Execution Result

 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
$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker-jnr
Picked up JAVA_TOOL_OPTIONS: -Djava.library.path=/jnr-native-side_release/0.0.1

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)
2020-04-07 08:14:42.569  INFO 1 --- [           main] c.e.s.SpringbootHelloworldApplication    : Starting SpringbootHelloworldApplication on d48bfc8bfad8 with PID 1 (/app/clas
ses started by root in /)
2020-04-07 08:14:42.579  INFO 1 --- [           main] c.e.s.SpringbootHelloworldApplication    : No active profile set, falling back to default profiles: default
2020-04-07 08:14:45.920  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-04-07 08:14:45.957  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-04-07 08:14:45.957  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]
2020-04-07 08:21:33.184  INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-04-07 08:21:33.187  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-04-07 08:21:33.220  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 32 ms
2020-04-07 08:21:33.680  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 1000
2020-04-07 08:21:33.680  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 2000
2020-04-07 08:21:33.680  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 3000
2020-04-07 08:21:33.681  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 4000
2020-04-07 08:21:33.681  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 5000
2020-04-07 08:21:33.681  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 6000
2020-04-07 08:21:33.681  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 7000
2020-04-07 08:21:33.681  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 8000
2020-04-07 08:21:33.681  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : result : 9000
2020-04-07 08:21:33.682  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : StopWatch '': running time = 387303705 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
366506118  095%  load
020797587  005%  exec
2020-04-07 08:21:33.682  INFO 1 --- [nio-8080-exec-1] c.e.springboothelloworld.RestController  : hoge value = test