Fabric8 Spring Native Integrations

Overview

Fabric8 Spring Native Integrations

Earlier, I wrote an example on how to get Spring Native and the official Kubernetes Native Java client working with the newly released Spring Native 0.11. I mentioned that I had to write a trivial configuration class to register the types that were used reflectively in the Spring Boot application, something that GraalVM frowns upon.

Then, Marc Nuri - who works on the Fabric8 project (the excellent Red Hat-driven client for Kubernetes) - took my trivial example, some of which I in turn took from the good Dr. Dave Syer, and turned into an example written in terms of Faric8. The Fabric8 project looks really good, and I wanted an excuse to get around to making that work at some point sooner rather than later, too! Now I had a great reason. Good news: it was even easier to get this working with Spring Boot and Spring Native. There's no Spring Boot autoconfiguration, per se, and there's no existing integration with Spring Nativem so I had to write that myself, but it was about as easy as the integration I wrote for the official Kubernetes Java client. This has to do mainly I think with the fact that there were fewer things in the project that I had to explicitly register. I could see patterns and then register everything that fit that pattern, and it just worked. (Three cheers for consistency!) It's so much more tedious to write GraalVM and Spring Native hints (configurations, basically) when you're sort of discovering the places that need those hints by surprise, one at a time. The cycle time is of course very slow for GraalVM compiles, so I was very happy to get something working so quickly: great job, Fabric8!

Here's the code for that integration:

clazz = NamedCluster.class; private final Reflections reflections = new Reflections(clazz.getPackageName(), clazz); @Override public void computeHints(NativeConfigurationRegistry registry, AotOptions aotOptions) { var subtypesOfKubernetesResource = reflections.getSubTypesOf(KubernetesResource.class); var othersToAddForReflection = List.of( io.fabric8.kubernetes.internal.KubernetesDeserializer.class ); var combined = new HashSet >(); combined.addAll(subtypesOfKubernetesResource); combined.addAll(othersToAddForReflection); combined.addAll(resolveSerializationClasses(JsonSerialize.class)); combined.addAll(resolveSerializationClasses(JsonDeserialize.class)); combined .stream() .filter(Objects::nonNull) .forEach(c -> { if (log.isDebugEnabled()) { log.debug("trying to register " + c.getName() + " for reflection"); } registry.reflection().forType(c).withAccess(TypeAccess.values()).build(); }); } @SneakyThrows private Set > resolveSerializationClasses(Class annotationClazz) { var method = annotationClazz.getMethod("using"); var classes = this.reflections.getTypesAnnotatedWith(annotationClazz); return classes.stream().map(clazzWithAnnotation -> { if (log.isDebugEnabled()) { log.debug("found " + clazzWithAnnotation.getName() + " : " + annotationClazz.getName()); } var annotation = clazzWithAnnotation.getAnnotation(annotationClazz); try { if (annotation != null) { return (Class) method.invoke(annotation); } } catch (Exception e) { ReflectionUtils.rethrowRuntimeException(e); } return null; }) .collect(Collectors.toSet()); } }">
package io.kubernetesnativejava.fabric8.nativex;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.fabric8.kubernetes.api.model.KubernetesResource;
import io.fabric8.kubernetes.api.model.NamedCluster;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.aot.context.bootstrap.generator.infrastructure.nativex.NativeConfigurationRegistry;
import org.springframework.nativex.AotOptions;
import org.springframework.nativex.hint.NativeHint;
import org.springframework.nativex.hint.TypeAccess;
import org.springframework.nativex.type.NativeConfiguration;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
	* Spring Native support for excellent Fabric8 Kubernetes client from Red Hat (thanks, Red Hat!)
	*
	* @author Josh Long
	*/
@Slf4j
@NativeHint(options = {"-H:+AddAllCharsets", "--enable-https", "--enable-url-protocols=https"})
public class Fabric8NativeConfiguration implements NativeConfiguration {

	private final Class clazz = NamedCluster.class;
	private final Reflections reflections = new Reflections(clazz.getPackageName(), clazz);

	@Override
	public void computeHints(NativeConfigurationRegistry registry, AotOptions aotOptions) {
		var subtypesOfKubernetesResource = reflections.getSubTypesOf(KubernetesResource.class);
		var othersToAddForReflection = List.of(
			io.fabric8.kubernetes.internal.KubernetesDeserializer.class
		);
		var combined = new HashSet<Class>();
		combined.addAll(subtypesOfKubernetesResource);
		combined.addAll(othersToAddForReflection);
		combined.addAll(resolveSerializationClasses(JsonSerialize.class));
		combined.addAll(resolveSerializationClasses(JsonDeserialize.class));
		combined
			.stream()
			.filter(Objects::nonNull)
			.forEach(c -> {
				if (log.isDebugEnabled()) {
					log.debug("trying to register " + c.getName() + " for reflection");
				}
				registry.reflection().forType(c).withAccess(TypeAccess.values()).build();
			});
	}

	@SneakyThrows
	private <R extends Annotation> Set<Class> resolveSerializationClasses(Class<R> annotationClazz) {
		var method = annotationClazz.getMethod("using");
		var classes = this.reflections.getTypesAnnotatedWith(annotationClazz);
		return classes.stream().map(clazzWithAnnotation -> {

			if (log.isDebugEnabled()) {
				log.debug("found " + clazzWithAnnotation.getName() + " : " + annotationClazz.getName());
			}

			var annotation = clazzWithAnnotation.getAnnotation(annotationClazz);
			try {
				if (annotation != null) {
					return (Class) method.invoke(annotation);
				}
			}
			catch (Exception e) {
				ReflectionUtils.rethrowRuntimeException(e);
			}
			return null;
		})
			.collect(Collectors.toSet());
	}

}

You'll need to add that your META-INF/spring.factories file, like this:

org.springframework.nativex.type.NativeConfiguration=io.kubernetesnativejava.fabric8.nativex.Fabric8NativeConfiguration

Now you just need an example that uses the Fabric8 API. I was able to sort of figure it out by looking at the blog post. The result looks fairly similar to my original example, so that helped, too. Here's the example:

* the Red Hat Fabric8 Kubernetes client. *

* The example is inspired by this blog, * which in turn was inspired by a blog I did. * * @author Josh Long */ @Slf4j @SpringBootApplication public class Fabric8NativeApplication { public static void main(String[] args) { SpringApplication.run(Fabric8NativeApplication.class, args); } @Bean KubernetesClient kubernetesClient() { return new DefaultKubernetesClient(); } @Bean ApplicationListener start(KubernetesClient client, SharedInformerFactory sharedInformerFactory, ResourceEventHandler nodeEventHandler) { return args -> { client.nodes().list(new ListOptionsBuilder().withLimit(1L).build()); sharedInformerFactory.startAllRegisteredInformers(); var nodeHandler = sharedInformerFactory.getExistingSharedIndexInformer(Node.class); nodeHandler.addEventHandler(nodeEventHandler); }; } @Bean ApplicationListener stop(SharedInformerFactory sharedInformerFactory) { return event -> sharedInformerFactory.stopAllRegisteredInformers(true); } @Bean SharedInformerFactory sharedInformerFactory(KubernetesClient client) { return client.informers(); } @Bean SharedIndexInformer nodeInformer(SharedInformerFactory factory) { return factory.sharedIndexInformerFor(Node.class, NodeList.class, 0); } @Bean SharedIndexInformer podInformer(SharedInformerFactory factory) { return factory.sharedIndexInformerFor(Pod.class, PodList.class, 0); } @Bean ResourceEventHandler nodeReconciler(SharedIndexInformer podInformer) { return new ResourceEventHandler<>() { @Override public void onAdd(Node node) { log.info("node: " + Objects.requireNonNull(node.getMetadata()).getName()); podInformer.getIndexer().list().stream() .map(pod -> Objects.requireNonNull(pod.getMetadata()).getName()) .forEach(podName -> log.info("pod name:" + podName)); } @Override public void onUpdate(Node oldObj, Node newObj) { } @Override public void onDelete(Node node, boolean deletedFinalStateUnknown) { } }; } }">

package com.example.fabric8native;

import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
import io.fabric8.kubernetes.client.informers.SharedInformerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextStoppedEvent;

import java.util.Objects;

/**
	* This example uses <a href="https://developers.redhat.com/blog/2020/05/20/getting-started-with-the-fabric8-kubernetes-java-client#using_fabric8_with_kubernetes">
	* the Red Hat Fabric8 Kubernetes client.
	* <p>
	* The example is inspired by <a href="https://blog.marcnuri.com/fabric8-kubernetes-java-client-and-quarkus-and-graalvm">this blog,
	* which in turn was inspired by a blog I did.
	*
	* @author Josh Long
	*/

@Slf4j
@SpringBootApplication
public class Fabric8NativeApplication {

	public static void main(String[] args) {
		SpringApplication.run(Fabric8NativeApplication.class, args);
	}

	@Bean
	KubernetesClient kubernetesClient() {
		return new DefaultKubernetesClient();
	}

	@Bean
	ApplicationListener<ApplicationReadyEvent> start(KubernetesClient client,
																																																		SharedInformerFactory sharedInformerFactory,
																																																		ResourceEventHandler<Node> nodeEventHandler) {
		return args -> {
			client.nodes().list(new ListOptionsBuilder().withLimit(1L).build());
			sharedInformerFactory.startAllRegisteredInformers();
			var nodeHandler = sharedInformerFactory.getExistingSharedIndexInformer(Node.class);
			nodeHandler.addEventHandler(nodeEventHandler);
		};
	}

	@Bean
	ApplicationListener<ContextStoppedEvent> stop(SharedInformerFactory sharedInformerFactory) {
		return event -> sharedInformerFactory.stopAllRegisteredInformers(true);
	}

	@Bean
	SharedInformerFactory sharedInformerFactory(KubernetesClient client) {
		return client.informers();
	}

	@Bean
	SharedIndexInformer<Node> nodeInformer(SharedInformerFactory factory) {
		return factory.sharedIndexInformerFor(Node.class, NodeList.class, 0);
	}

	@Bean
	SharedIndexInformer<Pod> podInformer(SharedInformerFactory factory) {
		return factory.sharedIndexInformerFor(Pod.class, PodList.class, 0);
	}

	@Bean
	ResourceEventHandler<Node> nodeReconciler(SharedIndexInformer<Pod> podInformer) {
		return new ResourceEventHandler<>() {

			@Override
			public void onAdd(Node node) {
				log.info("node: " + Objects.requireNonNull(node.getMetadata()).getName());
				podInformer.getIndexer().list().stream()
					.map(pod -> Objects.requireNonNull(pod.getMetadata()).getName())
					.forEach(podName -> log.info("pod name:" + podName));
			}

			@Override
			public void onUpdate(Node oldObj, Node newObj) {
			}

			@Override
			public void onDelete(Node node, boolean deletedFinalStateUnknown) {
			}
		};
	}
}

Compile that using mvn -DskipTests=true -Pnative clean package and then run it: ./target/fabric8-native. I get the following output.


❯ ./target/fabric8-native
2021-12-22 17:31:13.069  INFO 34762 --- [           main] o.s.nativex.NativeListener               : AOT mode enabled

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::                (v2.6.2)

2021-12-22 17:31:13.071  INFO 34762 --- [           main] c.e.f.Fabric8NativeApplication           : Starting Fabric8NativeApplication v0.0.1-SNAPSHOT using Java 17.0.1 on mbp2019.local with PID 34762 (/Users/jlong/Downloads/fabric8-native/target/fabric8-native started by jlong in /Users/jlong/Downloads/fabric8-native)
2021-12-22 17:31:13.071  INFO 34762 --- [           main] c.e.f.Fabric8NativeApplication           : No active profile set, falling back to default profiles: default
2021-12-22 17:31:13.083  INFO 34762 --- [           main] c.e.f.Fabric8NativeApplication           : Started Fabric8NativeApplication in 0.029 seconds (JVM running for 0.03)
2021-12-22 17:31:13.665  INFO 34762 --- [-controller-Pod] i.f.k.client.informers.cache.Controller  : informer#Controller: ready to run resync and reflector runnable
2021-12-22 17:31:13.665  INFO 34762 --- [controller-Node] i.f.k.client.informers.cache.Controller  : informer#Controller: ready to run resync and reflector runnable
2021-12-22 17:31:13.665  INFO 34762 --- [-controller-Pod] i.f.k.client.informers.cache.Controller  : informer#Controller: resync skipped due to 0 full resync period
2021-12-22 17:31:13.665  INFO 34762 --- [controller-Node] i.f.k.client.informers.cache.Controller  : informer#Controller: resync skipped due to 0 full resync period
2021-12-22 17:31:13.665  INFO 34762 --- [-controller-Pod] i.f.k.client.informers.cache.Reflector   : Started ReflectorRunnable watch for class io.fabric8.kubernetes.api.model.Pod
2021-12-22 17:31:13.666  INFO 34762 --- [controller-Node] i.f.k.client.informers.cache.Reflector   : Started ReflectorRunnable watch for class io.fabric8.kubernetes.api.model.Node
2021-12-22 17:31:14.254  INFO 34762 --- [pool-2-thread-1] c.e.f.Fabric8NativeApplication           : node: gke-knj-demos-default-pool-8fdf2ef6-55q9
2021-12-22 17:31:14.254  INFO 34762 --- [pool-2-thread-1] c.e.f.Fabric8NativeApplication           : node: gke-knj-demos-default-pool-8fdf2ef6-p3kz
2021-12-22 17:31:14.254  INFO 34762 --- [pool-2-thread-1] c.e.f.Fabric8NativeApplication           : node: gke-knj-demos-default-pool-8fdf2ef6-xh08

That's 29 thousandths of a second. And, best part: the resulting application takes up a meager 68M of RAM.

Not bad!

You might also like...

Spring Kurulumundan Başlayarak, Spring IOC ve Dependency Injection, Hibernate, Maven ve Spring Boot Konularına Giriş Yapıyoruz.

Spring Kurulumundan Başlayarak, Spring IOC ve Dependency Injection, Hibernate, Maven ve Spring Boot Konularına Giriş Yapıyoruz.

Spring Tutorial for Beginners File Directory Apache Tomcat Apache Tomcat - Eclipse Bağlantısı Spring Paketlerinin İndirilmesi ve Projeye Entegrasyonu

Apr 11, 2022

Spring Boot JdbcTemplate example with SQL Server: CRUD Rest API using Spring Data JDBC, Spring Web MVC

Spring Boot JdbcTemplate example with SQL Server: Build CRUD Rest API Build a Spring Boot CRUD Rest API example that uses Spring Data Jdbc to make CRU

Dec 20, 2022

Spring Boot & MongoDB Login and Registration example with JWT, Spring Security, Spring Data MongoDB

Spring Boot & MongoDB Login and Registration example with JWT, Spring Security, Spring Data MongoDB

Spring Boot Login and Registration example with MongoDB Build a Spring Boot Auth with HttpOnly Cookie, JWT, Spring Security and Spring Data MongoDB. Y

Dec 30, 2022

Spring Boot Login and Registration example with MySQL, JWT, Rest Api - Spring Boot Spring Security Login example

Spring Boot Login and Registration example with MySQL, JWT, Rest Api - Spring Boot Spring Security Login example

Spring Boot Login example with Spring Security, MySQL and JWT Appropriate Flow for User Login and Registration with JWT Spring Boot Rest Api Architect

Jan 5, 2023

Demo microservice architecture with Spring ,Spring Cloud Gateway , Spring Cloud config server , Eureuka , keycloak and Docker.

Demo microservice architecture with Spring ,Spring Cloud Gateway , Spring Cloud config server , Eureuka , keycloak and Docker.

spring-microservice Demo microservice architecture with Spring ,Spring Cloud Gateway , Spring Cloud config server , Eureuka , keycloak and Docker. Arc

Sep 13, 2022

Spring Boot JWT Authentication example with Spring Security & Spring Data JPA

Spring Boot JWT Authentication example with Spring Security & Spring Data JPA

Jan 26, 2022

Spring REST service built with Spring initializr and Spring Data.

Spring REST Service Generated with start.spring.io, using Spring Data. Documented using Spring REST Docs. Spring Initializr - Generate new Spring Rest

Jan 28, 2022

A React Native project starter with Typescript, a theme provider with hook to easy styling component, a folder architecture ready and some configs to keep a codebase clean.

React Native Boilerplate Folder structure : src ├── assets │   ├── audios │   ├── fonts │   ├── icons │   └── images ├── components │   ├── Layout.tsx

Sep 1, 2022

Lightweight React Native UI Components inspired on Vant

vant-react-native Install yarn add vant-react-native Or npm install vant-react-native Usage import React, { Component } from 'react'; import { View, T

Sep 29, 2022
Comments
  • Issue with ClassNotFoundException after upgrading to fabric8 v.6.0.0

    Issue with ClassNotFoundException after upgrading to fabric8 v.6.0.0

    Experienced an error after upgrading to fabric8 v6.0.0:

    java.lang.ClassNotFoundException: io.fabric8.kubernetes.client.DefaultKubernetesClient
    	at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:54) ~[na:na]
    	at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:141) ~[na:na]
    	at io.fabric8.kubernetes.client.KubernetesClientBuilder.<init>(KubernetesClientBuilder.java:56) ~[na:na]
    ...
    

    I solved it by adding the DefaultKubernetesClient-class to the computeHints in Fabric8NativeConfiguration:

    ...
            var othersToAddForReflection = List.of(
                    io.fabric8.kubernetes.internal.KubernetesDeserializer.class,
                    io.fabric8.kubernetes.client.DefaultKubernetesClient.class
            )
    ...
    

    I guess this is due to the new KuberneteClientBuilder-pattern introduced in v6: https://github.com/fabric8io/kubernetes-client/blob/6.0/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java

    I know this repo uses the v5 of the fabric8-client, but it might be useful for others trying to get it to work!

    opened by magnusgundersen 0
Owner
Kubernetes Native Java
Kubernetes Native Java
Sceneform React Native AR Component using ARCore and Google Filament as 3D engine. This the Sceneform Maintained Component for React Native

Discord Server Join us on Discord if you need a hand or just want to talk about Sceneform and AR. Features Remote and local assets Augmented Faces Clo

SceneView Open Community 42 Dec 17, 2022
React native wrapper for Jitsi Meet SDK Library that rely on the native view (Activity / ViewController)

react-native-jitsi-meet-sdk React native wrapper for Jitsi Meet SDK Library. This Library implements the Jitsi SDK with a native activity on the Andro

null 7 May 2, 2022
With react-native-update-in-app library you can easily implement in-app updates in your React Native app using CDN or any other file server

React Native In-App update With react-native-update-in-app library you can easily implement in-app updates in your React Native app using CDN or any o

Nepein Andrey 7 Dec 21, 2022
An awesome native wheel picker component for React Native.

⛏️ react-native-picky An awesome native wheel picker component for react-native. Features Supports multiple columns ✅ Supports looping ✅ Native Androi

null 28 Dec 4, 2022
Spring JPA Native Query example in Spring Boot

Spring JPA Native Query example in Spring Boot

null 12 Nov 30, 2022
该仓库中主要是 Spring Boot 的入门学习教程以及一些常用的 Spring Boot 实战项目教程,包括 Spring Boot 使用的各种示例代码,同时也包括一些实战项目的项目源码和效果展示,实战项目包括基本的 web 开发以及目前大家普遍使用的线上博客项目/企业大型商城系统/前后端分离实践项目等,摆脱各种 hello world 入门案例的束缚,真正的掌握 Spring Boot 开发。

Spring Boot Projects 该仓库中主要是 Spring Boot 的入门学习教程以及一些常用的 Spring Boot 实战项目教程,包括 Spring Boot 使用的各种示例代码,同时也包括一些实战项目的项目源码和效果展示,实战项目包括基本的 web 开发以及目前大家普遍使用的前

十三 4.5k Dec 30, 2022
JHipster Works with Spring Native!

Spring Native with JHipster This repository contains five apps we (@joshlong and @mraible) used to figure out how to make Spring Native work with JHip

Matt Raible 23 Dec 22, 2022
Spring-boot application to demo JVM HEAP and Native memory leak

Description This repo can be used as demo repo for finding memory leaks. Example spring-boot project to show how to find and fix JVM HEAP memory leak

Iranna Nk 4 Jul 22, 2022
一个涵盖六个专栏:Spring Boot 2.X、Spring Cloud、Spring Cloud Alibaba、Dubbo、分布式消息队列、分布式事务的仓库。希望胖友小手一抖,右上角来个 Star,感恩 1024

友情提示:因为提供了 50000+ 行示例代码,所以艿艿默认注释了所有 Maven Module。 胖友可以根据自己的需要,修改 pom.xml 即可。 一个涵盖六个主流技术栈的正经仓库: 《Spring Boot 专栏》 《Spring Cloud Alibaba 专栏》 《Spring Clou

芋道源码 15.7k Dec 31, 2022
参考 DDD/Clean Architecture 设计理念,整合 Spring Boot/Spring Security/Mybatis Plus/Vavr 的 Spring Realworld 应用案例

Demo · 更多项目 · 参考资料 ms-spring-ddd-examples Unified Domain-driven Layered Architecture for MicroService Apps,试图探索一套切实可行的应用架构规范,可以复制、可以理解、可以落地、可以控制复杂性的指导

王下邀月熊 19 Sep 23, 2022