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.
Step-by-step:
- Enablement - A user enables Java observability via the ScaleOps UI or Helm (
java.observability.enabled=true). - Pod creation - A new Java pod is created (Deployment, StatefulSet, or Job rollout).
- Webhook mutates pod spec - The ScaleOps admissions webhook detects the Java workload and patches the pod spec: adds the
scaleops-jmx-initinit container, anemptyDirvolume, and theLD_PRELOADenvironment variable. - Init container runs - The init container executes
/run_jmx, copyingpreload.soandagent.jarto/scaleops-libs/. - App container starts - The app container starts with
LD_PRELOADset. The JMX exporter collects JVM metrics on port7149. - Metrics presented - Java metrics are presented in the ScaleOps product for visibility and optimization.
- (If automation enabled) - ScaleOps delivers optimized JVM flags via the
SCALEOPS_OPTIONSenv 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, andscaleops.sh/admission: "true"as an annotation to mark the pod as mutated. - Init Container -
scaleops-jmx-initis prepended toinitContainers. It runs/run_jmxto copypreload.soandagent.jarinto a shared volume at/scaleops-libs/. - Environment Variables -
LD_PRELOADis set on the app container pointing to/scaleops-libs/preload.so, which attaches the JMX agent at JVM startup. IfLD_PRELOADalready exists, the ScaleOps path is appended (colon-separated). - Container Port - Port
7149(namedscaleops-jmx) is added to the app container for metrics scraping. - Shared Volume - An
emptyDirvolume namedscaleops-jmx-initis 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=trueThis 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.