Serverless Java: Building FaaS Applications with Knative

Article

Knative is a Kubernetes-based platform that provides the building blocks for serverless applications and event-driven architectures. When combined with Java, it enables developers to build highly scalable, event-driven functions that automatically scale to zero when not in use, providing true serverless capabilities.


What is Knative?

Knative extends Kubernetes to provide a serverless platform with three core components:

  • Serving: Request-driven compute that can scale to zero
  • Eventing: Management and delivery of events
  • Build: Source-to-container build workflows

Key Benefits for Java FaaS:

  • Scale to Zero: No cost when functions are idle
  • Cold Start Optimization: Fast startup for Java functions
  • Event-Driven Architecture: Native event processing
  • Traffic Management: Blue-green deployments and canary releases
  • Kubernetes Native: Leverages existing Kubernetes ecosystem

Knative Setup and Installation

1. Install Knative Serving

# Install Knative Serving
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.11.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.11.0/serving-core.yaml
# Install networking layer (Kourier)
kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-v1.11.0/kourier.yaml
# Configure Knative to use Kourier
kubectl patch configmap/config-network \
--namespace knative-serving \
--type merge \
--patch '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'

2. Install Knative Eventing

# Install Knative Eventing
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.11.0/eventing-crds.yaml
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.11.0/eventing-core.yaml
# Install eventing channels and brokers
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.11.0/in-memory-channel.yaml
kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.11.0/mt-channel-broker.yaml

3. Verify Installation

kubectl get pods -n knative-serving
kubectl get pods -n knative-eventing

Building Java Functions for Knative

1. Quarkus Function Project Setup

<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>knative-java-function</artifactId>
<version>1.0.0</version>
<properties>
<quarkus.version>3.6.0</quarkus.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
<surefire-plugin.version>3.1.2</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>
<!-- Quarkus Core -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<!-- RESTEasy Reactive -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<!-- Jackson for JSON -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<!-- Knative Serving -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-jib</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>
<!-- Funqy HTTP (for FaaS) -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-funqy-http</artifactId>
</dependency>
<!-- Knative Eventing -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-knative</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>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</xml>

2. Spring Cloud Function Project Setup

<!-- pom.xml for Spring Cloud Function -->
<project>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-function-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

HTTP Functions with Quarkus

1. Simple HTTP Function

package com.example.function;
import io.quarkus.funqy.Funq;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GreetingFunction {
private static final Logger LOG = LoggerFactory.getLogger(GreetingFunction.class);
@Inject
GreetingService greetingService;
@Funq
public GreetingResponse greet(GreetingRequest request) {
LOG.info("Processing greeting request for: {}", request.name());
String message = greetingService.createGreeting(
request.name(), 
request.language()
);
return new GreetingResponse(
message,
System.currentTimeMillis(),
request.name()
);
}
// Request and Response DTOs
public record GreetingRequest(String name, String language) {}
public record GreetingResponse(String message, long timestamp, String recipient) {}
}

2. Supporting Service

package com.example.function;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Map;
@ApplicationScoped
public class GreetingService {
private final Map<String, String> greetings = Map.of(
"en", "Hello",
"es", "Hola",
"fr", "Bonjour",
"de", "Hallo"
);
public String createGreeting(String name, String language) {
String greeting = greetings.getOrDefault(language, "Hello");
return String.format("%s, %s!", greeting, name);
}
}

3. Configuration

# application.properties
quarkus.container-image.build=true
quarkus.container-image.group=my-registry
quarkus.container-image.name=greeting-function
quarkus.container-image.tag=latest
# Knative Configuration
quarkus.kubernetes.deployment-target=knative
quarkus.kubernetes.namespace=knative-functions
# Knative Serving Configuration
quarkus.knative.min-scale=0
quarkus.knative.max-scale=10
quarkus.knative.scale-to-zero-grace-period=30s
quarkus.knative.container-concurrency=10
# Health Checks
quarkus.kubernetes.liveness-probe.http-action-path=/q/health/live
quarkus.kubernetes.readiness-probe.http-action-path=/q/health/ready
# Resource Limits
quarkus.kubernetes.resources.requests.memory=128Mi
quarkus.kubernetes.resources.requests.cpu=100m
quarkus.kubernetes.resources.limits.memory=256Mi
quarkus.kubernetes.resources.limits.cpu=500m
# JVM Optimization for Serverless
quarkus.kubernetes.env.vars.JAVA_OPTS=-Xmx128m -Xms64m -XX:MaxRAM=256m
quarkus.kubernetes.env.vars.QUARKUS_JVM_CHECK=false

Event-Driven Functions

1. CloudEvent Processing Function

package com.example.function;
import io.quarkus.funqy.Funq;
import io.quarkus.funqy.knative.events.CloudEvent;
import io.quarkus.funqy.knative.events.CloudEventMapping;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Instant;
public class EventProcessorFunction {
private static final Logger LOG = LoggerFactory.getLogger(EventProcessorFunction.class);
@Inject
EventProcessingService processingService;
@Funq
@CloudEventMapping(trigger = "order.created")
public CloudEvent<OrderProcessedEvent> handleOrderCreated(CloudEvent<OrderCreatedEvent> event) {
LOG.info("Processing order created event: {}", event.id());
OrderCreatedEvent orderEvent = event.data();
ProcessingResult result = processingService.processOrder(orderEvent);
OrderProcessedEvent processedEvent = new OrderProcessedEvent(
result.orderId(),
result.status(),
Instant.now(),
result.message()
);
return CloudEventBuilder
.create()
.type("order.processed")
.source("order-processor")
.id(event.id() + "-processed")
.data(processedEvent)
.build();
}
@Funq
@CloudEventMapping(trigger = "user.registered")
public void handleUserRegistered(CloudEvent<UserRegisteredEvent> event) {
LOG.info("Processing user registration: {}", event.data().email());
processingService.sendWelcomeEmail(event.data());
}
@Funq
@CloudEventMapping(trigger = "payment.processed")
public CloudEvent<OrderShippedEvent> handlePaymentProcessed(CloudEvent<PaymentProcessedEvent> event) {
PaymentProcessedEvent payment = event.data();
if ("COMPLETED".equals(payment.status())) {
OrderShippedEvent shippedEvent = processingService.shipOrder(payment.orderId());
return CloudEventBuilder
.create()
.type("order.shipped")
.source("shipping-service")
.data(shippedEvent)
.build();
}
return null;
}
// Event DTOs
public record OrderCreatedEvent(String orderId, String customerId, double amount, 
Instant createdAt, String items) {}
public record OrderProcessedEvent(String orderId, String status, Instant processedAt, 
String message) {}
public record UserRegisteredEvent(String userId, String email, String name) {}
public record PaymentProcessedEvent(String paymentId, String orderId, String status, 
double amount) {}
public record OrderShippedEvent(String orderId, String trackingNumber, Instant shippedAt) {}
public record ProcessingResult(String orderId, String status, String message) {}
}

2. Event Processing Service

package com.example.function;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.Instant;
import java.util.UUID;
@ApplicationScoped
public class EventProcessingService {
public ProcessingResult processOrder(OrderCreatedEvent order) {
// Simulate order processing
try {
Thread.sleep(100); // Processing time
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String status = order.amount() > 1000 ? "PENDING_REVIEW" : "APPROVED";
String message = String.format("Order %s processed with status: %s", order.orderId(), status);
return new ProcessingResult(order.orderId(), status, message);
}
public void sendWelcomeEmail(UserRegisteredEvent user) {
// Simulate email sending
System.out.printf("Sending welcome email to: %s%n", user.email());
}
public OrderShippedEvent shipOrder(String orderId) {
String trackingNumber = "TRK-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
return new OrderShippedEvent(orderId, trackingNumber, Instant.now());
}
}

Spring Cloud Functions

1. Spring Cloud Function Configuration

package com.example.function;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.Message;
import reactor.core.publisher.Flux;
import java.util.function.Function;
@SpringBootApplication
public class KnativeFunctionApplication {
public static void main(String[] args) {
SpringApplication.run(KnativeFunctionApplication.class, args);
}
@Bean
public Function<String, String> uppercase() {
return String::toUpperCase;
}
@Bean
public Function<Message<String>, String> greeting() {
return message -> {
String name = message.getPayload();
String language = message.getHeaders().getOrDefault("language", "en").toString();
return switch (language) {
case "es" -> "ÂĄHola, " + name + "!";
case "fr" -> "Bonjour, " + name + "!";
default -> "Hello, " + name + "!";
};
};
}
@Bean
public Function<Flux<String>, Flux<String>> streamProcessor() {
return flux -> flux
.map(String::toUpperCase)
.map(str -> "Processed: " + str);
}
@Bean
public Function<Order, OrderResult> orderProcessor() {
return order -> {
// Process order
String status = order.amount() > 100 ? "APPROVED" : "REJECTED";
String message = String.format("Order %s %s", order.id(), status.toLowerCase());
return new OrderResult(order.id(), status, message, System.currentTimeMillis());
};
}
public record Order(String id, String customerId, double amount, String items) {}
public record OrderResult(String orderId, String status, String message, long timestamp) {}
}

2. Spring Cloud Function Configuration

# application.properties
spring.cloud.function.definition=uppercase;greeting;streamProcessor;orderProcessor
spring.cloud.function.web.export.enabled=true
# Knative Properties
knative.min-scale=0
knative.max-scale=5
knative.container-concurrency=1
# Server Properties
server.port=8080
# Management
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always

Knative Serving Manifests

1. Knative Service Manifest

# knative-service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: greeting-function
namespace: knative-functions
labels:
app: greeting-function
version: "1.0.0"
spec:
template:
metadata:
annotations:
# Scaling configuration
autoscaling.knative.dev/min-scale: "0"
autoscaling.knative.dev/max-scale: "10"
autoscaling.knative.dev/target: "100"
autoscaling.knative.dev/window: "60s"
# Knative specific
autoscaling.knative.dev/scale-to-zero-grace-period: "30s"
autoscaling.knative.dev/scale-to-zero-pod-retention-period: "0s"
# Resource configuration
container.apparmor.security.beta.kubernetes.io/greeting-function: runtime/default
spec:
containerConcurrency: 10
timeoutSeconds: 300
containers:
- image: my-registry/greeting-function:latest
name: greeting-function
ports:
- containerPort: 8080
env:
- name: JAVA_OPTS
value: "-Xmx128m -Xms64m -XX:MaxRAM=256m -XX:+UseG1GC"
- name: QUARKUS_PROFILE
value: "knative"
- name: K_SERVICE
value: "greeting-function"
- name: K_CONFIGURATION
value: "greeting-function"
- name: K_REVISION
value: "greeting-function-00001"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /q/health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /q/health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL

2. Event Processor Knative Service

# event-processor-service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: event-processor
namespace: knative-functions
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/min-scale: "0"
autoscaling.knative.dev/max-scale: "20"
autoscaling.knative.dev/target: "50"
spec:
containerConcurrency: 5
containers:
- image: my-registry/event-processor:latest
name: event-processor
env:
- name: JAVA_OPTS
value: "-Xmx256m -Xms128m -XX:+UseG1GC"
- name: EVENT_BROKER_URL
value: "http://broker-ingress.knative-eventing.svc.cluster.local/knative-functions/default"
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "1000m"

Knative Eventing Configuration

1. Broker and Triggers

# eventing-setup.yaml
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
name: default
namespace: knative-functions
labels:
eventing.knative.dev/broker: default
spec:
config:
apiVersion: v1
kind: ConfigMap
name: config-br-default-channel
namespace: knative-eventing
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: order-created-trigger
namespace: knative-functions
spec:
broker: default
filter:
attributes:
type: order.created
source: order-service
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-processor
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: user-registered-trigger
namespace: knative-functions
spec:
broker: default
filter:
attributes:
type: user.registered
source: auth-service
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-processor
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: payment-processed-trigger
namespace: knative-functions
spec:
broker: default
filter:
attributes:
type: payment.processed
source: payment-service
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-processor

2. Event Source (Kafka)

# kafka-source.yaml
apiVersion: sources.knative.dev/v1
kind: KafkaSource
metadata:
name: kafka-order-source
namespace: knative-functions
spec:
consumerGroup: knative-order-consumer
bootstrapServers:
- my-kafka-bootstrap.kafka:9092
topics:
- orders
- payments
- users
sink:
ref:
apiVersion: eventing.knative.dev/v1
kind: Broker
name: default

Advanced Function Patterns

1. Stateful Function with Redis

package com.example.function;
import io.quarkus.funqy.Funq;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.value.ValueCommands;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Instant;
public class StatefulFunction {
private static final Logger LOG = LoggerFactory.getLogger(StatefulFunction.class);
@Inject
RedisDataSource redis;
private ValueCommands<String, String> valueCommands;
@PostConstruct
void init() {
valueCommands = redis.value(String.class);
}
@Funq
public SessionResponse createSession(SessionRequest request) {
String sessionId = generateSessionId();
Instant expiresAt = Instant.now().plusSeconds(request.ttlSeconds());
Session session = new Session(
sessionId,
request.userId(),
Instant.now(),
expiresAt,
request.data()
);
// Store session in Redis
valueCommands.set(
"session:" + sessionId, 
serializeSession(session),
request.ttlSeconds()
);
LOG.info("Created session: {} for user: {}", sessionId, request.userId());
return new SessionResponse(sessionId, expiresAt, "CREATED");
}
@Funq
public SessionResponse getSession(String sessionId) {
String sessionData = valueCommands.get("session:" + sessionId);
if (sessionData == null) {
return new SessionResponse(sessionId, null, "NOT_FOUND");
}
Session session = deserializeSession(sessionData);
return new SessionResponse(
sessionId,
session.expiresAt(),
"FOUND"
);
}
@Funq
public SessionResponse extendSession(ExtendSessionRequest request) {
String key = "session:" + request.sessionId();
String sessionData = valueCommands.get(key);
if (sessionData == null) {
return new SessionResponse(request.sessionId(), null, "NOT_FOUND");
}
Session session = deserializeSession(sessionData);
Instant newExpiresAt = session.expiresAt().plusSeconds(request.extendBySeconds());
Session updatedSession = new Session(
session.sessionId(),
session.userId(),
session.createdAt(),
newExpiresAt,
session.data()
);
// Update with new TTL
long newTtl = request.extendBySeconds() + 
(session.expiresAt().getEpochSecond() - Instant.now().getEpochSecond());
valueCommands.set(key, serializeSession(updatedSession), newTtl);
return new SessionResponse(request.sessionId(), newExpiresAt, "EXTENDED");
}
private String generateSessionId() {
return "sess_" + Instant.now().toEpochMilli() + "_" + 
Math.abs((long) (Math.random() * 10000));
}
private String serializeSession(Session session) {
// Simple serialization - use JSON in production
return String.join("|",
session.sessionId(),
session.userId(),
session.createdAt().toString(),
session.expiresAt().toString(),
session.data()
);
}
private Session deserializeSession(String data) {
String[] parts = data.split("\\|");
return new Session(
parts[0],
parts[1],
Instant.parse(parts[2]),
Instant.parse(parts[3]),
parts[4]
);
}
// DTOs
public record SessionRequest(String userId, int ttlSeconds, String data) {}
public record SessionResponse(String sessionId, Instant expiresAt, String status) {}
public record ExtendSessionRequest(String sessionId, int extendBySeconds) {}
public record Session(String sessionId, String userId, Instant createdAt, 
Instant expiresAt, String data) {}
}

2. Batch Processing Function

package com.example.function;
import io.quarkus.funqy.Funq;
import io.quarkus.funqy.knative.events.CloudEvent;
import io.quarkus.funqy.knative.events.CloudEventMapping;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.stream.Collectors;
public class BatchProcessorFunction {
private static final Logger LOG = LoggerFactory.getLogger(BatchProcessorFunction.class);
@Inject
BatchProcessingService processingService;
@Funq
@CloudEventMapping(trigger = "batch.job.created")
public CloudEvent<BatchJobResult> processBatchJob(CloudEvent<BatchJobRequest> event) {
LOG.info("Processing batch job: {}", event.id());
BatchJobRequest request = event.data();
List<ProcessedItem> processedItems = request.items().stream()
.map(processingService::processItem)
.collect(Collectors.toList());
BatchJobResult result = new BatchJobResult(
request.jobId(),
processedItems.size(),
processedItems.stream().filter(ProcessedItem::success).count(),
System.currentTimeMillis()
);
return CloudEventBuilder
.create()
.type("batch.job.completed")
.source("batch-processor")
.data(result)
.build();
}
@Funq
public BatchProcessResponse processBatch(BatchProcessRequest request) {
LOG.info("Processing batch of {} items", request.items().size());
long startTime = System.currentTimeMillis();
List<ProcessedItem> results = request.items().parallelStream()
.map(processingService::processItem)
.collect(Collectors.toList());
long processingTime = System.currentTimeMillis() - startTime;
return new BatchProcessResponse(
results,
processingTime,
results.stream().filter(ProcessedItem::success).count()
);
}
// DTOs
public record BatchJobRequest(String jobId, List<String> items) {}
public record BatchJobResult(String jobId, int totalItems, long successfulItems, long completedAt) {}
public record BatchProcessRequest(List<String> items) {}
public record BatchProcessResponse(List<ProcessedItem> results, long processingTime, long successCount) {}
public record ProcessedItem(String item, boolean success, String result, String error) {}
}

Deployment and Management

1. Build and Deploy Script

#!/bin/bash
# deploy-function.sh
set -e
FUNCTION_NAME=${1:-greeting-function}
IMAGE_TAG=${2:-latest}
ENVIRONMENT=${3:-dev}
echo "Deploying function: $FUNCTION_NAME"
echo "Image tag: $IMAGE_TAG"
echo "Environment: $ENVIRONMENT"
# Build the function
mvn clean package -DskipTests
# Build container image
docker build -t my-registry/$FUNCTION_NAME:$IMAGE_TAG .
# Push to registry
docker push my-registry/$FUNCTION_NAME:$IMAGE_TAG
# Deploy to Knative
kubectl apply -f knative/$ENVIRONMENT/$FUNCTION_NAME-service.yaml
# Wait for deployment
kubectl wait --for=condition=ready ksvc $FUNCTION_NAME --timeout=300s
# Get the URL
FUNCTION_URL=$(kubectl get ksvc $FUNCTION_NAME -o jsonpath='{.status.url}')
echo "Function deployed: $FUNCTION_URL"
# Run smoke test
curl -X POST $FUNCTION_URL \
-H "Content-Type: application/json" \
-d '{"name": "Test User", "language": "en"}' \
-w "\n"

2. CI/CD Pipeline

# .github/workflows/knative-function.yml
name: Deploy Knative Function
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Run tests
run: mvn clean test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Build function
run: mvn clean package -DskipTests
- name: Build container image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Push image
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to Knative
run: |
kubectl set image ksvc/greeting-function greeting-function=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
kubectl rollout status ksvc/greeting-function
env:
KUBECONFIG: ${{ secrets.KUBECONFIG }}

Best Practices

1. Performance Optimization

  • Use GraalVM native image for faster cold starts
  • Keep function images small
  • Optimize JVM settings for serverless
  • Use connection pooling for external services

2. Monitoring and Observability

  • Implement comprehensive health checks
  • Use distributed tracing
  • Monitor scale-to-zero behavior
  • Track function execution metrics

3. Error Handling

  • Implement retry mechanisms
  • Use dead letter queues for failed events
  • Log errors appropriately
  • Implement circuit breakers for external calls

Conclusion

Knative with Java provides a powerful platform for building serverless functions:

  • True Serverless: Scale to zero when not in use
  • Event-Driven: Native support for CloudEvents
  • Kubernetes Native: Leverages existing Kubernetes ecosystem
  • Fast Startup: Optimized for Java cold starts
  • Enterprise Ready: Comprehensive monitoring and management

By combining Knative's serverless capabilities with Java's robust ecosystem, developers can build highly scalable, cost-effective functions that automatically respond to demand while maintaining the reliability and features expected from enterprise Java applications.

Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/

OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/

OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/

Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.

https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics

Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2

Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide

Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2

Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide

Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.

https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server

Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.

Leave a Reply

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


Macro Nepal Helper