Knative is a Kubernetes-based platform for deploying and managing serverless workloads. It provides auto-scaling (including to zero), request-driven computation, and simplified deployment abstractions for Java applications.
Knative Core Components
- Serving - Request-driven compute, auto-scaling, routing
- Eventing - Event-driven architecture with event sources and brokers
- Build (deprecated in favor of Tekton) - Pipeline-based builds
Architecture Overview
HTTP Request → Knative Ingress → Activator → Autoscaler → Java Application Pod ↓ Configuration → Revision → Route
Prerequisites
- Kubernetes cluster (1.15+)
- kubectl and kn CLI
- Istio, Contour, or Kourier as network layer
- Java 11+ application
1. Knative Installation
Install Knative Serving
# Install Knative Serving CRDs kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.10.0/serving-crds.yaml # Install Knative Serving Core kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.10.0/serving-core.yaml # Install networking layer (Istio example) kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.10.0/istio.yaml kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.10.0/net-istio.yaml # Verify installation kubectl get pods -n knative-serving
Install Knative CLI (kn)
# Linux curl -Lo kn-linux-amd64 https://github.com/knative/client/releases/download/knative-v1.10.0/kn-linux-amd64 sudo install kn-linux-amd64 /usr/local/bin/kn # Verify kn version
2. Java Application Setup
Spring Boot Knative Application
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>knative-java-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<to>
<image>myregistry/knative-java-app:${project.version}</image>
</to>
</configuration>
</plugin>
</plugins>
</build>
</project>
Application.java
package com.example.knative;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
@RequestMapping("/api")
class KnativeController {
private final AtomicLong requestCount = new AtomicLong(0);
private final Map<String, String> dataStore = new ConcurrentHashMap<>();
@GetMapping("/hello")
public Map<String, Object> hello() {
long count = requestCount.incrementAndGet();
return Map.of(
"message", "Hello from Knative Java App!",
"timestamp", LocalDateTime.now().toString(),
"requestCount", count,
"instanceId", System.getenv().getOrDefault("HOSTNAME", "unknown"),
"version", "1.0.0"
);
}
@PostMapping("/data")
public Map<String, Object> storeData(@RequestBody Map<String, String> data) {
String key = data.get("key");
String value = data.get("value");
if (key != null && value != null) {
dataStore.put(key, value);
return Map.of(
"status", "success",
"key", key,
"value", value
);
} else {
return Map.of("status", "error", "message", "Key and value required");
}
}
@GetMapping("/data/{key}")
public Map<String, Object> getData(@PathVariable String key) {
String value = dataStore.get(key);
if (value != null) {
return Map.of("key", key, "value", value);
} else {
return Map.of("status", "not_found");
}
}
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "healthy", "timestamp", LocalDateTime.now().toString());
}
@GetMapping("/metrics/info")
public Map<String, Object> metrics() {
return Map.of(
"requestCount", requestCount.get(),
"dataStoreSize", dataStore.size(),
"memoryUsage", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(),
"maxMemory", Runtime.getRuntime().maxMemory()
);
}
}
@Component
class StartupPreloader {
@EventListener
public void onApplicationEvent(org.springframework.boot.context.event.ApplicationReadyEvent event) {
System.out.println("🚀 Knative Java Application Started Successfully!");
System.out.println("✅ Environment: " + System.getenv().getOrDefault("KNATIVE_ENV", "default"));
System.out.println("✅ Java Version: " + System.getProperty("java.version"));
System.out.println("✅ Available Processors: " + Runtime.getRuntime().availableProcessors());
}
}
3. Knative Service Configuration
Basic Knative Service
service.yaml
apiVersion: serving.knative.dev/v1 kind: Service metadata: name: java-knative-service namespace: default spec: template: metadata: annotations: # Auto-scaling configuration autoscaling.knative.dev/target: "10" autoscaling.knative.dev/target-utilization-percentage: "70" autoscaling.knative.dev/window: "60s" # Enable graceful shutdown features.knative.dev/grace-termination: "enabled" spec: containers: - image: myregistry/knative-java-app:1.0.0 name: java-app ports: - containerPort: 8080 env: - name: JAVA_OPTS value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" - name: SPRING_PROFILES_ACTIVE value: "knative" - name: KNATIVE_ENV value: "production" resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 20 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 traffic: - latestRevision: true percent: 100
Deploy with kn CLI
# Deploy using kn CLI kn service create java-knative-service \ --image myregistry/knative-java-app:1.0.0 \ --port 8080 \ --env JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" \ --env SPRING_PROFILES_ACTIVE=knative \ --request memory=256Mi,cpu=100m \ --limit memory=512Mi,cpu=500m \ --annotation autoscaling.knative.dev/target=10 # Verify deployment kn service list kn revision list kn route list
4. Advanced Knative Features
Blue-Green Deployment
blue-green-service.yaml
apiVersion: serving.knative.dev/v1 kind: Service metadata: name: java-app-blue-green spec: template: metadata: name: java-app-v2 spec: containers: - image: myregistry/knative-java-app:2.0.0 env: - name: APP_VERSION value: "2.0.0" traffic: - tag: current revisionName: java-app-v1 percent: 50 - tag: candidate revisionName: java-app-v2 percent: 50 - tag: latest latestRevision: true percent: 0
Using kn CLI for Traffic Management
# Split traffic between revisions
kn service update java-knative-service \
--traffic java-knative-service-00001=70 \
--traffic java-knative-service-00002=30
# Tag a specific revision
kn service update java-knative-service --tag java-knative-service-00001=stable
# Access specific revision
curl -H "Host: stable-java-knative-service.default.example.com" http://${INGRESS_IP}
5. Auto-scaling Configuration
Custom Auto-scaling
autoscaling-service.yaml
apiVersion: serving.knative.dev/v1 kind: Service metadata: name: java-app-autoscaling spec: template: metadata: annotations: # Scale based on concurrency autoscaling.knative.dev/metric: "concurrency" autoscaling.knative.dev/target: "10" # Scale based on RPS # autoscaling.knative.dev/metric: "rps" # autoscaling.knative.dev/target: "100" # Scale bounds autoscaling.knative.dev/min-scale: "1" autoscaling.knative.dev/max-scale: "10" # Scale behavior autoscaling.knative.dev/panic-window: "60s" autoscaling.knative.dev/panic-threshold: "200.0" # Enable scale-to-zero autoscaling.knative.dev/initial-scale: "1" autoscaling.knative.dev/scale-to-zero-pod-retention-period: "5m" spec: containers: - image: myregistry/knative-java-app:1.0.0 env: - name: K_REVISION value: "java-app-autoscaling-001" resources: requests: memory: "128Mi" cpu: "50m"
6. Knative Eventing with Java
Event Consumer Application
EventProcessor.java
package com.example.knative.eventing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.function.Consumer;
import java.util.logging.Logger;
@SpringBootApplication
public class EventProcessorApplication {
public static void main(String[] args) {
SpringApplication.run(EventProcessorApplication.class, args);
}
}
@Component
class CloudEventProcessor {
private static final Logger logger = Logger.getLogger(CloudEventProcessor.class.getName());
@Bean
public Consumer<CloudEvent<String>> processOrder() {
return event -> {
logger.info("📦 Received CloudEvent: " + event);
logger.info("🔧 Event ID: " + event.getId());
logger.info("🔧 Event Type: " + event.getType());
logger.info("🔧 Event Source: " + event.getSource());
logger.info("🔧 Event Data: " + event.getData());
// Process the event
processEventData(event.getData());
};
}
@Bean
public Consumer<CloudEvent<byte[]>> processBinaryEvent() {
return event -> {
logger.info("📦 Received Binary CloudEvent");
String data = new String(event.getData());
logger.info("🔧 Event Data: " + data);
};
}
private void processEventData(String data) {
// Business logic for processing events
logger.info("🔄 Processing event data: " + data);
// Simulate processing time
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("✅ Event processing completed");
}
}
// CloudEvent POJO
class CloudEvent<T> {
private String id;
private String source;
private String type;
private String specversion;
private T data;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getSpecversion() { return specversion; }
public void setSpecversion(String specversion) { this.specversion = specversion; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
@Override
public String toString() {
return String.format(
"CloudEvent{id='%s', source='%s', type='%s', specversion='%s'}",
id, source, type, specversion
);
}
}
Eventing Configuration
eventing-setup.yaml
apiVersion: eventing.knative.dev/v1 kind: Broker metadata: name: default namespace: default --- apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: name: java-app-trigger namespace: default spec: broker: default filter: attributes: type: "order.created" subscriber: ref: apiVersion: serving.knative.dev/v1 kind: Service name: java-event-processor
7. Java Application with Knative Features
Knative-Aware Application
KnativeAwareController.java
package com.example.knative.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@RestController
@RequestMapping("/knative")
public class KnativeAwareController {
private final AtomicInteger coldStartCount = new AtomicInteger(0);
private long startupTime = System.currentTimeMillis();
private boolean isColdStart = true;
@GetMapping("/info")
public Map<String, Object> getKnativeInfo(HttpServletRequest request) {
// Check if this is a cold start
if (isColdStart) {
coldStartCount.incrementAndGet();
isColdStart = false;
}
return Map.of(
"knativeRevision", System.getenv().getOrDefault("K_REVISION", "unknown"),
"knativeConfiguration", System.getenv().getOrDefault("K_CONFIGURATION", "unknown"),
"knativeService", System.getenv().getOrDefault("K_SERVICE", "unknown"),
"coldStarts", coldStartCount.get(),
"uptime", System.currentTimeMillis() - startupTime,
"instanceId", System.getenv().getOrDefault("HOSTNAME", "unknown"),
"requestHeaders", extractHeaders(request)
);
}
@PostMapping("/scale-test")
public ResponseEntity<Map<String, Object>> scaleTest(@RequestBody ScaleTestRequest request) {
int duration = request.duration();
int workload = request.workload();
// Simulate CPU workload
long startTime = System.currentTimeMillis();
performWorkload(workload, duration);
long endTime = System.currentTimeMillis();
return ResponseEntity.ok(Map.of(
"status", "completed",
"workload", workload,
"requestedDuration", duration,
"actualDuration", endTime - startTime,
"threadsUsed", Runtime.getRuntime().availableProcessors()
));
}
@GetMapping("/health/detailed")
public Map<String, Object> detailedHealth() {
Runtime runtime = Runtime.getRuntime();
return Map.of(
"status", "healthy",
"memory", Map.of(
"used", runtime.totalMemory() - runtime.freeMemory(),
"free", runtime.freeMemory(),
"total", runtime.totalMemory(),
"max", runtime.maxMemory()
),
"threads", Thread.activeCount(),
"coldStarts", coldStartCount.get(),
"environment", System.getenv().getOrDefault("KNATIVE_ENV", "default")
);
}
private Map<String, String> extractHeaders(HttpServletRequest request) {
return Map.of(
"user-agent", request.getHeader("User-Agent"),
"forwarded", request.getHeader("Forwarded"),
"knative-serving-namespace", request.getHeader("Knative-Serving-Namespace"),
"knative-serving-revision", request.getHeader("Knative-Serving-Revision")
);
}
private void performWorkload(int intensity, int duration) {
long endTime = System.currentTimeMillis() + duration;
while (System.currentTimeMillis() < endTime) {
// Simulate CPU-intensive work
double result = 0;
for (int i = 0; i < intensity * 1000; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
}
}
record ScaleTestRequest(int duration, int workload) {}
}
8. Configuration and Secrets
ConfigMap and Secrets
config.yaml
apiVersion: v1 kind: ConfigMap metadata: name: java-app-config data: application.properties: | server.port=8080 management.endpoints.web.exposure.include=health,info,metrics,prometheus logging.level.com.example=INFO spring.sleuth.sampler.probability=1.0 logback-spring.xml: | <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp/> <logLevel/> <loggerName/> <message/> <mdc/> <stackTrace/> </providers> </encoder> </appender> <root level="INFO"> <appender-ref ref="JSON"/> </root> </configuration> --- apiVersion: v1 kind: Secret metadata: name: java-app-secrets type: Opaque data: database-url: base64EncodedValue api-key: base64EncodedValue --- apiVersion: serving.knative.dev/v1 kind: Service metadata: name: java-app-with-config spec: template: spec: containers: - image: myregistry/knative-java-app:1.0.0 env: - name: CONFIG_MAP_VALUE valueFrom: configMapKeyRef: name: java-app-config key: application.properties - name: SECRET_VALUE valueFrom: secretKeyRef: name: java-app-secrets key: api-key volumeMounts: - name: config-volume mountPath: /app/config volumes: - name: config-volume configMap: name: java-app-config
9. Monitoring and Observability
Custom Metrics with Micrometer
MetricsConfiguration.java
package com.example.knative.metrics;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Component
public class KnativeMetrics {
private final MeterRegistry registry;
private final AtomicLong coldStartCounter;
private final Timer requestTimer;
private final ConcurrentHashMap<String, AtomicLong> endpointCounters;
public KnativeMetrics(MeterRegistry registry) {
this.registry = registry;
this.coldStartCounter = registry.gauge("knative.cold_starts", new AtomicLong(0));
this.requestTimer = Timer.builder("knative.request.duration")
.description("HTTP request duration")
.register(registry);
this.endpointCounters = new ConcurrentHashMap<>();
}
public void recordColdStart() {
coldStartCounter.incrementAndGet();
registry.counter("knative.cold_start_events").increment();
}
public void recordRequest(String endpoint, long duration) {
requestTimer.record(duration, TimeUnit.MILLISECONDS);
// Track endpoint-specific metrics
endpointCounters
.computeIfAbsent(endpoint, k -> registry.counter("knative.requests", "endpoint", k))
.increment();
}
public void recordScaleEvent(int currentPods, int desiredPods) {
registry.gauge("knative.pods.current", currentPods);
registry.gauge("knative.pods.desired", desiredPods);
}
}
10. CI/CD Pipeline with Knative
GitHub Actions for Knative
.github/workflows/knative-deploy.yaml
name: Deploy to Knative
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package -DskipTests
- name: Run tests
run: mvn test
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
- name: Deploy to Knative
if: github.ref == 'refs/heads/main'
run: |
# Update Knative service with new image
kn service update java-knative-service \
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--env APP_VERSION=${{ github.sha }}
- name: Run smoke tests
if: github.ref == 'refs/heads/main'
run: |
# Wait for service to be ready
kn service describe java-knative-service --verbose
# Run smoke test
SERVICE_URL=$(kn service describe java-knative-service -o url)
curl -f $SERVICE_URL/api/health
Best Practices for Java on Knative
- Fast Startup - Optimize JVM startup with Class Data Sharing
- Graceful Shutdown - Handle SIGTERM properly
- Resource Efficiency - Set appropriate memory limits
- Health Checks - Implement comprehensive readiness/liveness probes
- Stateless Design - Don't rely on local storage
- Observability - Implement distributed tracing and metrics
Benefits for Java Applications
- Cost Savings - Scale to zero when not in use
- Automatic Scaling - Handle traffic spikes seamlessly
- Simplified Operations - Less infrastructure management
- Rapid Deployment - Quick iteration and deployment
- Event-Driven - Natural fit for event-driven architectures
Conclusion
Knative provides a powerful serverless platform for Java applications on Kubernetes, offering:
- Auto-scaling - From zero to many based on demand
- Simplified Deployment - Abstract away Kubernetes complexity
- Event-Driven Architecture - Built-in eventing capabilities
- Traffic Management - Advanced routing and canary deployments
- Cost Efficiency - Pay only for what you use
Java applications can leverage Knative to achieve true serverless benefits while running on any Kubernetes cluster, making it ideal for microservices, event processors, and API endpoints.
Next Steps: Explore Knative Eventing sources, implement custom metrics for auto-scaling, and integrate with service meshes for advanced traffic management.