fabric8io / kubernetes-client

Java client for Kubernetes & OpenShift
http://fabric8.io
Apache License 2.0
3.41k stars 1.46k forks source link

Please consider seeding GraalVM native image reflect-config.json #2004

Open FWiesner opened 4 years ago

FWiesner commented 4 years ago

To build Kubernetes operators with significantly reduced image size, it would be great if you could natively support GraalVM native images. At the moment it is required to profile the using application in JVM mode with -agentlib:native-image-agent=config-output-dir=$PWD/src/main/resources/META-INF/native-image/<groupId>/<artifactId>.

Please see details here: https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md

Thanks

FWiesner commented 4 years ago

when you try a build with the KubernetesClient for native images, you need to make sure the binary includes support for TLS as well as all character sets. Easiest to do is to add a native-image.properties in src/main/resources/META-INF/native-image/<groupId>/<artifactId>/.

Here's an example that I use (includes the config for logback):

Args=\
--initialize-at-build-time=org.slf4j \
--initialize-at-build-time=ch.qos.logback \
-H:+AddAllCharsets \
--enable-https
manusa commented 4 years ago

Hi @FWiesner

I recently implemented a demo project using Kubernetes-Client and building a native image: https://github.com/marcnuri-demo/k8s-cli-java

Everything seems to work fine, could you provide some kind of example with reflection problems so we can implement a "fix".

FWiesner commented 4 years ago

@manusa please have a look at your project. It logs

Warning: Image 'kubectl-java' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation).
native image available at build/graal/kubectl-java (3 MB)

So, your container image will need a JDK. My proposal was to support --no-fallback to not bundle a JVM in the image

FWiesner commented 4 years ago

when you build your binaries with --no-fallback, then the JVM won't be there to help you with reflection. Makes your binary bigger (likely adds 50-100MB), but you can link it statically and don't need anything else in the container image

manusa commented 4 years ago

Ok, thnx for the feedback. I've tested GraalVM only on a couple of occasions and was completely unaware of that. We will look into it.

davidkarlsen commented 4 years ago

But internally kubernets-client uses quite a number of model classes based on Jackson:

/com.tietoevry.fss.garo.mainkt 
setrlimit to increase file descriptor limit failed, errno 22
[main] ERROR io.fabric8.kubernetes.client.Config - Failed to parse the kubeconfig.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `io.fabric8.kubernetes.api.model.Config` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (StringReader); line: 1, column: 1]
        at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1592)
        at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1058)
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3214)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3182)
        at io.fabric8.kubernetes.client.internal.KubeConfigUtils.parseConfigFromString(KubeConfigUtils.java:45)
        at io.fabric8.kubernetes.client.Config.loadFromKubeconfig(Config.java:505)
        at io.fabric8.kubernetes.client.Config.tryKubeConfig(Config.java:491)
        at io.fabric8.kubernetes.client.Config.autoConfigure(Config.java:230)
        at io.fabric8.kubernetes.client.Config.<init>(Config.java:214)
        at io.fabric8.kubernetes.client.ConfigBuilder.<init>(ConfigBuilder.java:16)
        at io.fabric8.kubernetes.client.ConfigBuilder.<init>(ConfigBuilder.java:13)
        at io.fabric8.kubernetes.client.BaseClient.<init>(BaseClient.java:43)
        at io.fabric8.kubernetes.client.DefaultKubernetesClient.<init>(DefaultKubernetesClient.java:97)
        at com.tietoevry.fss.garo.MainKt.main(Main.kt:12)
Exception in thread "main" java.lang.ExceptionInInitializerError
        at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:290)
        at java.lang.Class.ensureInitialized(DynamicHub.java:496)
        at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:235)
        at java.lang.Class.ensureInitialized(DynamicHub.java:496)
        at com.tietoevry.fss.garo.MainKt.main(Main.kt:13)
Caused by: java.util.MissingResourceException: Resource bundle not found org.apache.cxf.jaxrs.Messages. Register the resource bundle using the option -H:IncludeResourceBundles=org.apache.cxf.jaxrs.Messages.
        at com.oracle.svm.core.jdk.LocalizationSupport.getCached(LocalizationSupport.java:66)
        at java.util.ResourceBundle.getBundle(ResourceBundle.java:73)
        at org.apache.cxf.common.i18n.BundleUtils.getBundle(BundleUtils.java:94)
        at org.apache.cxf.jaxrs.AbstractJAXRSFactoryBean.<clinit>(AbstractJAXRSFactoryBean.java:69)
        at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:350)
        at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:270)
        ... 4 more

won't this become quite painful to handle in a whitelist?

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had any activity since 90 days. It will be closed if no further activity occurs within 7 days. Thank you for your contributions!

FWiesner commented 3 years ago

I'd like to propose a new extension, similar to the Helidon integration with GraalVM: https://github.com/oracle/helidon/tree/master/integrations/graal. There you find https://github.com/oracle/helidon/blob/master/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java. This class scans (among others) for classes annotated with @io.helidon.common.Reflected and adds them to the native-image reflection configuration programmatically. I think this would help a lot with listing all the Jackson model classes

rohanKanojia commented 3 years ago

@iocanel @oscerd @manusa @metacosm : Could you please share your thoughts on this?

manusa commented 3 years ago

Building native images requires additional processing, among other things, to enable reflection and to replace some of the classes which make use of incompatible or missing dependencies.

The easiest way to do this is by using additional pre-processing before generating the native-image, such as the one performed by the Kubernetes-Client extension in Quarkus.

Achieving the same result just by passing configurations to the GraalVM native-image build task, is really hard. It would also generate JSON files or configurations listing classes (thus the title of this issue).

Personally, to have a better opinion on the topic I need to check how Helidon works around this and if it provides a similar mechanism as Quarkus does.

Same would be applicable for Micronaut, or any other similar framekwork. However, I'm not sure the home for this extension should be this repository.

oscerd commented 3 years ago

I don't have a strong opinion on this, but I do believe this extension should be placed somewhere else

FWiesner commented 3 years ago

not sure why you would want all of this in the microservice frameworks, especially as the operator pattern doesn't require a microservice framework at all. When your service is only depending on the Kubernetes client to interact with the API server, why would you want to use Quarkus, Micronaut, Helidon, etc.

On the other hand native image generation is modularized and all native image configurations are additive. Check the contents of Netty jars for example.

FWiesner commented 3 years ago
Bildschirmfoto 2020-11-17 um 14 30 26
manusa commented 3 years ago

I guess I misunderstood your point (I thought Helidon provided something similar to Quarkus extensions) . Checking now what Helidon does. I didn't know that GraalVM extensions/features were a thing in a standalone fashion. It is poorly documented (at least I haven't found anything about that topic)

This HelidonReflectionFeature does something really similar to what the Quarkus extension does (Jandex is used there, but very similar nonetheless).

The problem with the Netty approach is that the client has many classes, and probably you won't be needing most of them at runtime. On the other hand, after the model split effort, models are more granular now, so maybe these reflection-config.json files wouldn't be that big.

chiefrocker86 commented 1 month ago

I know this quite old, but are there any news on this front?