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.