Skip to Content

JMX Injection: How It Works

ScaleOps uses a Kubernetes mutating admission webhook to inject a JMX exporter init container into Java pods at creation time. This enables deep JVM metrics collection - heap usage, garbage collection, thread counts - without any code changes or manual configuration in your workloads.

Injection Flow

When Java observability is enabled (via UI or Helm), the ScaleOps admissions controller intercepts pod creation events and injects the JMX monitoring components. The following diagram illustrates the end-to-end flow.

JMX Injection Flow

Step-by-step:

  1. Enablement - A user enables Java observability via the ScaleOps UI or Helm (java.observability.enabled=true).
  2. Pod creation - A new Java pod is created (Deployment, StatefulSet, or Job rollout).
  3. Webhook mutates pod spec - The ScaleOps admissions webhook detects the Java workload and patches the pod spec: adds the scaleops-jmx-init init container, an emptyDir volume, and the LD_PRELOAD environment variable.
  4. Init container runs - The init container executes /run_jmx, copying preload.so and agent.jar to /scaleops-libs/.
  5. App container starts - The app container starts with LD_PRELOAD set. The JMX exporter collects JVM metrics on port 7149.
  6. Metrics presented - Java metrics are presented in the ScaleOps product for visibility and optimization.
  7. (If automation enabled) - ScaleOps delivers optimized JVM flags via the SCALEOPS_OPTIONS env var on the next pod rollout.
⚠️

If your container image already relies on LD_PRELOAD, make sure it is defined in the pod spec (Deployment/StatefulSet manifest), not baked into the image. When LD_PRELOAD is set in the pod spec, ScaleOps appends its path (colon-separated) to preserve your existing value. If it is only set inside the image (e.g. via ENV in the Dockerfile), ScaleOps will override it.

What Gets Added to Your Pods

The webhook modifies the pod spec at admission time. No changes are made to your Deployment or StatefulSet manifests - the mutation happens only on the pod object.

Below is a simplified view of the fields the webhook adds to a pod:

metadata: labels: scaleops.sh/jmx-injector: "true" # service discovery label annotations: scaleops.sh/admission: "true" # marks pod as mutated spec: initContainers: - name: scaleops-jmx-init # runs before app containers image: registry.scaleops.com/scaleops-jmx-injector:<tag> command: ["/run_jmx"] # copies preload.so + agent.jar resources: requests: memory: "10Mi" cpu: "10m" volumeMounts: - name: scaleops-jmx-init mountPath: /scaleops-libs/ # shared volume for agent files containers: - name: my-java-app env: - name: LD_PRELOAD # attaches JMX agent at JVM startup value: /scaleops-libs/preload.so ports: - name: scaleops-jmx containerPort: 7149 # JMX exporter metrics port volumeMounts: - name: scaleops-jmx-init mountPath: /scaleops-libs/ # reads agent files from init container volumes: - name: scaleops-jmx-init emptyDir: {} # ephemeral volume for agent transfer
  • Labels & Annotations - scaleops.sh/jmx-injector: "true" is added as a label for service discovery, and scaleops.sh/admission: "true" as an annotation to mark the pod as mutated.
  • Init Container - scaleops-jmx-init is prepended to initContainers. It runs /run_jmx to copy preload.so and agent.jar into a shared volume at /scaleops-libs/.
  • Environment Variables - LD_PRELOAD is set on the app container pointing to /scaleops-libs/preload.so, which attaches the JMX agent at JVM startup. If LD_PRELOAD already exists, the ScaleOps path is appended (colon-separated).
  • Container Port - Port 7149 (named scaleops-jmx) is added to the app container for metrics scraping.
  • Shared Volume - An emptyDir volume named scaleops-jmx-init is mounted in both the init container and the app container at /scaleops-libs/.

Advanced Configuration

Network Policy

A NetworkPolicy named scaleops-jmx-injector is created in the workload namespace only if the namespace already has existing Network Policies that would block ScaleOps from scraping the JMX port. This policy allows the ScaleOps recommender to reach port 7149 for metrics collection. Creation is controlled by jmxInjector.networkPolicyCreation.enabled (default: true).

Private Image Registry

If you use a Private Image Registry and credentials are configured via imagePullSecrets, add the following optional Helm flag:

--set jmxInjector.imagePullSecretCreation.enabled=true

This creates an imagePullSecret named scaleops-jmx-injector-config in each workload namespace so the init container image can be pulled.

Helm Reference

For the full list of jmxInjector Helm values, see the Helm Configuration Reference - jmxInjector.