Part 2: How to Create a Spring Boot Kubernetes Controller

Project Setup

  • Spring Starter Web
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java-spring-integration</artifactId>
<version>16.0.0</version>
</dependency>

Generating Java Model Classes For CRD

# Local generation
LOCAL_MANIFEST_FILE=/home/amrut/projects/kubernetes-custom-resource/crd/my-crd.yaml
mkdir -p /tmp/java && cd /tmp/java
docker run \
--rm \
-v "$LOCAL_MANIFEST_FILE":"$LOCAL_MANIFEST_FILE" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd)":"$(pwd)" \
-ti \
--network host \
ghcr.io/kubernetes-client/java/crd-model-gen:v1.0.6 \
/generate.sh \
-u $LOCAL_MANIFEST_FILE \
-n prabhu.amrut.com \
-p com.amrut.prabhu \
-o "$(pwd)"

Understanding the Use Case

Creating a Kubernetes Controller

  • A reconciler: This component is used to handle requests when there is a change in the CRD instance. It will be invoked when we create, update or delete a CRD instance so that we can do something with the changes.
  • A Shared Index Informer: This is more like a cache, so the controller does not need to continuously poll the Kubernetes cluster (API server) to check if there are any new CRD instances created, updated, or deleted.
  • APIClient: It is a client used to connect to the Kubernetes Cluster (API server)
  • CRD Model: These are the model classes we generated earlier.
@Bean
SharedIndexInformer<V1MyCrd> shareIndexInformer( SharedInformerFactory sharedInformerFactory, ApiClient apiClient) {
GenericKubernetesApi<V1MyCrd, V1MyCrdList> api = new
GenericKubernetesApi<>(V1MyCrd.class,
V1MyCrdList.class,
"com.amrut.prabhu",
"v1",
"my-crds",
apiClient);
return sharedInformerFactory.sharedIndexInformerFor(api, V1MyCrd.class, 0);}

Understanding the Reconciler Component

  • Create Instance Request:- When we receive this request, we get a reference to the resource instance from the index informer. We can then do something like creating a config map. Now, when we create a new resource as a result of CRD instance creation, we need to set the current CRD instance as the owner of the new resource. Why? we will find out soon.
  • Update Instance Request: In this case, we get an updated reference of the CRD instance and we perform an update on our components i.e. update the previously created config map.
  • Delete Instance Request: In this case, we don't have to do anything. As soon as we delete the CRD instance, Kubernetes automatically deletes all the resources it owns. This is why we set the ownership while creating a new resource.

Implementing the Reconciler Component

request -> {
String key = request.getNamespace() + "/" + request.getName();

V1MyCrd resourceInstance = shareIndexInformer
.getIndexer()
.getByKey(key);

if (resourceInstance != null) {

V1ConfigMap v1ConfigMap = createConfigMap(resourceInstance);

try {
coreV1Api.createNamespacedConfigMap(request.getNamespace(),
v1ConfigMap,
"true",
null,
"",
"");
} catch (ApiException e) {
private V1ConfigMap createConfigMap(V1MyCrd resourceInstance) {
return new V1ConfigMap()
.metadata(new V1ObjectMeta()
.name("my-config-map")
.addOwnerReferencesItem(new V1OwnerReference()
.apiVersion(resourceInstance.getApiVersion())
.kind(resourceInstance.getKind())
.name(resourceInstance.getMetadata().getName())
.uid(resourceInstance.getMetadata().getUid())))
.data(Map.of("amrut", "prabhu"));
}
} catch (ApiException e) {
System.out.println(e);
if (e.getCode() == 409) {
try {
coreV1Api.replaceNamespacedConfigMap("my-config-map",
request.getNamespace(),
v1ConfigMap,
"true",
null,
"",
"");
} catch (ApiException ex) {
throw new RuntimeException(ex);
}
} else {
throw new RuntimeException(e);
}
}

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Amrut Prabhu

Amrut Prabhu

Software Craftsman, Tech Enthusiast. I run https://refactorfirst.com to post all my articles