Featured image for Kubernetes topics.

The fabric8 Kubernetes client has been simplifying Java developers' use of Kubernetes for several years. Although the parent fabric8 project has ended, the Kubernetes client continues to be popular, and the recent 5.5.0 release includes many new features and bug fixes.

This article takes a look at new features in fabric8 Kubernetes client, focusing on:

Knowing about these changes will help you avoid problems when you upgrade to the latest version of fabric8's Java client for Kubernetes or Red Hat OpenShift.

Note: The fabric8 development team mostly consists of Java developers, so the client's design is heavily influenced by a Java developer's perspective. I will demonstrate just a few of fabric8's features for using Kubernetes APIs in a Java environment.

How to get the new fabric8 Java client

You can find the most current fabric8 Java client release on Maven Central. To start using the new client, add it as a dependency in your Maven pom.xml file. For Kubernetes, the dependency is:

<dependency>
  <groupId>io.fabric8</groupId>
  <artifactId>kubernetes-client</artifactId>
  <version>5.5.0</version>
</dependency>

For OpenShift, it's:

<dependency>
  <groupId>io.fabric8</groupId>
  <artifactId>openshift-client</artifactId>
  <version>5.5.0</version>
</dependency>

New features in fabric8 Kubernetes client 5.5.0

In many areas, we've been watching developments in Kubernetes and OpenShift and listening to the needs of developers. I will cover the most important new features in the following sections.

Dynamic clients: Unstructured and GenericKubernetesResource

The Kubernetes Go language client has the concept of a dynamic client and a generic Kubernetes type called Unstructured. The fabric8 Kubernetes client already provided support for Unstructured in its CustomResource API using raw HashMaps. In 5.5.0, we added a new type, GenericKubernetesResource, which you can use for deserializing unknown types. Here is an example of using it to get a list of CronTab resources (mentioned in the Kubernetes CustomResourceDefinition guide) in a specified namespace:

try (KubernetesClient client = new DefaultKubernetesClient()) {
    CustomResourceDefinitionContext context = new CustomResourceDefinitionContext.Builder()
            .withVersion("v1")
            .withGroup("stable.example.com")
            .withScope("Namespaced")
            .withPlural("crontabs")
            .build();
 
    System.out.println("CronTab resources in default namespace: ");
    client.genericKubernetesResources(context)
            .inNamespace("default")
            .list().getItems().stream()
            .map(GenericKubernetesResource::getMetadata)
            .map(ObjectMeta::getName)
            .forEach(System.out::println);
} catch (KubernetesClientException e) {
    System.out.println("Exception received: " + e.getMessage());
}

In the future, we will provide additional methods to make using GenericKubernetesResource even easier. We also plan to add support for applying the list of Kubernetes resources (right now the class works only for primitive Kubernetes resource lists), which can also contain custom resources

Dynamic informers

With the introduction of GenericKubernetesResource, you can use the SharedInformer API for a CustomResource without providing any type. The earlier KubernetesClient raw API was missing support for SharedInformers, but now you can use informers from both SharedInformerFactory and the DSL inform() method.

Here is an example of using dynamic informers from SharedInformerFactory:

try (KubernetesClient client = new DefaultKubernetesClient()) {
    SharedInformerFactory informerFactory = client.informers();
    CustomResourceDefinitionContext context = new CustomResourceDefinitionContext.Builder()
            .withGroup("stable.example.com")
            .withVersion("v1")
            .withPlural("crontabs")
            .withScope("Namespaced")
            .build();
 
    SharedIndexInformer<GenericKubernetesResource> informer = informerFactory.sharedIndexInformerForCustomResource(context, 60 * 1000L);
    informer.addEventHandler(new ResourceEventHandler<>() {
        @Override
        public void onAdd(GenericKubernetesResource genericKubernetesResource) {
            System.out.printf("ADD %s\n", genericKubernetesResource.getMetadata().getName());
        }
 
        @Override
        public void onUpdate(GenericKubernetesResource genericKubernetesResource, GenericKubernetesResource t1) {
            System.out.printf("UPDATE %s\n", genericKubernetesResource.getMetadata().getName());
        }
 
        @Override
        public void onDelete(GenericKubernetesResource genericKubernetesResource, boolean b) {
            System.out.printf("DELETE %s\n", genericKubernetesResource.getMetadata().getName());
        }
    });
 
    informerFactory.startAllRegisteredInformers();
 
    TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    e.printStackTrace();
}

We’ve also added a new method to the DSL:

inform(ResourceEventHandle eventHandler)

This method can be used from within the DSL without having to create a SharedInformerFactory. A SharedIndexInformer will be started automatically, so there is no need to invoke the informer's run() method. Here is an example:

package io.fabric8.demos;
 
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
 
import java.util.concurrent.TimeUnit;
 
public class DSLInformMethod {
    public static void main(String[] args) {
        try (KubernetesClient client = new DefaultKubernetesClient()) {
            client.pods().inNamespace("default").inform(new ResourceEventHandler<>() {
                @Override
                public void onAdd(Pod pod) {
                    System.out.println(pod.getMetadata().getName() + " ADD ");
                }
 
                @Override
                public void onUpdate(Pod pod, Pod t1) {
                    System.out.println(pod.getMetadata().getName() + " UPDATE ");
                }
 
                @Override
                public void onDelete(Pod pod, boolean b) {
                    System.out.println(pod.getMetadata().getName() + " DELETE ");
                }
            });
 
            TimeUnit.SECONDS.sleep(10 * 1000);
        } catch (InterruptedException interruptedException) {
           Thread.currentThread().interrupt();
            interruptedException.printStackTrace();
        }
    }
}

Certification management

A new extension was contributed to this release: The JetStack cert-manager extension. With this extension, you can manage TLS web certificates through cert-manager-based CRDs from the Kubernetes API server in Java, using the cert-manager extension.

Add this dependency to use the extension in your projects:

<dependency>
     <groupId>io.fabric8</groupId>
     <artifactId>certmanager-client</artifactId>
     <version>5.5.0</version>
</dependency>

Here is a simple example of a CerificateRequest using the extension:

package io.fabric8.demo;

import io.fabric8.certmanager.api.model.meta.v1.ObjectReferenceBuilder;
import io.fabric8.certmanager.api.model.v1.CertificateRequest;
import io.fabric8.certmanager.api.model.v1.CertificateRequestBuilder;
import io.fabric8.certmanager.client.CertManagerClient;
import io.fabric8.certmanager.client.DefaultCertManagerClient;
import io.fabric8.kubernetes.api.model.Duration;

import java.text.ParseException;

public class CertificateRequestExample {
    public static void main(String[] args) {
        try (CertManagerClient certManagerClient = new DefaultCertManagerClient()) {
            CertificateRequest certificateRequest = new CertificateRequestBuilder()
                    .withNewMetadata().withName("my-ca-cr").endMetadata()
                    .withNewSpec()
                       .withRequest("base64encodedcert=")
                       .withIsCA(false)
                       .addToUsages("signing", "digital signature", "server auth")
                       .withDuration(Duration.parse("90d"))
                       .withIssuerRef(new ObjectReferenceBuilder()
                               .withName("ca-issuer")
                               .withKind("Issuer")
                               .withGroup("cert-manager.io")
                               .build())
                    .endSpec()
                    .build();

            certManagerClient.v1().certificateRequests().inNamespace("default").create(certificateRequest);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

HTTP operation retry configuration options with exponential backoff

According to Kubernetes API conventions, when the HTTP status code is 500 (Status Internal Server Error), 503 (Status Service Unavailable), or 504 (Status Server Timeout), the suggested client recovery behavior is "retry with exponential backoff." In this release, we introduced additional configuration properties for this retry mechanism, shown in Table 1.

Table 1: New properties to control HTTP retries in fabric8 Kubernetes client 5.5.0.

Property name

Description

kubernetes.request.retry.backoffLimit

Number of retry attempts on status codes >= 500. Defaults to 0.

kubernetes.request.retry.backoffInterval

Retry initial backoff interval, in milliseconds. Defaults to 1000.

OpenShift client DSL improvements

The previous release provided support for all Kubernetes resources, as described in this issue in the fabric8 Kubernetes client GitHub repository. We also had good coverage for OpenShift 3.x resources. But in OpenShift 4, newer resources were introduced by the installation of additional operators. Now we’re covering most of the resources offered by OpenShift 4. You can find more details in this GitHub issue. Table 2 shows the newly added DSL methods.

Table 2: New resource DSLs in fabric8 Kubernetes client 5.5.0

OpenShift resource

OpenShift client DSL

authorization.openshift.io/v1 ClusterRole

openShiftClient.clusterRoles()

authorization.openshift.io/v1 ResourceAccessReview

openShiftClient.resourceAccessReviews()

authorization.openshift.io/v1 LocalSubjectAccessReview

openShiftClient.localSubjectAccessReviews()

authorization.openshift.io/v1 LocalResourceAccessReview

openShiftClient.localResourceAccessReviews()

authorization.openshift.io/v1 SubjectRulesReview

openShiftClient.subjectRulesReviews()

authorization.openshift.io/v1 SelfSubjectRulesReview

openShiftClient.selfSubjectRulesReviews()

authorization.openshift.io/v1 RoleBindingRestrictions

openShiftClient.roleBindingRestrictions()

autoscaling.openshift.io/v1 ClusterAutoscaler

openShiftClient.clusterAutoscaling().v1().clusterAutoscalers()

autoscaling.openshift.io/v1beta1 MachineAutoscaler

openShiftClient.clusterAutoscaling().v1beta1().machineAutoscalers()

cloudcredential.openshift.io/v1 CredentialsRequest

openShiftClient.credentialsRequests()

config.openshift.io/v1 Authentication

openShiftClient.config().authentications()

config.openshift.io/v1 Console

openShiftClient.config().consoles()

config.openshift.io/v1 DNS

openShiftClient.config().dnses()

config.openshift.io/v1 Network

openShiftClient.config().networks()

console.openshift.io/v1alpha1 ConsolePlugin

openShiftClient.console().consolePlugins()

console.openshift.io/v1 ConsoleQuickStart

openShiftClient.console().consoleQuickStarts()

controlplane.operator.openshift.io/v1alpha1 PodNetworkConnectivityCheck

openShiftClient.operator().podNetworkConnectivityChecks()

helm.openshift.io/v1beta1 HelmChartRepository

openShiftClient.helmChartRepositories()

image.openshift.io/v1 ImageSignature

openShiftClient.imageSignatures()

image.openshift.io/v1 ImageStreamImage

openShiftClient.imageStreamImages()

image.openshift.io/v1 ImageStreamImport

openShiftClient.imageStreamImports()

image.openshift.io/v1 ImageStreamMapping

openShiftClient.imageStreamMappings()

machine.openshift.io/v1beta1 MachineHealthCheck

openShiftClient.machine().machineHealthChecks()

machine.openshift.io/v1beta1 MachineSet

openShiftClient.machine().machineSets()

machineconfiguration.openshift.io/v1 ContainerRuntimeConfig

openShiftClient.machineConfigurations().containerRuntimeConfigs()

machineconfiguration.openshift.io/v1 ControllerConfig

openShiftClient.machineConfigurations().controllerConfigs()

machineconfiguration.openshift.io/v1 KubeletConfig

openShiftClient.machineConfigurations().kubeletConfigs()

machineconfiguration.openshift.io/v1 MachineConfigPool

openShiftClient.machineConfigurations().machineConfigPools()

machineconfiguration.openshift.io/v1 MachineConfig

openShiftClient.machineConfigurations().machineConfigs()

metal3.io/v1alpha1 BareMetalHost

openShiftClient.bareMetalHosts()

monitoring.coreos.com/v1alpha1 AlertmanagerConfig

openShiftClient.monitoring().alertmanagerConfigs()

monitoring.coreos.com/v1 Probe

openShiftClient.monitoring().probes()

monitoring.coreos.com/v1 ThanosRuler

openShiftClient.monitoring().thanosRulers()

network.openshift.io/v1 HostSubnet

openShiftClient.hostSubnets()

network.operator.openshift.io/v1 OperatorPKI

openShiftClient.operatorPKIs()

oauth.openshift.io/v1 OAuthClientAuthorization

openShiftClient.oAuthClientAuthorizations()

oauth.openshift.io/v1 UserOAuthAccessToken

openShiftClient.userOAuthAccessTokens()

operator.openshift.io/v1 CloudCredential

openShiftClient.operator().cloudCredentials()

operator.openshift.io/v1 ClusterCSIDriver

openShiftClient.operator().clusterCSIDrivers()

operator.openshift.io/v1 Storage

openShiftClient.operator().storages()

operators.coreos.com/v1 OperatorCondition

openShiftClient.operatorHub().operatorConditions()

operators.coreos.com/v1 Operator

openShiftClient.operatorHub().operators()

packages.operators.coreos.com/v1 PackageManifest

openShiftClient.operatorHub().packageManifests()

security.openshift.io/v1 PodSecurityPolicyReview

openShiftClient.podSecurityPolicyReviews()

security.openshift.io/v1 PodSecurityPolicySelfSubjectReview

openShiftClient.podSecurityPolicySelfSubjectReviews()

security.openshift.io/v1 PodSecurityPolicySubjectReview

openShiftClient.podSecurityPolicySubjectReviews()

template.openshift.io/v1 BrokerTemplateInstance

openShiftClient.brokerTemplateInstances()

template.openshift.io/v1 TemplateInstance

openShiftClient.templateInstances()

tuned.openshift.io/v1 Profile

openShiftClient.tuned().profiles()

tuned.openshift.io/v1 Tuned

openShiftClient.tuned().tuneds()

user.openshift.io/v1 Identity

openShiftClient.identities()

user.openshift.io/v1 UserIdentityMapping

openShiftClient.userIdentityMappings()

Other improvements

Other notable improvements to the Kubernetes client in this release include:

  • fabric8 Knative extension: Knative model updated to v0.23.0.
  • fabric8 Tekton extension: Tekton pipeline model updated to v0.24.1.
  • fabric8 Tekton extension: Tekton triggers model updated to v0.13.0.
  • fabric8 Kubernetes mock server: Bug fixes related to ignoring the local kubeconfig, CRUD-mode fixes such as status subresource handling, apiVersion awareness, etc.
  • Introduction of withNewFilter() in the KubernetesClient DSL, offering better options for Kubernetes resource filtering.

Learn more about fabric8

This article has demonstrated just a few of fabric8's features for using Kubernetes APIs in a Java environment. For more examples, see the Kubernetes Java client examples repository. For a deep dive into using fabric8, visit the fabric8 Kubernetes Java Client Cheat Sheet.

Comments