Building stuff with the Kubernetes API (Part 2) — Using Java

Vladimir Vivien
Programming Kubernetes
9 min readMar 9, 2018

--

This is part 2, of a multipart series, where I walkthrough the different options for working with the Kubernetes API. Starting with this post, I am going to attempt to create a contrived, but functional, example using the official Kubernetes client frameworks. This post will focus on using the client for the Java programming language.

Fear not! Future posts will cover additional clients including Python and Go.

The Kubernetes Clients

The Kubernetes client frameworks can be grouped into two broad categories, mainly, the Go client and the OpenAPI clients. The Go binding is the oldest and most complete of all of the client frameworks. It not only provides API primitives for building simple client tools, but as crucial component of Kubernetes itself, it can be used to create sophisticated distributed systems.

A newer crop of Kubernetes client bindings are also available. They use a Swagger code generator (https://github.com/kubernetes-client/gen) to generate OpenAPI-compliant clients for core and extended API objects. There ares several of these clients that are officially supported including Java, Python, and JavaScript (typescript). Other clients seem to be in early development stage for languages such as C#, Haskell, and Ruby.

Visit the Kubernetes Client Github organization for more info.

In this series, we will explore OpenAPI client frameworks for Java and Python (Go will come later). Fortunately, the capabilities of these clients seem to be moving in the same directions. Therefore, the material covered here should be applicable to other OpenAPI clients such as JavaScript, Ruby, C#, and Haskell.

A simple client tool for Kubernetes

To illustrate the usage of these client frameworks, we will walk through the creation of a simple CLI tool. The tool, pvcwatch, watches the total claimed persistent storage capacity in a cluster for a specified threshold. When the total reaches the threshold, it takes an action (in this example, it’s a simple notification on the screen).

You can find the complete example on GitHub.

The example is designed to highlight several aspects of Kubernetes client frameworks. We will limit our code to the followings:

  • connectivity
  • resource list retrieval and walk through
  • object watch

All client frameworks covered in this series are also capable of creating, updating, and removing API objects. For sake of simplicity, we will not cover these operations here. However, you will find examples in their respective repositories.

The Kubernetes Java Client

The official Kubernetes Java client framework (https://github.com/kubernetes-client/java) is a collection of several Java library projects and the Swagger scripts (used to generate the API clients).

The kubernetes directory contains the HTTP client (implemented with okHttp), the generated models and the Kubernetes object serializers. Directory util is a collection of utilitarian classes for connectivity and other Kubernetes cluster operations.

Connecting to the API server

The first step in our Java client program will be to connect to the API server. To do this, we are going to create an ApiClient object using the Config.defaultClient() static method.

import io.kubernetes.client.ApiClient;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.Configuration;
public class PVCWatch {
public static void main(String[] args) throws IOException{
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);
...
}
}

In the previous snippet, class Config (found in project util) exposes several utility methods that can return a new ApiClient depending how and where we want to connect to the API server.

Within a cluster

If the tool you are building will be deployed in a Kubernetes cluster, we can use the following method to return an ApiClient object pre-configured with cluster information.

public static ApiClient fromCluster() throws IOException{...}

From a config file

If the tool you are building has access to a local kubeconfig file you can use the following method to create an ApiClient instance from the file.

public static ApiClient fromConfig(String fileName)

Auto-detect

Method defaultClient() (used in our example earlier) attempts to auto detect where the code is being used and creates a client object accordingly. It first looks for a kubeconfig file in environment variable $KUBECONFIG, then falls back to path $HOME/.kube/config. If that doesn't work, it falls back to an in-cluster mode by detecting a default service account. Lastly, if none of this works, it attempts to connect to the server at http://localhost:8080.

public static ApiClient defaultClient()

It should be noted that the Config class provides several other utility methods to create ApiClient instances using parameters such as a remote API server, user credentials, bearer token, public certificate files. Checkout these additional methods here.

Create an API object serializer

The ApiClient contains the base transport mechanism to communicate with the server. So, next we need to create a serializer to process typed API objects to and from the server. The Java client framework uses OpenAPI-generated serializer classes which provide methods to access versioned API operations.

You can see examples and read about the versioned API serializer clients and their documentation is here.

For our example, we instantiate class CoreV1Api to access V1 Kubernetes core API objects as shown below.

import io.kubernetes.client.ApiClient;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.Configuration;
import io.kubernetes.client.apis.CoreV1Api;
public class PVCWatch {
public static void main(String[] args) throws IOException{
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);
CoreV1Api api = new CoreV1Api(client);
...
}
}

Listing cluster PVCs

One of the most basic operations we can do with the API clients is to retrieve resource lists of stored API objects. For our example, we are going to retrieve a namespaced list of PVCs using method listNamespacedPersistentVolumeClaim which returns class V1PersistentVolumeClaimList.

import io.kubernetes.client.ApiClient;
import io.kubernetes.client.apis.CoreV1Api;
import io.kubernetes.client.models.V1PersistentVolumeClaimList;
public class PVCWatch {
public static void main(String[] args) throws IOException{
String namespace = System.getenv("K8S_NAMESPACE");
if (namespace == null || "".equals(namespace)) {
namespace = "default";
}
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);
CoreV1Api api = new CoreV1Api(client);
V1PersistentVolumeClaimList list = null;
try{
list = api.listNamespacedPersistentVolumeClaim(
namespace, null, null, null,
null, null, null, null,
null, null);
}catch(ApiException apie){...}
PVCWatch.printPVCs(list);
...
}
}

Next, we use method printPVCs to print the retrieved list of PersistentVolumeClaims as shown in the following:

public class PVCWatch {
...
public static void printPVCs(V1PersistentVolumeClaimList list){
System.out.println("----- PVCs ----");
String template = "%-16s\t%-40s\t%-6s%n";
System.out.format(template,"Name", "Volume", "Size");
for (V1PersistentVolumeClaim item : list.getItems()) {
String name = item.getMetadata().getName();
String volumeName = item.getSpec().getVolumeName();
String size = item.getSpec().getResources().
getRequests().get("storage");
System.out.format(template,name, volumeName, size);
}
}
}

Watching the cluster PVCs

The Java framework supports the ability to watch the cluster for specified lifecycle API object events including ADDED, MODIFIED, DELETED generated when an object is created, updated, and removed respectively. For our simple CLI tool, we will use this watch capability to monitor the total capacity of claimed persistent storage against a running cluster.

When the total claim capacity, for a given namespace, reaches a certain threshold (say 150Gi), we will take an arbitrary action. For simplicity sake, we will just print a notification on the screen. However, in a more sophisticated implementation, the same approach can be used to trigger a some automated action.

Setup a watch

First let’s define a Watch object and specifies the API type that we want it to watch,V1PersistentVolumeClaim, using method Watch.createWatch() as shown below:

import io.kubernetes.client.ApiClient;
import io.kubernetes.client.util.Watch;
import io.kubernetes.client.models.V1PersistentVolumeClaim;
public class PVCWatch {
public static void main(String[] args) throws IOException{
ApiClient client = Config.defaultClient();
client.getHttpClient().setReadTimeout(0, TimeUnit.SECONDS);
...
try {
Watch<V1PersistentVolumeClaim> watch = Watch.createWatch(
client,
api.listNamespacedPersistentVolumeClaimCall(
namespace, null, null, null, null,
null, null, null, null, Boolean.TRUE, null, null),
new TypeToken<Watch.Response<V1PersistentVolumeClaim>>() {
}.getType()
);
}
}

Notice that method cerateWatch takes, as parameter, method listNamespacedPersistentVolumeClaimCall. Though similar in name as the listing method we used earlier, this method returns an okHttp Call type used to populate the list of watched objects. The Boolean.TRUE value passed in the method indicates that we want to use the list in a watch.

Also notice that we are setting the ReadTimeout on the client 0. This forces the internal HTTP client to keep a long running connection with the server. Otherwise, if you don’t do this the connection may timeout before the program receive any event from the server.

Loop through watched items

Next, we use the result of the createWatch() method call to setup a loop to process incoming object events from the API server. Before the loop, notice that we use class Quantity (type to represent resource quantities) to setup our threshold.

...
import io.kubernetes.client.custom.Quantity;
import io.kubernetes.client.custom.Quantity.Format;
import java.math.BigDecimal;
public class PVCWatch {
public static void main(String[] args) throws IOException{
...
Quantity maxClaims = Quantity.fromString("150Gi");
Quantity totalClaims = Quantity.fromString("0Gi");

try {
Watch<V1PersistentVolumeClaim> watch = Watch.createWatch(...);
for (Watch.Response<V1PersistentVolumeClaim> item : watch) {
switch (item.type){
...
}
}
}
}

Processing PVC added event

Inside the loop, we setup a switch statement to inspect each watch item retrieved from the server. An event type of “ADDED” means that a new PVC has been created. The following code extract the PVC and its quantity, from the event watch, to calculate the total quantity (totalClaims). If the total quantity is greater than the threshold (maxClaims), a screen notification action is printed.

...
import io.kubernetes.client.custom.Quantity;
import io.kubernetes.client.custom.Quantity.Format;
import java.math.BigDecimal;
public class PVCWatch {
public static void main(String[] args) throws IOException{
...
Quantity maxClaims = Quantity.fromString("150Gi");
Quantity totalClaims = Quantity.fromString("0Gi");
V1PersistentVolumeClaim pvc = item.object;
String claimSize = null;
Quantity claimQuant = null;
BigDecimal totalNum = null;
try {
Watch<V1PersistentVolumeClaim> watch = Watch.createWatch(...);
for (Watch.Response<V1PersistentVolumeClaim> item : watch) {
switch (item.type){
case "ADDED":
claimSize = pvc.getSpec().getResources()
.getRequests().get("storage");
claimQuant = Quantity.fromString(claimSize);
totalNum = totalClaims
.getNumber().add(claimQuant.getNumber());
totalClaims = new Quantity(totalNum, Format.BINARY_SI);
if (totalClaims.getNumber()
.compareTo(maxClaims.getNumber()) >= 1) {
...
System.out.format(
"%n*** Trigger over capacity action ***"
);
}
break;
...
}
}
}
}

Processing PVC deleted events

The code also monitors when PVCs are removed. It applies a reverse logic and decreases the deleted PVC size from the running total count.

...
case "DELETED":
claimSize = pvc.getSpec().getResources()
.getRequests().get("storage");
claimQuant = Quantity.fromString(claimSize);
totalNum = totalClaims.getNumber()
.subtract(claimQuant.getNumber());
totalClaims = new Quantity(totalNum, Format.BINARY_SI);
if (totalClaims.getNumber()
.compareTo(maxClaims.getNumber()) <= 0) {
System.out.format(
"%nINFO: claim usage normal: max %s, at %s",
maxClaims.toSuffixedString(),
totalClaims.toSuffixedString()
);
}
break;

Run the program

When the program is executed against a running cluster, it first displays the list of existing PVCs. Then it starts watching the cluster for new PersistentVolumeClaim events.

> java -cp .:"./lib/*" PVCWatchconnecting to API server https://0.0.0.0:8443----- PVCs ----
Name Volume Size
my-influx-influxdb pvc-59f760cd-1133-11e8-b7b8-08002739cded 75Gi
----- PVC Watch (max total claims: 150Gi) -----
ADDED: PVC my-influx-influxdb added, size 75Gi
INFO: Total PVC is at 50.0% capacity (75Gi/150Gi)

Next, let us deploy another application unto the cluster that requests an additional 100Gi in storage claim (for our example, let us use Helm to deploy, say, a redis chart) while pvcwatch is still running.

helm install --name my-redis \
--set persistence.enabled=true,persistence.size=100Gi stable/redis

As you can see below, pvcwatch immediately reacts to the new claim and displays our alert since we have gone over the threshold.

----- PVC Watch (max total claims: 150Gi) -----
...
ADDED: PVC my-redis-redis added, size 100Gi
WARNING: claim overage reached: max 150Gi, at 175Gi
*** Trigger over capacity action ***
INFO: Total PVC is at 116.7% capacity (175Gi/150Gi)

Conversely, when a PVC is deleted from the cluster, the tool react accordingly with an alert message. For instance, if we remove the redis application, this will delete its PVC claim of 100Gi and the tool reacts as follows:

----- PVC Watch (max total claims: 150Gi) -----
...
INFO: Total PVC is at 116.7% capacity (175Gi/150Gi)
DELETED: PVC my-redis-redis removed, size 100Gi
INFO: claim usage normal: max 150Gi, at 75Gi
INFO: Total PVC is at 50.0% capacity (75Gi/150Gi)

Summary

In our previous post, we started exploring how to access API objects from Kubernetes manually. This second post, of the series, starts coverage of programmatic interaction with the API server using the official Kubernetes client framework for Java by implementing a CLI tool to watch total PVC sizes for a given namespace.

What’s next

The next post will explore the same tool using the official Kubernetes framework for Python.

If you find this writeup useful, please let me know by clicking on the clapping hands 👏 icon to recommend this post.

References

Series table of content

Git repo for example code— https://github.com/vladimirvivien/k8s-client-examples

Kubernetes clients — https://kubernetes.io/docs/reference/client-libraries/

Official Java client — https://github.com/kubernetes-client/java/

--

--