As Java developers, we know the pain of managing OpenTelemetry agents across microservices. Should we bake them into Docker images? Mount them via ConfigMaps? Use init containers? Kubernetes v1.33's beta Image Volume feature offers a surprisingly elegant solution that keeps our application images clean while ensuring consistent observability.
The Java Developer's Observability Dilemma
Every Spring Boot microservice needs observability, but traditional approaches create friction:
# The old way - baking agents into images
FROM openjdk:17-jre
COPY opentelemetry-javaagent.jar /opt/
COPY myapp.jar /app/
ENTRYPOINT ["java", "-javaagent:/opt/opentelemetry-javaagent.jar", "-jar", "/app/myapp.jar"]
This approach has several downsides:
- Larger Images: Every service carries its own agent copy
- Version Management: Updating agents requires rebuilding all services
- Build Complexity: Developers must remember to include agents
- Testing Overhead: Different environments may use different agent versions
A Cleaner Approach with Image Volumes
The beta Image Volume feature lets us mount container images as volumes, enabling a clean separation between application code and instrumentation:
# Clean application deployment with external agent
volumeMounts:
- name: otel-agent
mountPath: /opt/opentelemetry
readOnly: true
volumes:
- name: otel-agent
image:
reference: localhost:5001/opentelemetry-javaagent:v2.15.0
Setting Up the Demo Environment
My [demo repository]({{ repository }}) shows exactly how this works with a complete Spring Boot example. Here's the setup process:
1. Prepare the Infrastructure
# Build custom Kubernetes environment with OCI Volume support
./01-build-custom-kind-image.sh
./02-kind-with-registry.sh
Since this is a beta feature that's disabled by default, we need Kubernetes v1.33 with specific containerd versions and feature gate enablement. The demo handles all the complexity for you.
2. Package the OpenTelemetry Agent
The magic happens in the [artifact creation script]({{ repository }}/blob/main/03-artifact-javaagent-upload.sh):
# Download and package the latest OpenTelemetry agent
curl -L "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.15.0/opentelemetry-javaagent.jar" -o opentelemetry-javaagent.jar
# Create OCI artifact and push to registry
./03-artifact-javaagent-upload.sh
This creates a proper OCI image containing just the agent JAR, with all the right metadata for reproducible builds.
3. Deploy Your Spring Boot Application
# Deploy with automatic agent injection
./04-deploy-spring-hello-world.sh
The [Spring Boot deployment]({{ repository }}/tree/main/spring-hello-world) shows how clean this becomes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-hello-world
spec:
template:
spec:
containers:
- name: spring-hello-world
image: openjdk:17-jre
command: ["/bin/sh"]
args: ["-c", "java -javaagent:/opt/opentelemetry/opentelemetry-javaagent.jar -jar /app/spring-hello-world.jar"]
volumeMounts:
- name: app-jar
mountPath: /app
- name: otel-agent
mountPath: /opt/opentelemetry
readOnly: true
volumes:
- name: otel-agent
image:
reference: localhost:5001/opentelemetry-javaagent:v2.15.0
Observability in Action
The demo includes the [Aspire Dashboard]({{ repository }}/tree/main/aspire-dashboard) for visualizing telemetry data:
# Deploy observability dashboard
./04a-deploy-aspire-dashboard.sh
Once deployed, you can see your Spring Boot metrics, traces, and logs flowing through OpenTelemetry without any code changes.
Benefits for Java Teams
Simplified Development Workflow
- Clean Dockerfiles: Focus on your application, not infrastructure concerns
- Faster Builds: Smaller images build and deploy faster
- Version Independence: Update agents without touching application code
Better Testing and Debugging
- Consistent Environments: Same agent version across dev, test, and prod
- Easy Toggling: Enable/disable instrumentation by changing volume mounts
- Reduced Variables: Fewer moving parts in your container images
Operational Excellence
- Zero-Downtime Updates: Update observability without service restarts
- Centralized Management: One place to manage agent versions across all services
- Better Security: Smaller attack surface in application images
Production Considerations for Java Teams
Performance Impact
OpenTelemetry agents are designed for production use, but consider:
- JVM startup time may increase slightly
- Memory overhead is typically 10-50MB depending on instrumentation
- CPU overhead is usually under 5% for most applications
Configuration Management
env:
- name: OTEL_SERVICE_NAME
value: "my-spring-service"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://jaeger:14250"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.version=1.0.0"
Keep your OpenTelemetry configuration in environment variables or ConfigMaps, separate from the agent itself.
Integration with Spring Boot Features
The auto-instrumentation works seamlessly with:
- Spring Boot Actuator metrics
- Spring Security traces
- Spring Data database instrumentation
- Spring Cloud distributed tracing
Future-Proofing Your Observability
Image Volumes graduated to beta in Kubernetes v1.33, representing the future of clean container architecture. While still disabled by default, this progression shows the feature's maturity. Java teams should:
- Experiment Now: Use the demo to understand the workflow
- Plan Migration: Identify services that would benefit from external agents
- Update Practices: Start designing new services with this pattern in mind
Try It Yourself
The [complete demo]({{ repository }}) includes:
- Working Spring Boot application with OpenTelemetry
- Automated setup scripts for local testing
- Observability dashboard for data visualization
- Cleanup scripts for easy teardown
git clone https://github.com/dol/k8s-oci-volume-source-demo
cd k8s-oci-volume-source-demo
# Full demo workflow
./01-build-custom-kind-image.sh
./02-kind-with-registry.sh
./03-artifact-javaagent-upload.sh
./04-deploy-spring-hello-world.sh
./04a-deploy-aspire-dashboard.sh
# Check your observability data!
This approach transforms Java observability from a deployment complexity into a simple operational concern. As this feature is now in beta in Kubernetes v1.33, it's the perfect time for Java teams to explore how it can simplify their microservices architecture.
Explore the complete working example at [{{ repository }}]({{ repository }}), including Spring Boot code, Kubernetes manifests, and step-by-step setup instructions.