Quarkus の始め方 〜 Gradle 編 〜

f:id:Naotsugu:20191119224111p:plain


はじめに

クラウドネイティブなアプリケーションフレームワークである Quarkus の バージョン 1.0 リリースが間近です。

現在(2019年11月20日時点)は 1.0.0.CR1 が出ている状況です(と思った矢先に 1.0.0.CR2 が出た模様ですね)。


少し先走りで、1.0 リリース記念として Quarkus の Project Starter の使い方と Gradle での実行について見ていきます。


「Quarkus とは」については以下を参照してください。

etc9.hatenablog.com


Project Starter

Quarkus は、以下のページでオプション選択することで、プロジェクトのひな形が入手できます。

Quarkus - Start coding with code.quarkus.io


早速プロジェクトを入手していきましょう。

f:id:Naotsugu:20191119223918p:plain


(1) で Build Tool を選択します。Maven と Gradle が選択できます。

Gradle の方は Preview となっており、まだ完全ではありませんが、ここでは Gradle を選択します。


(2) では拡張機能を選択します。ここでは RESTEasy JAX-RS を選択します。

その他にも多数の Extension が用意されています。


(3) で以下の画面になりプロジェクトの zip ファイルダウンロードできます。


f:id:Naotsugu:20191119224022p:plain


プロジェクトを解答すると以下のような構成になっています。

f:id:Naotsugu:20191119224046p:plain


アプリケーションの起動

まずはそのままアプリケーションを起動してみましょう。

quarkusDev とすると開発モードでアプリケーショが起動します。

$ ./gradlew quarkusDev


http://localhost:8080 にアクセスすると以下のページが表示されます。

f:id:Naotsugu:20191119224255p:plain

これはプロジェクト内の index.html が表示されているだけです。


http://localhost:8080/hello にアクセスすると以下の表示になります。

f:id:Naotsugu:20191119224312p:plain


これは、以下の org.acme.ExampleResource.java で処理されたものです。

package org.acme;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class ExampleResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}


単純に hello を返しているだけですね。


プロジェクトの構成

まずは、build.gradle を見てみましょう。

// this block is necessary to make enforcedPlatform work for Quarkus plugin available
// only locally (snapshot) that is also importing the Quarkus BOM
buildscript {
    repositories {
        mavenLocal()
    }
    dependencies {
        classpath "io.quarkus:quarkus-gradle-plugin:${quarkusVersion}"
    }
}

plugins {
    id 'java'
}

apply plugin: 'io.quarkus'

repositories {
     mavenLocal()
     mavenCentral()
}

dependencies {
    implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
    implementation 'io.quarkus:quarkus-resteasy'

    testImplementation 'io.quarkus:quarkus-junit5'
    testImplementation 'io.rest-assured:rest-assured'

    nativeTestImplementation 'io.quarkus:quarkus-junit5'
    nativeTestImplementation 'io.rest-assured:rest-assured'
}

group 'org.acme'
version '1.0.0-SNAPSHOT'

compileJava {
    options.compilerArgs << '-parameters'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}


ついでに gradle.properties も載せておきます。

quarkusVersion=1.0.0.CR1
quarkusPlatformArtifactId=quarkus-universe-bom
quarkusPlatformVersion=1.0.0.CR1
quarkusPlatformGroupId=io.quarkus


Gradle プラグインとして quarkus-gradle-plugin を使っています。

プロジェクト作成時のオプションとして RESTEasy JAX-RS を選択したため、依存に quarkus-resteasy が追加されています。

オプションとして選択したものに応じて、各種依存が定義されたプロジェクトが作成されます。


build.gradle では 古い Gradle でプラグインを適用する定義になっています。

新しい Gradle では以下のように書くこともできます。

plugins {
    id 'java'
    id 'io.quarkus' version '1.0.0.CR1'
}

ただし、1.0.0.CR1 版では、quarkus-gradle-plugin の不具合で新しい書き方では動きませんので注意してください(正式リリース時には直っていると思います)。


ネイティブ・ビルド

Quarkus では、GraalVM を使ったネイティブバイナリを作成することができます。

ローカルに GraalVM が入っていれば(環境変数 GRAALVM_HOME が定義されていれば)、直接バイナリをビルドすることもできますが、ここでは Docker 上でビルドしてみましょう。


Docker でビルドする場合は --docker-build オプションを指定してビルドします(Dockerはインストール済みとします)。

$ ./gradlew buildNative --docker-build=true


ビルドには少々時間がかかりますが、完了すれば build ディレクトリに code-with-quarkus-1.0.0-SNAPSHOT-runner というバイナリが作成されます。


ネイティブバイナリの実行

ビルドしたネィティブバイナリはLinux 用なので、実行も Docker で行います。


最初に、少し準備が必要です。

Gradle 用のプロジェクトは Preview 版ということもあってか、Maven 用の設定になっている箇所がいくつかあります。


src/main/docker/Dockerfile.native は以下のようになっています。

FROM registry.access.redhat.com/ubi8/ubi-minimal
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

targetMaven 用なので build に変更します。

COPY build/*-runner /work/application

なお、編集時に Windows の場合は改行コードが CRLF に変わらないように注意してください。


さらにプロジェクトのルートにある .dockerignore が以下のようになっています。

*
!target/*-runner
!target/*-runner.jar
!target/lib/*


.dockerignore は docker build 時にビルドコンテキスト(通常はコマンドを叩いたカレントディレクトリ)配下のファイルをサブフォルダも合わせて Docker デーモンにファイルを送信する際に、対象外とするファイルを指定するものです。


以下のように変更します。

*
!build/*-runner
!build/*-runner.jar
!build/lib/*


これで docker build の準備ができました。プロジェクトのルードディレクトリにて docker build しましょう。

$ docker build -f src/main/docker/Dockerfile.native -t quarkus/code-with-quarkus .


-f で Dockerfile の場所を指定、-t でタグ名を指定、末尾の . でカレントディレクトリをビルドコンテキストに指定しています。


以下のような出力と共に、Docker コンテナが作成されます。

Sending build context to Docker daemon  31.29MB
Step 1/6 : FROM registry.access.redhat.com/ubi8/ubi-minimal
 ---> 469119976c56
Step 2/6 : WORKDIR /work/
 ---> Using cache
 ---> 14cb18a3903d
Step 3/6 : COPY build/code-with-quarkus-1.0.0-SNAPSHOT-runner /work/application
 ---> 244714077ecf
Step 4/6 : RUN chmod 775 /work
 ---> Running in b9db39c49807
Removing intermediate container b9db39c49807
 ---> 376a646a0478
Step 5/6 : EXPOSE 8080
 ---> Running in c6f6d7fd8c70
Removing intermediate container c6f6d7fd8c70
 ---> ff2ab5fb1953
Step 6/6 : CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
 ---> Running in 57917c5b1593
Removing intermediate container 57917c5b1593
 ---> cb723c9e7750
Successfully built cb723c9e7750
Successfully tagged quarkus/code-with-quarkus:latest


コンテナが作成されたら、早速 起動しましょう。

$ docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus


以下のように秒以下で起動します。

2019-11-20 10:42:30,921 INFO  [io.quarkus] (main) code-with-quarkus 1.0.0-SNAPSHOT (running on Quarkus 1.0.0.CR1) started in 0.006s. Listening on: http://0.0.0.0:8080
2019-11-20 10:42:30,921 INFO  [io.quarkus] (main) Profile prod activated.
2019-11-20 10:42:30,921 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]


http://localhost:8080/hello にアクセスすると以下の表示になり、起動していることが分かります。

f:id:Naotsugu:20191120194341p:plain


JVM モードのコンテナ作成

先ほどはネィティブバイナリを使ってコンテナ起動しましたが、jar を直接起動するコンテナも簡単に作成できます。


jar を以下で作成します。

$ ./gradlew quarkusBuild


build ディレクトリに code-with-quarkus-1.0.0-SNAPSHOT-runner.jar が作成されます。


では、先ほどと同じ流れで docker build しましょう。

JVM モード用の Dockerfile は src/main/docker/Dockerfile.jvm に用意されています。

こちらも先ほどと同じく target -> build に変更して以下のようになります

FROM fabric8/java-alpine-openjdk8-jre
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV AB_ENABLED=jmx_exporter
COPY build/lib/* /deployments/lib/
COPY build/*-runner.jar /deployments/app.jar
EXPOSE 8080

# run with user 1001 and be prepared for be running in OpenShift too
RUN adduser -G root --no-create-home --disabled-password 1001 \
  && chown -R 1001 /deployments \
  && chmod -R "g+rwX" /deployments \
  && chown -R 1001:root /deployments
USER 1001

ENTRYPOINT [ "/deployments/run-java.sh" ]


$ docker build -f src/main/docker/Dockerfile.jvm -t quarkus/code-with-quarkus-jvm .


以下のようにコンテナが作成されます。

Sending build context to Docker daemon  31.29MB
Step 1/9 : FROM fabric8/java-alpine-openjdk8-jre
 ---> 6383f0513235
Step 2/9 : ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
 ---> Using cache
 ---> e2e8ca4359b6
Step 3/9 : ENV AB_ENABLED=jmx_exporter
 ---> Using cache
 ---> c73944d90c45
Step 4/9 : COPY build/lib/* /deployments/lib/
 ---> 06772c22ad5c
Step 5/9 : COPY build/*-runner.jar /deployments/app.jar
 ---> c91141d2157f
Step 6/9 : EXPOSE 8080
 ---> Running in 12b3b2e73ca1
Removing intermediate container 12b3b2e73ca1
 ---> de4f26ce38bb
Step 7/9 : RUN adduser -G root --no-create-home --disabled-password 1001   && chown -R 1001 /deployments   && chmod -R "g+rwX" /deployments   && chown -R 1001:root /deployments
 ---> Running in c218022a1ad8
Removing intermediate container c218022a1ad8
 ---> 368b377a51e2
Step 8/9 : USER 1001
 ---> Running in b822230b842b
Removing intermediate container b822230b842b
 ---> b0b9a5bfa94a
Step 9/9 : ENTRYPOINT [ "/deployments/run-java.sh" ]
 ---> Running in a31d01fdab46
Removing intermediate container a31d01fdab46
 ---> 5295a11b2e32
Successfully built 5295a11b2e32
Successfully tagged quarkus/code-with-quarkus-jvm:latest


実行は以下のコマンドで行います。

$ docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-jvm

ネイティブ版には敵いませんが、2秒程度で起動します。

http://localhost:8080/hello にアクセスすると以下の表示になり、起動していることが分かります。

f:id:Naotsugu:20191120194341p:plain


最後に、ここで起動したアプリケーションに対してテストを行いましょう。


RestAssured によるテスト

RestAssured によるテストのひな形は ExampleResourceTest.java として用意されています。

package org.acme;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class ExampleResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/hello")
          .then()
             .statusCode(200)
             .body(is("hello"));
    }

}


テストは以下のコマンドで行います。

$ ./gradlew test

起動中のアプリケーションに対してテストが行われ、build/reports/tests/test/index.html に以下のようなレポートが作成されます。

f:id:Naotsugu:20191120214235p:plain


まとめ

Quarkus の プロジェクト作成からコンテナによる実行までを見てきました。

Maven 優先で開発されており、Gradle サポートは Preview 扱いなので、躓く点も多少ありますが、一通りの動作は確認できました。


その他機能についても追って見ていきたいと思います。



Kubernetes完全ガイド (impress top gear)

Kubernetes完全ガイド (impress top gear)

試して学ぶ Dockerコンテナ開発

試して学ぶ Dockerコンテナ開発

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ