Access the Kubernetes API from a Pod in Java
Introduction
In this example, I will show you how to access the Kubernetes cluster’s REST API from inside a Pod using YAKC (Yet Another Kubernetes Client), Eclipse JKube and Spring Boot.
In the first part of the tutorial I’ll show how to create a very simple Spring Boot application with the required YAKC Kubernetes Client dependency (see the introductory post for YAKC).
Next you’ll see how to quickly deploy the application into a Kubernetes cluster. I will also show you how to perform requests to the Pod’s exposed REST endpoints to retrieve information from the cluster.
Example application
The first step will be to create an example Spring Boot Maven application. This is the pom.xml file that we’ll be using for this example. Besides the standard Spring Boot dependencies, we’ll need to include the required dependencies for YAKC Kubernetes Client:
1<!-- ... -->
2 <dependencies>
3<!-- ... -->
4 <dependency>
5 <groupId>com.marcnuri.yakc</groupId>
6 <artifactId>kubernetes-api</artifactId>
7 <version>${project.version}</version>
8 </dependency>
9 <dependency>
10 <groupId>com.marcnuri.yakc</groupId>
11 <artifactId>kubernetes-client</artifactId>
12 <version>${project.version}</version>
13 </dependency>
14 </dependencies>
15<!-- ... -->
We can now declare a shared Bean of KubernetesClient
in the application’s main class (AccessClusterFromPodApplication
):
1/* ... */
2 @Bean(destroyMethod = "close")
3 public KubernetesClient kubernetesClient() {
4 return new KubernetesClient();
5 }
6/* ... */
This Bean enables us to autowire the KubernetesClient
singleton instance into any of the application’s components.
Cluster Service
We’ll define the different methods in the ClusterService
interface and their implementations in the KubernetesClientClusterService
implementation.
The most complex method in this class will be the implementation of getDeploymentLogs
that will retrieve the aggregated Pod logs sorted by time for a specific deployment. Since this is the most complex method I’ll analyze its implementation, the rest of the method implementations should be self-explanatory.
1@Override
2 public String getDeploymentLogs(String deploymentName) {
3 try {
4 final Deployment deployment = kubernetesClient.create(AppsV1Api.class)
5 .readNamespacedDeployment(deploymentName, getNamespace()).get();
6 return kubernetesClient.create(CoreV1Api.class).listNamespacedPod(getNamespace(),
7 new ListNamespacedPod().labelSelector(
8 deployment.getSpec().getSelector().getMatchLabels().entrySet().stream()
9 .map(e -> String.format("%s=%s", e.getKey(), e.getValue())).collect(
10 Collectors.joining(","))
11 )).stream()
12 .flatMap(this::getPodLog)
13 .sorted()
14 .collect(Collectors.joining("\n"));
15 } catch(NotFoundException e) {
16 throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
17 } catch (IOException e) {
18 throw new ServerErrorException(e.getMessage(), e);
19 }
20 }
21
22 private Stream<String> getPodLog(Pod pod) {
23 try {
24 return Stream.of(kubernetesClient.create(CoreV1Api.class)
25 .readNamespacedPodLog(pod.getMetadata().getName(), getNamespace(), new ReadNamespacedPodLog().timestamps(true))
26 .get().split("\n")
27 ).map(logLine -> {
28 final String[] splitLine = logLine.split("\\s", 2);
29 return String.format("%s [%s] - %s", splitLine[0], pod.getMetadata().getName(),
30 splitLine.length > 1 ? splitLine[1] : "");
31 });
32 } catch (IOException e) {
33 throw new ServerErrorException("Cannot retrieve log", e);
34 }
35 }
The method starts by creating the client for the Apps V1 API which is used to get the information for the provided Deployment name in the configured namespace.
The Deployment spec.selector.matchLabels
field entries are used in a Core V1 API client to retrieve the Pods that match this selector. The resulting Pods are streamed and flat mapped to a stream of log entries for all of the matching Pods, this mapping is performed by the getPodLog
method.
The getPodLog
method uses the Core V1 API client to read the logs of a given Pod including timestamps. The resulting log is then split by line separators and transformed to include the Pod name after the timestamp.
After the mapping is completed the getDeploymentLogs
sorts the log entries and merges them back into a single String which is returned.
Despite the complexity to merge logs from all Pods for a given deployment, YAKC’s declarative and streamable interfaces make it very easy and straightforward. The resulting implementation is very readable too and I can accomplish this task with very few lines of code.
Cluster Resource
The ClusterResource class implements the different REST endpoints that will be exposed by our application. This class is a standard Spring RestController
with GetMapping
annotated methods to retrieve the configured namespace, available Deployments and Pods in this namespace and the aggregated log entries for all Pods in a given Deployment. For the service method explained in the previous section, we can perform a GET request to http://cluster-ingress-url/deployments/deployment-name/logs
to retrieve the aggregate log for the specified deployment:
1@GetMapping("deployments/{deployment}/logs")
2 public String getDeploymentLogs(@PathVariable("deployment") String deployment) {
3 return clusterService.getDeploymentLogs(deployment);
4 }
Deploying the application into the Cluster
We’ve seen the anatomy of our application and how it makes use of YAKC to interact with a Kubernetes cluster. Now let’s see how to deploy the application into the cluster.
The main requirement to allow access from a Pod to the cluster’s API is to provide access to the default Pod’s service account. You can achieve this by running the following command:
1kubectl create clusterrolebinding default-cluster-admin --clusterrole cluster-admin --serviceaccount=default:default
From this point on, the default service account (credentials provided in /var/run/secrets/kubernetes.io/serviceaccount/token
) in your Pod will be able to access the cluster’s REST API.
Eclipse JKube
The example uses Eclipse JKube’s Kubernetes Maven Plugin to easily build and deploy the Java application into Kubernetes.
The plugin is configured in the pom.xml
:
1<!-- ... -->
2<plugin>
3 <groupId>org.eclipse.jkube</groupId>
4 <artifactId>kubernetes-maven-plugin</artifactId>
5 <version>1.17.0</version>
6 <executions>
7 <execution>
8 <id>jkube</id>
9 <phase>package</phase>
10 <goals>
11 <goal>build</goal>
12 <goal>resource</goal>
13 <goal>apply</goal>
14 </goals>
15 </execution>
16 </executions>
17 <configuration>
18 <enricher>
19 <config>
20 <jkube-service>
21 <type>NodePort</type>
22 </jkube-service>
23 </config>
24 </enricher>
25 </configuration>
26</plugin>
27<!-- ... -->
The plugin is configured so that the docker image is built and deployed into the cluster when running the mvn package
command.
You can also see a JKube enricher configuration to make the Kuberentes service of type NodePort
.
If we are running Minikube we first need to get access to the Docker daemon in the cluster. This allows us to skip pushing the built image to a shared registry too.
eval $(minikube docker-env)
on Linux@FOR /f "tokens=*" %i IN ('minikube -p minikube docker-env') DO @%i
on Windows
We can now proceed to deploy our application (mvn clean package
):
The application should now be deployed, if running Minikube we can check out some of the endpoints with the following commands:
Conclusion
In this post, we’ve seen how we can very easily access a Kubernetes cluster REST API from within a Java application running in one of the cluster’s Pods using YAKC (Yet Another Kubernetes Client). We’ve also covered how to build the image for the application and deploy it on the cluster with very little configuration taking advantage of Eclipse JKube’s Kubernetes Maven Plugin.
You can check the full source code for this post in the quickstarts section of YAKC’s GitHub repository.