Statically linked Java images are a superb idea; what's not to love about super small, super fast images that have a low attack surface. However, the reality is far from it. This post is describing some of the
fun frustration I've had trying to get something rather basic working: namely Cloud Logging.
The first problem is statically linked Graal Native Images cannot do some things without
libc set, in this case they can't perform a DNS lookup which results in a
java.net.UnknownHostException. I'm not going to dwell on how stupid this is and just move on. There are rough instructions available on the Graal site to use
musl for this but a quick Google came up with something from, erm, Google Cloud. I bodged this into Helidon and... it didn't build:
#11 274.5 [WARNING] Error: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image runtime.A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. To see how this object got instantiated use --trace-object-instantiation=java.nio.DirectByteBuffer. The object was probably created by a class initializer and is reachable from a static field. You can request class initialization at image runtime by using the option --initialize-at-run-time=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. #11 274.5 [WARNING] Trace: Object was reached by #11 274.5 [WARNING] reading field io.grpc.netty.shaded.io.netty.buffer.PoolChunk.memory of #11 274.5 [WARNING] constant io.grpc.netty.shaded.io.netty.buffer.PoolChunk@42d125b0 reached by ...
The build problem was due to the embedded Netty within the gRPC libraries. Fortunately the fix for this was pretty easy: Google have their own library for it:
<dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-graalvm-support</artifactId> <version>0.3.0</version> </dependency>
With this added it built and ran, but now it couldn't find any GCP config:
java.lang.IllegalArgumentException: A project ID is required for this service but could not be determined from the builder or the environment. Please set a project ID using the builder. at com.google.common.base.Preconditions.checkArgument(Preconditions.java:142) at com.google.cloud.ServiceOptions.<init>(ServiceOptions.java:304)
Normally, I would have just mounted the volume into the users home, but it appears Graal doesn't have value for
user.home. I've not digged into this to confirm it, so thought I'd just work around it:
docker run --rm -it -p 8080:8080 ` -e CLOUDSDK_CONFIG=/gcloud ` -v $Env:APPDATA/gcloud/:/gcloud:ro ` helidon-quickstart-mp:native
Booom! It ran up without any errors.
Alas, there was one final hiccup which was answered by microsoft/WSL#5324. In a nutshell, if you let Windows go to sleep then the clock in WSL2 drifts. That meant I couldn't find any of my log messages as they were timestamped incorrectly. With WSL restarted we got some logs in Cloud Logging. Hurrah!
The resulting image is a somewhat portly 116MB which is 24MB larger than last time and only 11MB off Alpine JLink. I don't think could be explained by the extra libraries but something to worry about another day. However, it starts in around 160ms with
-m 32m set on the docker container which can't be sniffed at. Regardless of how compelling Graal is, there are lots of bear traps to be aware of. I hope this improves soon.
Now for metrics and tracing...