【发布时间】:2022-08-18 23:38:10
【问题描述】:
哟哟!
范围
我正在尝试使用一些将基于 Quarkus 的应用程序部署到 Raspberry Pi花哨的技术,我的目标是找到一种简单的方法来使用 Quarkus 框架开发应用程序,然后作为本机可执行文件部署到具有完整 GPIO 引脚访问权限的树莓设备。下面我将提供您将看到我为自己和我的环境设置设置的要求,以便更好地了解我所面临的问题。
验收标准
- Java 17
- 使用 GraalVM 构建本机可执行文件
- 在 raspberry 的 docker 上的微镜像中执行本机可执行文件
- 目标平台可能不同
- 能够使用树莓派的GPIO、SPI、I2C等接口
环境
Development PC Raspberry Pi Model 3 B+ os: Ubuntu 22.04.1 LTS os: DietPy platform: x86_64, linux/amd64 platform: aarch64, linux/arm64/v8 先决条件
- Java: diozero a device I/O library
- Docker: working with buildx
- Quarkus: build a native executable
- How I built ARM based Docker Images for Raspberry Pi using buildx CLI Plugin on Docker Desktop?
- Building Multi-Architecture Docker Images With Buildx
应用
- 至于我使用的项目库入门从申请 https://github.com/quarkusio/quarkus-quickstarts
- 将 diozero 库添加到 pom.xml
<dependency> <groupId>com.diozero</groupId> <artifactId>diozero-core</artifactId> <version>1.3.3</version> </dependency> - 创建一个简单的资源来测试 GPIO 引脚
package org.acme.getting.started; import com.diozero.devices.LED; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; @Path(\"led\") public class LedResource { @Path(\"on\") public String turnOn(final @QueryParam(\"gpio\") Integer gpio) { try (final LED led = new LED(gpio)) { led.on(); } catch (final Throwable e) { return e.getMessage(); } return \"turn on led on gpio \" + gpio; } @Path(\"off\") public String turnOff(final @QueryParam(\"gpio\") Integer gpio) { try (final LED led = new LED(gpio)) { led.off(); } catch (final Throwable e) { return e.getMessage(); } return \"turn off led on gpio \" + gpio; } }4.Dockerfile
``` # Stage 1 : build with maven builder image with native capabilities FROM quay.io/quarkus/ubi-quarkus-native-image:22.0.0-java17-arm64 AS build COPY --chown=quarkus:quarkus mvnw /code/mvnw COPY --chown=quarkus:quarkus .mvn /code/.mvn COPY --chown=quarkus:quarkus pom.xml /code/ USER quarkus WORKDIR /code RUN ./mvnw -B org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline COPY src /code/src RUN ./mvnw package -Pnative # Stage 2 : create the docker final image FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6-902 WORKDIR /work/ COPY --from=build /code/target/*-runner /work/application # set up permissions for user 1001 RUN chmod 775 /work /work/application \\ && chown -R 1001 /work \\ && chmod -R \"g+rwX\" /work \\ && chown -R 1001:root /work EXPOSE 8080 USER 1001 CMD [\"./application\", \"-Dquarkus.http.host=0.0.0.0\"] ```使用本机可执行文件构建映像
基于quarkus docs的Dockerfile,我将构建容器的镜像更改为
quay.io/quarkus/ubi-quarkus-native-image:22.0.0-java17-arm64,将执行容器的镜像更改为registry.access.redhat.com/ubi8/ubi-minimal:8.6-902,这两个都符合linux/arm64*。由于我正在
linux/amd64中开发和构建,并且我想以linux/arm64/v8为目标,因此我的可执行文件必须在类似目标的环境中创建。我可以做到这一点构建x为 docker 镜像启用跨架构构建的功能。- 安装 QEMU
sudo apt-get install -y qemu-user-staticsudo apt-get install -y binfmt-support- 为
linux/arm64/v8构建初始化 buildx
sudo docker buildx create --platform linux/arm64/v8 --name arm64-v8- 使用新的驱动程序
sudo docker buildx use arm64-v8- 引导驱动程序
sudo docker buildx inspect --bootstrap- 验证
sudo docker buildx inspectName: arm64-v8 Driver: docker-container Nodes: Name: arm64-v80 Endpoint: unix:///var/run/docker.sock Status: running Platforms: linux/arm64*, linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6现在看起来我们已经准备好运行构建了。我结束了以下命令
sudo docker buildx build --push --progress plain --platform linux/arm64/v8 -f Dockerfile -t nanobreaker/agus:arm64 .-
--push- 因为我需要在某处部署最终映像 -
--platform linux/arm64/v8- docker 需要定义目标平台 -
-t nanobreaker/agus:arm64- 我的最终图像的目标存储库
- 目标平台是
linux/arm64,根据需要 - 59.75 MB 图像大小,已经足够好(使用微图像我可以达到 ~10 MB)
连接到覆盆子后,下载图像并运行它
docker run -p 8080:8080 nanobreaker/agus:arm64很好,让我们尝试执行一个 http 请求来测试 gpio 引脚
curl 192.168.0.20:8080/led/on?gpio=3好的,所以我在这里看到存在权限问题,并且 diozero 库不在 java.library.path 中
我们可以通过向 docker run 命令添加额外的参数来修复权限问题
docker run --privileged -p 8080:8080 nanobreaker/agus:arm64问题
从这一点来看,我不知道如何解决本机可执行文件中的库加载错误。
我试过了:
- 从最终容器中提取本机可执行文件,在 raspberry 主机操作系统上执行并得到相同的结果,这让我认为 GraalVM 编译时不包含库?
- 了解如何加载库https://github.com/mattjlewis/diozero/blob/main/diozero-core/src/main/java/com/diozero/util/LibraryLoader.java
更新我
看起来我在这里有两个选择
- 找出一种为 diozero 库创建配置的方法,以便在本机映像编译期间由 GraalVM 正确解析。
- 将库添加到本机映像并将其传递给本机可执行文件。
更新二
进一步阅读 quarkus 文档让我在这里https://quarkus.io/guides/writing-native-applications-tips
默认情况下,在构建本机可执行文件时,GraalVM 不会将类路径上的任何资源包含到它创建的本机可执行文件中。需要明确配置本机可执行文件的一部分的资源。 Quarkus 自动包含 META-INF/resources(网络资源)中存在的资源,但在此目录之外,您只能靠自己。
我联系了@Matt Lewis(diozero 的创建者),他很乐意分享他的配置,他曾经将这些配置编译到 GraalVM 中。谢谢马特!
这是我最初测试的文档:https://www.diozero.com/performance/graalvm.html 我将 GraalVM 配置存放在这里:https://github.com/mattjlewis/diozero/tree/main/src/main/graalvm/config
所以结合我们可以丰富 pom.xml 的知识和额外的设置来告诉 GraalVM 如何处理我们的库
<quarkus.native.additional-build-args> -H:ResourceConfigurationFiles=resource-config.json, -H:ReflectionConfigurationFiles=reflection-config.json, -H:JNIConfigurationFiles=jni-config.json, -H:+TraceServiceLoaderFeature, -H:+ReportExceptionStackTraces </quarkus.native.additional-build-args>还在项目的资源文件夹(src/main/resources)中添加了resource-config.json、reflection-config.json、jni-config.json
首先,我将尝试在我的原生操作系统
./mvnw package -Dnative中创建一个原生可执行文件Fatal error: org.graalvm.compiler.debug.GraalError: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of java.lang.ProcessHandleImpl are allowed in the image heap as this class should be initialized at image runtime. To see how this object got instantiated use --trace-object-instantiation=java.lang.ProcessHandleImpl.好吧,所以它失败了,但是让我们按照建议跟踪对象实例化,也许我们可以在配置中做一些事情来解决这个问题。我将
--trace-object-instantiation=java.lang.ProcessHandleImpl添加到附加的构建参数中。Fatal error: org.graalvm.compiler.debug.GraalError: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of java.lang.ProcessHandleImpl are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized by the java.lang.ProcessHandleImpl class initializer with a trace: at java.lang.ProcessHandleImpl.<init>(ProcessHandleImpl.java:227) at java.lang.ProcessHandleImpl.<clinit>(ProcessHandleImpl.java:77) . To fix the issue mark java.lang.ProcessHandleImpl for build-time initialization with --initialize-at-build-time=java.lang.ProcessHandleImpl or use the the information from the trace to find the culprit and --initialize-at-run-time=<culprit> to prevent its instantiation.至少有一些新的东西,让我们尝试在构建时先用
--initialize-at-build-time=java.lang.ProcessHandleImpl初始化它Error: Incompatible change of initialization policy for java.lang.ProcessHandleImpl: trying to change BUILD_TIME from command line with \'java.lang.ProcessHandleImpl\' to RERUN for JDK native code support via JNI com.oracle.svm.core.util.UserError$UserException: Incompatible change of initialization policy for java.lang.ProcessHandleImpl: trying to change BUILD_TIME from command line with \'java.lang.ProcessHandleImpl\' to RERUN for JDK native code support via JNI好吧,我们无法更改初始化类型,而且看起来它不会给我们带来任何影响。 我发现使用
-H:+PrintClassInitialization我们可以生成一个带有类初始化信息的 csv 文件 这里我们有两行java.lang.ProcessHandleImpljava.lang.ProcessHandleImpl, RERUN, for JDK native code support via JNI java.lang.ProcessHandleImpl$Info, RERUN, for JDK native code support via JNI所以它说这个类被标记为
RERUN,但这不是我们要找的东西吗?现在对我来说毫无意义。更新三
使用@Matt 提供的 graalvm 配置,我能够编译本机映像,但由于
java.lang.UnsatisfiedLinkError,它在运行时无论如何都会失败,这让我觉得库没有正确注入。所以看起来我们只需要构建一个适当的配置文件,为了做到这一点,让我们现在构建我们的应用程序没有原生,只需在 raspberry 上运行它,触发与 diozero 相关的代码,获取输出配置。
./mvnw clean package -Dquarkus.package.type=uber-jar部署到 raspberry,将使用 graalvm 代理运行以生成配置 (https://www.graalvm.org/22.1/reference-manual/native-image/Agent/)
/$GRAALVM_HOME/bin/java -agentlib:native-image-agent=config-output-dir=config -jar ags-gateway-1.0.0-SNAPSHOT-runner.jar运行简单的请求来触发 diozero 代码(我在 gpio 4 上连接了一个 led 到树莓派,实际上看到它关闭/打开)
curl -X POST 192.168.0.20:8080/blink/off?gpio=4curl -X POST 192.168.0.20:8080/blink/on?gpio=4我已经发布了project 和output configs
一件事我注意到
\"pattern\":\"\\\\Qlib/linux-aarch64/libdiozero-system-utils.so\\\\E\"aarch64 库在 py 上运行时被拉取,这是正确的,但是当我在本机操作系统上构建时,我应该指定 \'amd64\' 平台。让我们尝试用新的配置构建一个原生
./mvnw package -Dnative编译成功,我们运行测试
./target/ags-gateway-1.0.0-SNAPSHOT-runner curl -X POST localhost:8080/led/on?gpio=4在这里我们又遇到了错误
ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-0) HTTP Request to /led/on?gpio=4 failed, error id: b0ef3f8a-6813-4ea8-886f-83f626eea3b5-1: java.lang.UnsatisfiedLinkError: com.diozero.internal.provider.builtin.gpio.NativeGpioDevice.openChip(Ljava/lang/String;)Lcom/diozero/internal/provider/builtin/gpio/GpioChip; [symbol: Java_com_diozero_internal_provider_builtin_gpio_NativeGpioDevice_openChip or Java_com_diozero_internal_provider_builtin_gpio_NativeGpioDevice_openChip__Ljava_lang_String_2]所以我终于设法构建了本机映像,但由于某种原因它没有为本机库解析 JNI。
关于如何将 diozero 库正确注入本机可执行文件的任何想法?
- 为
- 安装 QEMU
标签: java linux arm64 buildx quarkus-native