Quarkus Kubernetes Extension: Complete Guide for Cloud-Native Java

The Quarkus Kubernetes extension provides seamless integration with Kubernetes, enabling easy deployment, configuration, and management of Quarkus applications in Kubernetes environments.


Setup and Dependencies

1. Maven Configuration
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>quarkus-k8s-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<quarkus.version>3.2.0.Final</quarkus.version>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
<surefire-plugin.version>3.0.0</surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Kubernetes Extension -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
<!-- Kubernetes Client -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-client</artifactId>
</dependency>
<!-- Configurations for specific Kubernetes distributions -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-openshift</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-minikube</artifactId>
</dependency>
<!-- Health checks -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>
<!-- RESTEasy Reactive -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<!-- Configuration -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. Application Configuration
# src/main/resources/application.yml
quarkus:
application:
name: user-service
version: 1.0.0
# Kubernetes Configuration
kubernetes:
# Generate Kubernetes resources
generate: true
export: true
# Deployment configuration
deployment:
kind: Deployment
replicas: 3
labels:
app.kubernetes.io/part-of: user-management
app.kubernetes.io/version: ${quarkus.application.version}
annotations:
description: "User Management Microservice"
# Container configuration
container:
image: quay.io/company/user-service:${quarkus.application.version}
image-pull-policy: IfNotPresent
ports:
- name: http
container-port: 8080
env:
- name: JAVA_OPTS
value: "-Xmx256m -Xms128m"
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
# Service configuration
service:
type: ClusterIP
ports:
- name: http
port: 8080
target-port: 8080
# Ingress configuration
ingress:
expose: true
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
rules:
- host: users.company.com
path: /
# Liveness and Readiness probes
liveness-probe:
http-action-path: /q/health/live
initial-delay: 30
period: 10
readiness-probe:
http-action-path: /q/health/ready
initial-delay: 5
period: 10
# Service Account
service-account: user-service-account
# Security Context
security-context:
run-as-user: 1001
run-as-non-root: true
# Node Selector and Affinity
node-selector:
disktype: ssd
affinity:
node-affinity:
required-during-scheduling-ignored-during-execution:
node-selector-terms:
- match-expressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
# Health Configuration
smallrye-health:
root-path: /q/health
# Metrics Configuration
micrometer:
export:
prometheus:
path: /q/metrics
binders:
process:
enabled: true
enabled: true
# Logging
log:
level: INFO
category:
"com.company": DEBUG
# HTTP Server
http:
port: 8080
host: 0.0.0.0
cors: true
# Kubernetes Client
kubernetes-client:
namespace: default
trust-certs: true

Kubernetes Resource Generation

1. Custom Resource Configuration
package com.company.config;
import io.quarkus.kubernetes.client.KubernetesConfigSourceProvider;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithName;
import java.util.Map;
import java.util.Optional;
/**
* Kubernetes-specific configuration mapping
*/
@ConfigMapping(prefix = "k8s")
public interface KubernetesConfig {
@WithName("deployment")
DeploymentConfig deployment();
@WithName("service")
ServiceConfig service();
@WithName("ingress")
Optional<IngressConfig> ingress();
@WithName("resources")
ResourceConfig resources();
interface DeploymentConfig {
int replicas();
Map<String, String> labels();
Map<String, String> annotations();
}
interface ServiceConfig {
String type();
Map<String, String> annotations();
}
interface IngressConfig {
String host();
String path();
Map<String, String> annotations();
}
interface ResourceConfig {
Memory memory();
Cpu cpu();
interface Memory {
String request();
String limit();
}
interface Cpu {
String request();
String limit();
}
}
}
2. Custom Kubernetes Resources
package com.company.k8s;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;
import io.fabric8.kubernetes.model.annotation.Kind;
/**
* Custom Resource Definition for Application Configuration
*/
@Group("company.com")
@Version("v1")
@Kind("ApplicationConfig")
public class ApplicationConfig extends CustomResource<ApplicationConfigSpec, ApplicationConfigStatus> 
implements Namespaced {
// Custom Resource implementation
}
class ApplicationConfigSpec {
private String environment;
private DatabaseConfig database;
private CacheConfig cache;
private SecurityConfig security;
// Getters and setters
public String getEnvironment() { return environment; }
public void setEnvironment(String environment) { this.environment = environment; }
public DatabaseConfig getDatabase() { return database; }
public void setDatabase(DatabaseConfig database) { this.database = database; }
public CacheConfig getCache() { return cache; }
public void setCache(CacheConfig cache) { this.cache = cache; }
public SecurityConfig getSecurity() { return security; }
public void setSecurity(SecurityConfig security) { this.security = security; }
}
class ApplicationConfigStatus {
private String phase;
private String message;
private Conditions conditions;
// Getters and setters
public String getPhase() { return phase; }
public void setPhase(String phase) { this.phase = phase; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public Conditions getConditions() { return conditions; }
public void setConditions(Conditions conditions) { this.conditions = conditions; }
}
// Supporting classes
class DatabaseConfig {
private String url;
private String username;
private String passwordSecret;
// Getters and setters
}
class CacheConfig {
private String redisUrl;
private int ttl;
// Getters and setters
}
class SecurityConfig {
private boolean tlsEnabled;
private String secretName;
// Getters and setters
}
class Conditions {
private boolean available;
private boolean ready;
// Getters and setters
}

Kubernetes Client Integration

1. Kubernetes Client Service
package com.company.k8s.client;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentList;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.quarkus.kubernetes.client.KubernetesClientObjectMapper;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Service for Kubernetes API operations
*/
@ApplicationScoped
public class KubernetesClientService {
@Inject
KubernetesClient kubernetesClient;
@Inject
@KubernetesClientObjectMapper
com.fasterxml.jackson.databind.ObjectMapper objectMapper;
@ConfigProperty(name = "quarkus.kubernetes-client.namespace", defaultValue = "default")
String namespace;
// Deployment Operations
public Deployment createDeployment(Deployment deployment) {
return kubernetesClient.apps()
.deployments()
.inNamespace(namespace)
.create(deployment);
}
public List<Deployment> getDeployments() {
return kubernetesClient.apps()
.deployments()
.inNamespace(namespace)
.list()
.getItems();
}
public Optional<Deployment> getDeployment(String name) {
Deployment deployment = kubernetesClient.apps()
.deployments()
.inNamespace(namespace)
.withName(name)
.get();
return Optional.ofNullable(deployment);
}
public Deployment updateDeployment(String name, Deployment deployment) {
return kubernetesClient.apps()
.deployments()
.inNamespace(namespace)
.withName(name)
.patch(deployment);
}
public boolean deleteDeployment(String name) {
return kubernetesClient.apps()
.deployments()
.inNamespace(namespace)
.withName(name)
.delete();
}
// Pod Operations
public List<Pod> getPodsByLabel(String labelSelector) {
return kubernetesClient.pods()
.inNamespace(namespace)
.withLabelSelector(labelSelector)
.list()
.getItems();
}
public List<Pod> getPodsForDeployment(String deploymentName) {
Optional<Deployment> deployment = getDeployment(deploymentName);
if (deployment.isEmpty()) {
return List.of();
}
Map<String, String> labels = deployment.get().getSpec().getSelector().getMatchLabels();
StringBuilder labelSelector = new StringBuilder();
labels.forEach((key, value) -> {
if (!labelSelector.isEmpty()) {
labelSelector.append(",");
}
labelSelector.append(key).append("=").append(value);
});
return getPodsByLabel(labelSelector.toString());
}
// ConfigMap Operations
public ConfigMap createConfigMap(String name, Map<String, String> data) {
ConfigMap configMap = new ConfigMapBuilder()
.withNewMetadata()
.withName(name)
.withNamespace(namespace)
.endMetadata()
.withData(data)
.build();
return kubernetesClient.configMaps()
.inNamespace(namespace)
.create(configMap);
}
public Optional<ConfigMap> getConfigMap(String name) {
ConfigMap configMap = kubernetesClient.configMaps()
.inNamespace(namespace)
.withName(name)
.get();
return Optional.ofNullable(configMap);
}
// Secret Operations
public Secret createSecret(String name, Map<String, String> data) {
Secret secret = new SecretBuilder()
.withNewMetadata()
.withName(name)
.withNamespace(namespace)
.endMetadata()
.withStringData(data)
.build();
return kubernetesClient.secrets()
.inNamespace(namespace)
.create(secret);
}
// Namespace Operations
public List<Namespace> getNamespaces() {
return kubernetesClient.namespaces()
.list()
.getItems();
}
public Namespace createNamespace(String name) {
Namespace namespaceObj = new NamespaceBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.build();
return kubernetesClient.namespaces()
.create(namespaceObj);
}
// Custom Resource Operations
public <T extends io.fabric8.kubernetes.client.CustomResource<?, ?>> 
MixedOperation<T, ?, ?> getCustomResourceClient(Class<T> resourceClass) {
return kubernetesClient.resources(resourceClass);
}
// Scale Deployment
public Deployment scaleDeployment(String name, int replicas) {
return kubernetesClient.apps()
.deployments()
.inNamespace(namespace)
.withName(name)
.scale(replicas);
}
// Get Pod Logs
public String getPodLogs(String podName) {
return kubernetesClient.pods()
.inNamespace(namespace)
.withName(podName)
.getLog();
}
// Execute Command in Pod
public String executeCommandInPod(String podName, String containerName, String... command) {
return kubernetesClient.pods()
.inNamespace(namespace)
.withName(podName)
.inContainer(containerName)
.writingOutput(System.out)
.exec(command);
}
// Health Check - Verify cluster connectivity
public boolean isClusterHealthy() {
try {
kubernetesClient.getApiGroups();
return true;
} catch (Exception e) {
return false;
}
}
}
2. Custom Resource Watcher
package com.company.k8s.watcher;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.WatcherException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import com.company.k8s.ApplicationConfig;
import org.jboss.logging.Logger;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Watcher for ApplicationConfig custom resources
*/
@ApplicationScoped
public class ApplicationConfigWatcher implements Watcher<ApplicationConfig> {
private static final Logger LOG = Logger.getLogger(ApplicationConfigWatcher.class);
@Inject
MixedOperation<ApplicationConfig, ?, ?> applicationConfigClient;
@Inject
ApplicationConfigService applicationConfigService;
private final ConcurrentMap<String, ApplicationConfig> cache = new ConcurrentHashMap<>();
public void startWatching() {
LOG.info("Starting ApplicationConfig watcher...");
applicationConfigClient.inAnyNamespace().watch(this);
}
@Override
public void eventReceived(Action action, ApplicationConfig resource) {
String name = resource.getMetadata().getName();
String namespace = resource.getMetadata().getNamespace();
LOG.infof("ApplicationConfig %s in namespace %s: %s", name, namespace, action);
switch (action) {
case ADDED:
case MODIFIED:
cache.put(getCacheKey(namespace, name), resource);
applicationConfigService.handleConfigUpdate(resource);
break;
case DELETED:
cache.remove(getCacheKey(namespace, name));
applicationConfigService.handleConfigDeletion(resource);
break;
case ERROR:
LOG.errorf("Error watching ApplicationConfig %s in namespace %s", name, namespace);
break;
}
}
@Override
public void onClose(WatcherException cause) {
if (cause != null) {
LOG.error("Watcher closed with error", cause);
// Attempt to restart watcher
restartWatcher();
} else {
LOG.info("Watcher closed normally");
}
}
private void restartWatcher() {
try {
Thread.sleep(5000); // Wait 5 seconds before restart
startWatching();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.error("Failed to restart watcher", e);
}
}
private String getCacheKey(String namespace, String name) {
return namespace + "/" + name;
}
public ApplicationConfig getCachedConfig(String namespace, String name) {
return cache.get(getCacheKey(namespace, name));
}
}

Health Checks and Metrics

1. Custom Health Checks
package com.company.health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.Readiness;
import org.eclipse.microprofile.health.Startup;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
/**
* Kubernetes health checks
*/
@ApplicationScoped
public class KubernetesHealthChecks {
@Inject
com.company.k8s.client.KubernetesClientService kubernetesClientService;
@Liveness
HealthCheck liveness() {
return () -> HealthCheckResponse.named("user-service-liveness")
.status(isServiceAlive())
.withData("timestamp", System.currentTimeMillis())
.build();
}
@Readiness
HealthCheck readiness() {
return () -> HealthCheckResponse.named("user-service-readiness")
.status(isServiceReady())
.withData("database", checkDatabaseConnectivity())
.withData("cache", checkCacheConnectivity())
.withData("kubernetes", checkKubernetesConnectivity())
.build();
}
@Startup
HealthCheck startup() {
return () -> HealthCheckResponse.named("user-service-startup")
.status(isStartupComplete())
.withData("migrations", checkDatabaseMigrations())
.withData("config", checkConfigurationLoaded())
.build();
}
private boolean isServiceAlive() {
// Basic liveness check - is the application running?
return true;
}
private boolean isServiceReady() {
// Readiness check - can the application handle requests?
return checkDatabaseConnectivity() && 
checkCacheConnectivity() && 
checkKubernetesConnectivity();
}
private boolean isStartupComplete() {
// Startup check - is the application fully initialized?
return checkDatabaseMigrations() && checkConfigurationLoaded();
}
private boolean checkDatabaseConnectivity() {
// Implement database connectivity check
return true;
}
private boolean checkCacheConnectivity() {
// Implement cache connectivity check
return true;
}
private boolean checkKubernetesConnectivity() {
return kubernetesClientService.isClusterHealthy();
}
private boolean checkDatabaseMigrations() {
// Check if database migrations completed successfully
return true;
}
private boolean checkConfigurationLoaded() {
// Check if all required configuration is loaded
return true;
}
}
2. Custom Metrics
package com.company.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.annotation.Gauge;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* Application metrics for Kubernetes monitoring
*/
@ApplicationScoped
public class ApplicationMetrics {
@Inject
MeterRegistry meterRegistry;
private final Counter requestsTotal;
private final Counter errorsTotal;
private final Timer requestDuration;
private final ConcurrentMap<String, Counter> endpointCounters;
public ApplicationMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.requestsTotal = Counter.builder("app_requests_total")
.description("Total number of requests")
.register(meterRegistry);
this.errorsTotal = Counter.builder("app_errors_total")
.description("Total number of errors")
.register(meterRegistry);
this.requestDuration = Timer.builder("app_request_duration_seconds")
.description("Request duration in seconds")
.register(meterRegistry);
this.endpointCounters = new ConcurrentHashMap<>();
}
public void recordRequest(String endpoint, long durationMs) {
requestsTotal.increment();
requestDuration.record(durationMs, TimeUnit.MILLISECONDS);
// Record per-endpoint metrics
String counterName = "app_endpoint_requests_total";
endpointCounters.computeIfAbsent(endpoint, 
ep -> Counter.builder(counterName)
.tag("endpoint", ep)
.register(meterRegistry)
).increment();
}
public void recordError(String endpoint, String errorType) {
errorsTotal.increment();
String counterName = "app_endpoint_errors_total";
endpointCounters.computeIfAbsent(endpoint + "_" + errorType,
key -> Counter.builder(counterName)
.tag("endpoint", endpoint)
.tag("type", errorType)
.register(meterRegistry)
).increment();
}
@Gauge(name = "app_active_connections", unit = MetricUnits.NONE)
public int getActiveConnections() {
// Implement active connections tracking
return 0;
}
@Gauge(name = "app_cache_size", unit = MetricUnits.NONE)
public long getCacheSize() {
// Implement cache size tracking
return 0L;
}
@Gauge(name = "app_queue_size", unit = MetricUnits.NONE)
public int getQueueSize() {
// Implement queue size tracking
return 0;
}
}

REST API with Kubernetes Integration

1. REST Resource with Kubernetes Operations
package com.company.resource;
import com.company.k8s.client.KubernetesClientService;
import com.company.metrics.ApplicationMetrics;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
/**
* REST resource exposing Kubernetes operations
*/
@Path("/api/k8s")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class KubernetesResource {
private static final Logger LOG = Logger.getLogger(KubernetesResource.class);
@Inject
KubernetesClientService kubernetesClientService;
@Inject
ApplicationMetrics metrics;
@ConfigProperty(name = "quarkus.application.name")
String applicationName;
@GET
@Path("/deployments")
public Response getDeployments() {
long startTime = System.currentTimeMillis();
try {
List<io.fabric8.kubernetes.api.model.apps.Deployment> deployments = 
kubernetesClientService.getDeployments();
metrics.recordRequest("getDeployments", System.currentTimeMillis() - startTime);
return Response.ok(deployments).build();
} catch (Exception e) {
metrics.recordError("getDeployments", "kubernetes_error");
LOG.error("Failed to get deployments", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to retrieve deployments"))
.build();
}
}
@GET
@Path("/deployments/{name}")
public Response getDeployment(@PathParam("name") String name) {
long startTime = System.currentTimeMillis();
try {
var deployment = kubernetesClientService.getDeployment(name);
if (deployment.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "Deployment not found: " + name))
.build();
}
metrics.recordRequest("getDeployment", System.currentTimeMillis() - startTime);
return Response.ok(deployment.get()).build();
} catch (Exception e) {
metrics.recordError("getDeployment", "kubernetes_error");
LOG.errorf("Failed to get deployment: %s", name, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to retrieve deployment: " + name))
.build();
}
}
@POST
@Path("/deployments/{name}/scale")
public Response scaleDeployment(
@PathParam("name") String name,
@QueryParam("replicas") int replicas) {
long startTime = System.currentTimeMillis();
if (replicas < 0 || replicas > 10) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "Replicas must be between 0 and 10"))
.build();
}
try {
var deployment = kubernetesClientService.scaleDeployment(name, replicas);
metrics.recordRequest("scaleDeployment", System.currentTimeMillis() - startTime);
return Response.ok(Map.of(
"message", "Deployment scaled successfully",
"deployment", deployment.getMetadata().getName(),
"replicas", deployment.getSpec().getReplicas()
)).build();
} catch (Exception e) {
metrics.recordError("scaleDeployment", "kubernetes_error");
LOG.errorf("Failed to scale deployment: %s to %d replicas", name, replicas, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to scale deployment: " + name))
.build();
}
}
@GET
@Path("/pods")
public Response getPods(@QueryParam("labelSelector") String labelSelector) {
long startTime = System.currentTimeMillis();
try {
List<io.fabric8.kubernetes.api.model.Pod> pods;
if (labelSelector != null && !labelSelector.isEmpty()) {
pods = kubernetesClientService.getPodsByLabel(labelSelector);
} else {
// Get pods for current application
pods = kubernetesClientService.getPodsForDeployment(applicationName);
}
metrics.recordRequest("getPods", System.currentTimeMillis() - startTime);
return Response.ok(pods).build();
} catch (Exception e) {
metrics.recordError("getPods", "kubernetes_error");
LOG.error("Failed to get pods", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to retrieve pods"))
.build();
}
}
@GET
@Path("/cluster/health")
public Response getClusterHealth() {
long startTime = System.currentTimeMillis();
try {
boolean isHealthy = kubernetesClientService.isClusterHealthy();
metrics.recordRequest("getClusterHealth", System.currentTimeMillis() - startTime);
return Response.ok(Map.of(
"healthy", isHealthy,
"timestamp", System.currentTimeMillis()
)).build();
} catch (Exception e) {
metrics.recordError("getClusterHealth", "kubernetes_error");
LOG.error("Failed to check cluster health", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to check cluster health"))
.build();
}
}
@GET
@Path("/configmaps/{name}")
public Response getConfigMap(@PathParam("name") String name) {
long startTime = System.currentTimeMillis();
try {
var configMap = kubernetesClientService.getConfigMap(name);
if (configMap.isEmpty()) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "ConfigMap not found: " + name))
.build();
}
metrics.recordRequest("getConfigMap", System.currentTimeMillis() - startTime);
return Response.ok(configMap.get()).build();
} catch (Exception e) {
metrics.recordError("getConfigMap", "kubernetes_error");
LOG.errorf("Failed to get ConfigMap: %s", name, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to retrieve ConfigMap: " + name))
.build();
}
}
}
2. Application Configuration Resource
package com.company.resource;
import com.company.k8s.ApplicationConfig;
import com.company.k8s.client.KubernetesClientService;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
/**
* Resource for managing ApplicationConfig custom resources
*/
@Path("/api/configs")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ApplicationConfigResource {
@Inject
KubernetesClientService kubernetesClientService;
private MixedOperation<ApplicationConfig, ?, ?> getApplicationConfigClient() {
return kubernetesClientService.getCustomResourceClient(ApplicationConfig.class);
}
@GET
public Response getAllConfigs(@QueryParam("namespace") String namespace) {
try {
List<ApplicationConfig> configs;
if (namespace != null && !namespace.isEmpty()) {
configs = getApplicationConfigClient().inNamespace(namespace).list().getItems();
} else {
configs = getApplicationConfigClient().inAnyNamespace().list().getItems();
}
return Response.ok(configs).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to retrieve application configs"))
.build();
}
}
@GET
@Path("/{namespace}/{name}")
public Response getConfig(
@PathParam("namespace") String namespace,
@PathParam("name") String name) {
try {
ApplicationConfig config = getApplicationConfigClient()
.inNamespace(namespace)
.withName(name)
.get();
if (config == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "ApplicationConfig not found"))
.build();
}
return Response.ok(config).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to retrieve application config"))
.build();
}
}
@POST
public Response createConfig(ApplicationConfig config) {
try {
String namespace = config.getMetadata().getNamespace();
if (namespace == null) {
namespace = "default";
config.getMetadata().setNamespace(namespace);
}
ApplicationConfig created = getApplicationConfigClient()
.inNamespace(namespace)
.create(config);
return Response.status(Response.Status.CREATED)
.entity(created)
.build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to create application config"))
.build();
}
}
@PUT
@Path("/{namespace}/{name}")
public Response updateConfig(
@PathParam("namespace") String namespace,
@PathParam("name") String name,
ApplicationConfig config) {
try {
ApplicationConfig updated = getApplicationConfigClient()
.inNamespace(namespace)
.withName(name)
.patch(config);
return Response.ok(updated).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to update application config"))
.build();
}
}
@DELETE
@Path("/{namespace}/{name}")
public Response deleteConfig(
@PathParam("namespace") String namespace,
@PathParam("name") String name) {
try {
boolean deleted = getApplicationConfigClient()
.inNamespace(namespace)
.withName(name)
.delete();
if (deleted) {
return Response.ok(Map.of("message", "ApplicationConfig deleted successfully")).build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "ApplicationConfig not found"))
.build();
}
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", "Failed to delete application config"))
.build();
}
}
}

Deployment and Build Configuration

1. Dockerfile Configuration
# Multi-stage Dockerfile
FROM quay.io/quarkus/ubi-quarkus-native-image:22.3-java17 AS build
WORKDIR /project
COPY --chown=quarkus:quarkus mvnw /project/mvnw
COPY --chown=quarkus:quarkus .mvn /project/.mvn
COPY --chown=quarkus:quarkus pom.xml /project/
COPY --chown=quarkus:quarkus src /project/src
USER quarkus
RUN ./mvnw package -Dnative
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
WORKDIR /work/
COPY --from=build /project/target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
2. Kubernetes Deployment Manifests
# Generated by Quarkus Kubernetes extension
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: default
labels:
app.kubernetes.io/name: user-service
app.kubernetes.io/version: 1.0.0
app.kubernetes.io/part-of: user-management
annotations:
description: "User Management Microservice"
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: user-service
template:
metadata:
labels:
app.kubernetes.io/name: user-service
app.kubernetes.io/version: 1.0.0
spec:
serviceAccountName: user-service-account
securityContext:
runAsUser: 1001
runAsNonRoot: true
containers:
- name: user-service
image: quay.io/company/user-service:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
env:
- name: JAVA_OPTS
value: "-Xmx256m -Xms128m"
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /q/health/live
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /q/health/ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
startupProbe:
httpGet:
path: /q/health/started
port: http
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 30
---
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: default
labels:
app.kubernetes.io/name: user-service
app.kubernetes.io/version: 1.0.0
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app.kubernetes.io/name: user-service
type: ClusterIP
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: user-service-account
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: user-service-role
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch"]
- apiGroups: ["company.com"]
resources: ["applicationconfigs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: user-service-role-binding
namespace: default
subjects:
- kind: ServiceAccount
name: user-service-account
namespace: default
roleRef:
kind: Role
name: user-service-role
apiGroup: rbac.authorization.k8s.io

Testing

1. Kubernetes Client Test
package com.company.k8s.test;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.kubernetes.client.KubernetesTestServer;
import io.quarkus.test.kubernetes.client.WithKubernetesTestServer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
@WithKubernetesTestServer
class KubernetesClientServiceTest {
@KubernetesTestServer
KubernetesServer mockServer;
@Inject
KubernetesClient kubernetesClient;
@Inject
com.company.k8s.client.KubernetesClientService kubernetesClientService;
@BeforeEach
void setup() {
// Setup mock responses
mockServer.expect().get().withPath("/api/v1/namespaces/default/pods")
.andReturn(200, "{\"items\": []}")
.always();
}
@Test
void testClusterHealth() {
assertTrue(kubernetesClientService.isClusterHealthy());
}
@Test
void testGetDeployments() {
var deployments = kubernetesClientService.getDeployments();
assertNotNull(deployments);
assertTrue(deployments.isEmpty());
}
}
2. Resource Test
package com.company.resource.test;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.*;
@QuarkusTest
class KubernetesResourceTest {
@Test
void testClusterHealth() {
RestAssured.given()
.when().get("/api/k8s/cluster/health")
.then()
.statusCode(200)
.body("healthy", is(true));
}
@Test
void testGetDeployments() {
RestAssured.given()
.when().get("/api/k8s/deployments")
.then()
.statusCode(200)
.body("$", hasSize(greaterThanOrEqualTo(0)));
}
}

Summary

The Quarkus Kubernetes extension provides:

  1. Seamless Kubernetes Integration: Automatic resource generation and deployment
  2. Type-Safe Kubernetes Client: Easy interaction with Kubernetes API
  3. Health and Metrics: Built-in health checks and Prometheus metrics
  4. Custom Resources: Support for Custom Resource Definitions
  5. Security: RBAC configuration and security context
  6. Development Experience: Fast development with live coding and testing

Key Benefits:

  • Reduced boilerplate for Kubernetes deployments
  • Native compilation for fast startup and low memory footprint
  • Comprehensive monitoring and observability
  • Easy configuration management
  • Excellent developer experience with live reload

This setup enables rapid development of cloud-native Java applications that are optimized for Kubernetes environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper