Quarkus - Kubernetes Client

Quarkus includes the kubernetes-client extension which enables the use of the Fabric8 Kubernetes Clientin native mode while also making it easier to work with.

Having a Kubernetes Client extension in Quarkus is very useful in order to unlock the power of Kubernetes Operators.Kubernetes Operators are quickly emerging as a new class of Cloud Native applications.These applications essentially watch the Kubernetes API and react to changes on various resources and can be used to manage the lifecycle of all kinds of complex systems like databases, messaging systems and much much more.Being able to write such operators in Java with the very low footprint that native images provide is a great match.

Configuration

Once you have your Quarkus project configured you can add the kubernetes-client extensionto your project by running the following command in your project base directory.

/mvnw quarkus:add-extension -Dextensions="kubernetes-client"

  1. This will add the following to your pom.xml:
  1. <dependency>
  2. <groupId>io.quarkus</groupId>
  3. <artifactId>quarkus-kubernetes-client</artifactId>
  4. </dependency>

Usage

Quarkus configures a Bean of type KubernetesClient which can be injected into application code using the well known CDI methods.This client can be configured using various properties as can be seen in the following example:

  1. quarkus.kubernetes-client.trust-certs=false
  2. quarkus.kubernetes-client.namespace=default

Note that the full list of properties is available in the KubernetesClientBuildConfig class.

Overriding

The extension also allows application code to override either of io.fabric8.kubernetes.client.Config or io.fabric8.kubernetes.client.KubernetesClient which arenormally provided by the extension by simply declaring custom versions of those beans.

An example of this can be seen in the following snippet:

  1. @ApplicationScoped
  2. public class KubernetesClientProducer {
  3. @Produces
  4. public KubernetesClient kubernetesClient() {
  5. // here you would create a custom client
  6. return new DefaultKubernetesClient();
  7. }
  8. }

Testing

To make testing against a mock Kubernetes API extremely simple, Quarkus provides the KubernetesMockServerTestResource which automatically launchesa mock of the Kubernetes API server and sets the proper environment variables needed so that the Kubernetes Client configures itself to use said mock.Tests can inject the mock and set it up in any way necessary for the particular testing using the @MockServer annotation.

Let’s assume we have a REST endpoint defined like so:

  1. @Path("/pod")
  2. public class Pods {
  3. private final KubernetesClient kubernetesClient;
  4. public Pods(KubernetesClient kubernetesClient) {
  5. this.kubernetesClient = kubernetesClient;
  6. }
  7. @GET
  8. @Produces(MediaType.APPLICATION_JSON)
  9. @Path("/{namespace}")
  10. public List<Pod> pods(@PathParam("namespace") String namespace) {
  11. return kubernetesClient.pods().inNamespace(namespace).list().getItems();
  12. }
  13. }

We could write a test for this endpoint very easily like so:

  1. @QuarkusTestResource(KubernetesMockServerTestResource.class)
  2. @QuarkusTest
  3. public class KubernetesClientTest {
  4. @MockServer
  5. KubernetesMockServer mockServer;
  6. @BeforeEach
  7. public void before() {
  8. final Pod pod1 = new PodBuilder().withNewMetadata().withName("pod1").withNamespace("test").and().build();
  9. final Pod pod2 = new PodBuilder().withNewMetadata().withName("pod2").withNamespace("test").and().build();
  10. mockServer.expect().get().withPath("/api/v1/namespaces/test/pods")
  11. .andReturn(200,
  12. new PodListBuilder().withNewMetadata().withResourceVersion("1").endMetadata().withItems(pod1, pod2)
  13. .build())
  14. .always();
  15. }
  16. @Test
  17. public void testInteractionWithAPIServer() {
  18. RestAssured.when().get("/pod/test").then()
  19. .body("size()", is(2));
  20. }
  21. }

Note that to take advantage of these features, the quarkus-test-kubernetes-client dependency needs to be added, for example like so:

  1. <dependency>
  2. <groupId>io.quarkus</groupId>
  3. <artifactId>quarkus-test-kubernetes-client</artifactId>
  4. <scope>test</scope>
  5. </dependency>

Note on implementing the Watcher interface

Due to the restrictions imposed by GraalVM, extra care needs to be taken when implementing a io.fabric8.kubernetes.client.Watcher if the application is intended to work in native mode.Essentially every Watcher implementation needs to specify the Kubernetes model class that it handles via the Watcher's generic type at class definition time.To better understand this, suppose we want to watch for changes to Kubernetes Pod resources. There are a couple ways to write such a Watcher that are guaranteed to work in native:

  1. client.pods().watch(new Watcher<Pod>() {
  2. @Override
  3. public void eventReceived(Action action, Pod pod) {
  4. // do something
  5. }
  6. @Override
  7. public void onClose(KubernetesClientException e) {
  8. // do something
  9. }
  10. });

or

  1. public class PodResourceWatcher implements Watcher<Pod> {
  2. @Override
  3. public void eventReceived(Action action, Pod pod) {
  4. // do something
  5. }
  6. @Override
  7. public void onClose(KubernetesClientException e) {
  8. // do something
  9. }
  10. }
  11. ...
  12. client.pods().watch(new PodResourceWatcher());

Note that defining the generic type via a class hierarchy similar to the following example will also work correctly:

  1. public abstract class MyWatcher<S> implements Watcher<S> {
  2. }
  3. ...
  4. client.pods().watch(new MyWatcher<Pod>() {
  5. @Override
  6. public void eventReceived(Action action, Pod pod) {
  7. // do something
  8. }
  9. });
The following example will not work in native mode because the generic type of watcher cannot be determined by looking at the class and method definitionsthus making Quarkus unable to properly determine the Kubernetes model class for which reflection registration is needed:
  1. public class ResourceWatcher<T extends HasMetadata> implements Watcher<T> {
  2. @Override
  3. public void eventReceived(Action action, T resource) {
  4. // do something
  5. }
  6. @Override
  7. public void onClose(KubernetesClientException e) {
  8. // do something
  9. }
  10. }
  11. client.pods().watch(new ResourceWatcher<Pod>());

Access to the Kubernetes API

In many cases in order to access the Kubernetes API server a ServiceAccount, Role and RoleBinding will be necessary.An example that allows listing all pods could look something like this:

  1. ---
  2. apiVersion: v1
  3. kind: ServiceAccount
  4. metadata:
  5. name: <applicationName>
  6. namespace: <namespace>
  7. ---
  8. apiVersion: rbac.authorization.k8s.io/v1
  9. kind: Role
  10. metadata:
  11. name: <applicationName>
  12. namespace: <namespace>
  13. rules:
  14. - apiGroups: [""]
  15. resources: ["pods"]
  16. verbs: ["list"]
  17. ---
  18. apiVersion: rbac.authorization.k8s.io/v1
  19. kind: RoleBinding
  20. metadata:
  21. name: <applicationName>
  22. namespace: <namespace>
  23. roleRef:
  24. kind: Role
  25. name: <applicationName>
  26. apiGroup: rbac.authorization.k8s.io
  27. subjects:
  28. - kind: ServiceAccount
  29. name: <applicationName>
  30. namespace: <namespace>

Replace <applicationName> and <namespace> with your values.Have a look at Configure Service Accounts for Pods to get further information.

Configuration Reference

Configuration property fixed at build time - ️ Configuration property overridable at runtime

Configuration propertyTypeDefault
quarkus.kubernetes-client.trust-certsWhether or not the client should trust a self signed certificate if so presented by the API serverbooleanfalse
quarkus.kubernetes-client.master-urlURL of the Kubernetes API serverstring
quarkus.kubernetes-client.namespaceDefault namespace to usestring
quarkus.kubernetes-client.ca-cert-fileCA certificate filestring
quarkus.kubernetes-client.ca-cert-dataCA certificate datastring
quarkus.kubernetes-client.client-cert-fileClient certificate filestring
quarkus.kubernetes-client.client-cert-dataClient certificate datastring
quarkus.kubernetes-client.client-key-fileClient key filestring
quarkus.kubernetes-client.client-key-dataClient key datastring
quarkus.kubernetes-client.client-key-algoClient key algorithmstring
quarkus.kubernetes-client.client-key-passphraseClient key passphrasestring
quarkus.kubernetes-client.usernameKubernetes auth usernamestring
quarkus.kubernetes-client.passwordKubernetes auth passwordstring
quarkus.kubernetes-client.watch-reconnect-intervalWatch reconnect intervalDurationPT1S
quarkus.kubernetes-client.watch-reconnect-limitMaximum reconnect attempts in case of watch failure By default there is no limit to the number of reconnect attemptsint-1
quarkus.kubernetes-client.connection-timeoutMaximum amount of time to wait for a connection with the API server to be establishedDurationPT10S
quarkus.kubernetes-client.request-timeoutMaximum amount of time to wait for a request to the API server to be completedDurationPT10S
quarkus.kubernetes-client.rolling-timeoutMaximum amount of time in milliseconds to wait for a rollout to be completedDurationPT15M
quarkus.kubernetes-client.http-proxyHTTP proxy used to access the Kubernetes API serverstring
quarkus.kubernetes-client.https-proxyHTTPS proxy used to access the Kubernetes API serverstring
quarkus.kubernetes-client.proxy-usernameProxy usernamestring
quarkus.kubernetes-client.proxy-passwordProxy passwordstring
quarkus.kubernetes-client.no-proxyIP addresses or hosts to exclude from proxyinglist of java.util.Optional<java.lang.String>required
About the Duration formatThe format for durations uses the standard java.time.Duration format.You can learn more about it in the Duration#parse() javadoc.You can also provide duration values starting with a number.In this case, if the value consists only of a number, the converter treats the value as seconds.Otherwise, PT is implicitly prepended to the value to obtain a standard java.time.Duration format.