| //// |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| //// |
| = Apache Geronimo Arthur Maven |
| |
| Arthur Maven plugin provides a way to generate `native-image` configuration and execute it. |
| It also enables to automatically download GraalVM avoiding you to manage the installation part. |
| |
| == Get started in 5 minutes |
| |
| First thing to do to enable a native build of your application is to add the plugin with your application entry point (`main(String[])`): |
| |
| [source,xml] |
| ---- |
| <plugin> |
| <groupId>org.apache.geronimo.arthur</groupId> |
| <artifactId>arthur-maven-plugin</artifactId> |
| <version>${arthur.version}</version> |
| <configuration> |
| <main>org.kamelot.Florida</main> <1> |
| </configuration> |
| </plugin> |
| ---- |
| |
| <1> The application to compile natively |
| |
| Once it is done, you can run `mvn [process-classes] arthur:native-image`. |
| |
| == Graal Setup |
| |
| You probably notices that in previous part we didn't mention you need to set your `JAVA_HOME` to a Graal instance or so. |
| This is because the plugin is able to download GraalVM if it is not already done and to install - using GraalVM `gu` tool - `native-image`extension. |
| You will find all the details of that feature in the configuration section but it is important to note a few points: |
| |
| 1. You can explicit the `native-image` instance to use and avoid the implicit installation setting the configuration `nativeImage`, |
| 2. GraalVM version is configurable (note that it relies on SDKMan by default so ensure the last version you want to upgrade immediately is available), |
| 3. The plugin caches the GraalVM archive and its unpack flavor in your local maven repository to avoid to download and explode it each time. |
| |
| == Execution example |
| |
| Here is a dump of a sample execution: |
| |
| [source] |
| ---- |
| $ mvn arthur:native-image -o |
| [INFO] Scanning for projects... |
| [INFO] |
| [INFO] ---------------------< org.apache.geronimo.arthur:sample >--------------------- |
| [INFO] Building Arthur :: Sample 1.0.0-SNAPSHOT |
| [INFO] --------------------------------[ jar ]--------------------------------- |
| [INFO] |
| [INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:native-image (default-cli) @ jdbc --- |
| [INFO] Using GRAAL: /home/rmannibucau/.m2/repository/org/apache/geronimo/arthur/cache/graal/19.2.1/distribution_exploded <1> |
| [INFO] Extension org.apache.geronimo.arthur.maven.extension.MavenArthurExtension updated build context <2> |
| [INFO] Creating resources model '/opt/dev/arthur/sample/target/arthur_workdir/generated_configuration/resources.arthur.json' <3> |
| [INFO] Creating dynamic proxy model '/opt/dev/arthur/sample/target/arthur_workdir/generated_configuration/dynamicproxies.arthur.json' <3> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] classlist: 6,427.14 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (cap): 2,126.93 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] setup: 3,480.51 ms <4> |
| ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated |
| ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (typeflow): 15,605.58 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (objects): 11,369.56 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (features): 1,901.64 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] analysis: 30,057.86 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (clinit): 568.59 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] universe: 1,196.02 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (parse): 2,334.15 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (inline): 3,093.09 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] (compile): 20,410.16 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] compile: 27,352.51 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] image: 2,792.40 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] write: 677.26 ms <4> |
| /opt/dev/arthur/sample/target/sample.graal.bin:16697] [total]: 72,280.13 ms <4> |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] BUILD SUCCESS |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] Total time: 01:14 min |
| [INFO] Finished at: 2019-10-29T17:55:16+01:00 |
| [INFO] ------------------------------------------------------------------------ |
| |
| ---- |
| |
| <1> The GraalVM distribution was already existing so was not download and was directly used, |
| <2> Maven inline Graal configuration (resources, reflection, bundles) was set up, |
| <3> Dynamic (Arthur prebuild phase) configuration was dumped before launching `native-image`, |
| <4> `native-image` execution/phases |
| |
| == Native Image Mojo configuration |
| |
| The plugin is quite configurable and even enable to build a main in a test scope. |
| Here is the full list of available configuration. |
| |
| include::{generated_dir}/generated_nativeimage_mojo.adoc[] |
| |
| |
| TIP: if you want to debug native image generation, you must add `org.graalvm.nativeimage:svm` dependency and add the `customOption` `--debug-attach`. |
| |
| == What about docker? |
| |
| One of the main reasons to go native is to reduce the startup latency and the memory consumption. |
| This is literally a paraphrase to say we want to run into Kubernetes. |
| Therefore the question to bundle the native binary as a docker image comes pretty quickly. |
| |
| === Sample Dockerfile |
| |
| There are multiple option to create a docker image but one interesting point to mention is to build the binary in a docker image to ensure the target binary matches the target image architecture. |
| For that case, we recommend you to build your project as you want, copy the project in a builder docker image and use a multi-stage builder to ensure you build on the platform you will run (the `FROM` of your `Dockerfile`). |
| |
| TIP: to avoid a lot of downloads/latency it can be neat to *not* bind `arthur:native-image` plugin goal to any phase and just call it explicitly in your build. To do that just use `<phase />` instead of an explicit phase if you define a custom execution. |
| |
| Here is an example: |
| |
| [source,Dockerfile] |
| ---- |
| FROM maven:3.6-jdk-8-slim AS builder |
| COPY . /project |
| COPY target/m2 /root/.m2/repository |
| WORKDIR /project |
| RUN mvn package arthur:native-image |
| |
| FROM scratch |
| COPY --from=builder /project/target/myapp.graal.bin /project/myapp.graal.bin |
| ENTRYPOINT [ "/project/myapp.graal.bin" ] |
| ---- |
| |
| To avoid to download all needed dependencies all the time don't forget to prepare a local copy of the maven repository, here is how to launch this docker creation assuming you put this `Dockerfile` at the root of your maven project: |
| |
| [source,sh] |
| ---- |
| # prepare the local copy of the m2 with dependency plugin (for examples, some other plugins will also include build maven plugins in the dedicated m2) |
| mvn dependency:copy-dependencies -Dmdep.useRepositoryLayout=true -DoutputDirectory=target/m2 |
| |
| # actually build |
| docker build -t sample:latest |
| ---- |
| |
| TIP: you can also do the package on your machine and skip it in the docker build but the tip to prepare a local m2 copy is still helping to speed up the build. |
| |
| TIP: if you use private repositories, don't forget to copy your settings.xml as well (in `/root/.m2` for `maven`/builder image). |
| |
| IMPORTANT: when building on a jenkins running on Kubernetes, ensure to use a build image with an architecture compatible, this avoids all that setup and you can just run it directly as if it was locally. |
| |
| === Jib to create an image |
| |
| Jib is an awesome project propulsed by Google enabling to build docker images without docker. |
| It can be used to put your binary into a very light image (based on the "empty" `scratch` one). Here is how to add it to your pom: |
| |
| [source,xml] |
| ---- |
| <plugin> <1> |
| <groupId>com.google.cloud.tools</groupId> |
| <artifactId>jib-maven-plugin</artifactId> |
| <version>1.7.0</version> |
| <configuration> |
| <from> |
| <image>scratch</image> <2> |
| </from> |
| <to> |
| <image>sample:latest</image> |
| </to> |
| <extraDirectories> |
| <paths> |
| <path>${project.build.directory}/graal</path> <3> |
| </paths> |
| <permissions> |
| <permission> <4> |
| <file>/sample.graal.bin</file> |
| <mode>755</mode> |
| </permission> |
| </permissions> |
| </extraDirectories> |
| <container> |
| <creationTime>USE_CURRENT_TIMESTAMP</creationTime> |
| <entrypoint> |
| <arg>/sample.graal.bin</arg> <5> |
| </entrypoint> |
| </container> |
| </configuration> |
| </plugin> |
| ---- |
| |
| <1> Add `jib-maven-plugin` in your `pom.xml` |
| <2> Use `scratch` keyword as base image (which almost means "nothing" or "empty image") |
| <3> Reference the folder containing your binary (you can desire to tune the `output`parameter of the `arthur-maven-plugin` to ensure the folder only contains your binary since the full folder will be added to the image) |
| <4> Ensure the binary has the right permission (executable) |
| <5> Set the binary as entrypoint |
| |
| Then you can create your docker image just launching: `mvn jib:dockerBuild`. |
| |
| TIP: you can also directly push to a remote repository without the need of having a local docker daemon, see Jib documentation for more details. |
| |
| IMPORTANT: in current jib version, the dependencies are build artifacts are still added to the image so you can have some "dead" code with that option. |
| To avoid it you can switch to `arthur:docker` or `arthur:image` goals. |
| |
| === Arthur Docker |
| |
| `arthur-maven-plugin` enables to build a docker image with a plain binary built with `native-image`. |
| Its usage is close to jib - with a simpler configuration - but it also can be combine with `native-image` goal: |
| |
| [source,bash] |
| ---- |
| mvn arthur:native-image arthur:docker |
| ---- |
| |
| Output looks like: |
| |
| [source] |
| ---- |
| $ mvn arthur:native-image arthur:docker |
| [INFO] Scanning for projects... |
| [INFO] |
| [INFO] ---------------------< org.apache.geronimo.Arthur:sample >--------------------- |
| [INFO] Building Arthur :: Sample 1.0.0-SNAPSHOT |
| [INFO] --------------------------------[ jar ]--------------------------------- |
| [INFO] |
| [INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:native-image (default-cli) @ sample --- |
| [INFO] Using GRAAL: /home/rmannibucau/.m2/repository/org/apache/geronimo/arthur/cache/graal/19.2.1/distribution_exploded |
| [INFO] Extension org.apache.geronimo.arthur.maven.extension.MavenArthurExtension updated build context |
| [INFO] Creating resources model '/media/data/home/rmannibucau/1_dev/connectors-se/jdbc/target/arthur_workdir/generated_configuration/resources.arthur.json' |
| [INFO] Creating dynamic proxy model '/media/data/home/rmannibucau/1_dev/connectors-se/jdbc/target/arthur_workdir/generated_configuration/dynamicproxies.arthur.json' |
| ...same as before |
| [INFO] |
| [INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:docker (default-cli) @ sample --- |
| [INFO] Containerizing application with the following files: |
| [INFO] Binary: |
| [INFO] /opt/geronimo/arthur/sample/target/sample.graal.bin |
| [INFO] Getting scratch base image... |
| [INFO] Building Binary layer... |
| [INFO] |
| [INFO] Container entrypoint set to [/sample] |
| [INFO] Container program arguments set to [] |
| [INFO] Loading to Docker daemon... |
| [INFO] Built 'sample:1.0.0-SNAPSHOT' |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] BUILD SUCCESS |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] Total time: 01:29 min |
| [INFO] Finished at: 2019-10-30T10:51:43+01:00 |
| [INFO] ------------------------------------------------------------------------ |
| ---- |
| |
| ==== LDD or not |
| |
| By default Arthur maven docker/image plugins assume the binary is self executable by itself (statically linked). |
| If it is not the case, you need to ensure the from/base image has the needed libraries. |
| It is not always trivial nor convenient so we have a mode using the build machine `ldd` to detect which libs are needed and add them in the image parsing `ldd` output. |
| |
| IMPORTANT: this mode enables to build very light native images using `scratch` virtual base image (empty) but it can conflict with other base images since it sets `LD_LIBRARY_PATH` if not present int he `environment` configuration. |
| |
| Here is what the `ldd` output look like - if not the case ensure to set a PATH before running the plugin which has a compliant ldd: |
| |
| [source] |
| ---- |
| $ ldd doc.graal.bin |
| linux-vdso.so.1 (0x00007ffcc41ba000) |
| libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f4ad85e7000) |
| libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4ad85e1000) |
| libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4ad83ef000) |
| /lib64/ld-linux-x86-64.so.2 (0x00007f4adba6b000) |
| ---- |
| |
| The parser will take all line containing a `/` and the first string without any space starting by a `/` will be considered as a path of a library to include. |
| With previous output, the image will get `libpthread.so.0`, `libdl.so.2`, `libc.so.6` and `ld-linux-x86-64.so.2` included (but not `linux-vdso.so.1` since it is not a path / it has no slash in the line). |
| |
| ==== Configuration |
| |
| include::{generated_dir}/generated_docker_mojo.adoc[] |
| |
| === Arthur Image |
| |
| Similarly to docker goal, the plugin can generate an image. |
| To do it just replace the `arthur:docker` by `arthur:image`. |
| |
| TIP: this goal does *not* need a docker daemon on the machine, it just uses HTTP(S) communication with a registry. |
| |
| IMPORTANT: if you use `static` build you can use `scratch` as base image (empty) but if you use `-H:+StaticExecutableWithDynamicLibC` which statically link everything but glic you should switch to `busybox:glic` for example. |
| Also note that some applications will require more libraries like libdl so ensure to adjust your base image on your usage. |
| |
| ==== Configuration |
| |
| include::{generated_dir}/generated_image_mojo.adoc[] |
| |
| == Advanced example |
| |
| Just to give a real world configuration, here is how a simple JDBC application can be natified. |
| It configure some resource bundle, setup h2 driver and exclude derby from the build classpath: |
| |
| [source,xml] |
| ---- |
| <plugin> |
| <groupId>org.apache.geronimo.arthur</groupId> |
| <artifactId>arthur-maven-plugin</artifactId> |
| <version>${arthur.version}</version> |
| <configuration> |
| <main>org.talend.components.jdbc.graalvm.MainTableNameInputEmitter</main> |
| <supportTestArtifacts>true</supportTestArtifacts> <!-- for demo only --> |
| <initializeAtBuildTime> |
| <initializeAtBuildTime>org.h2.Driver</initializeAtBuildTime> |
| <initializeAtBuildTime>org.company.MyProxyInterface1</initializeAtBuildTime> |
| <initializeAtBuildTime>org.company.MyProxyInterface2</initializeAtBuildTime> |
| </initializeAtBuildTime> |
| <bundles> |
| <bundle> |
| <name>org.company.MyProxyInterface1Messages</name> |
| </bundle> |
| </bundles> |
| <dynamicProxies> |
| <dynamicProxy> <!-- short notation --> |
| <classes>org.company.MyProxyInterface1</classes> |
| </dynamicProxy> |
| <dynamicProxy> |
| <classes> <!-- long notation --> |
| <class>org.company.MyProxyInterface2</class> |
| </classes> |
| </dynamicProxy> |
| </dynamicProxies> |
| <excludedArtifacts> <!-- this setup uses supportTestArtifacts but we assume we don't want derby which is used in test dependencies --> |
| <excludedArtifact>org.apache.derby:derby</excludedArtifact> |
| <excludedArtifact>org.apache.derby:derbyclient</excludedArtifact> |
| <excludedArtifact>org.apache.derby:derbynet</excludedArtifact> |
| </excludedArtifacts> |
| </configuration> |
| </plugin> |
| ---- |
| |
| Then you can just run `mvn [package] arthur:native-image arthur:docker` to get a ready to deploy image. |
| |
| --- |
| |
| Previous: link:implementation.html[Arthur Implementation] Next: link:knights.adoc[Knights] |