Skip to main content

Profile a Java application running in an AKS cluster

This page describes how to profile a java application which is deployed in one of our AKS clusters.

Prerequisites

  1. Your user will need to have permissions to run kubectl exec and to ssh to a node. (Subscription contributor role works)
  2. Install kubectl node-shell which in turn requires krew
  3. Something to open .jfr files with like Java Mission Control

Setup

You’ll need the following bits of information during this exercise: 1. namespace (hopefully you just know this or browse kubectl get namespaces) 2. pod name (find it in kubectl get pods -n $namespace) 3. container name (kubectl get pods -n $namespace $podName -o=jsonpath='{.spec.containers[0].name}') 4. node name (kubectl get pods -n $namespace $podName -o=jsonpath='{.spec.nodeName}')

Example

namespace=rpe
podName=rpe-send-letter-service-java-7fc6fffdf9-qvfd6
containerName=$(kubectl get pods -n $namespace $podName -o=jsonpath='{.spec.containers[0].name}')
nodeName=$(kubectl get pods -n $namespace $podName -o=jsonpath='{.spec.nodeName}')

Running Java Flight Recorder (JFR)

After completing the setup above you can run kubectl exec $podName -n $namespace -- jattach 1 jcmd "JFR.start name=profile1" The output will look something like this

Connected to remote JVM
Response code = 0
Started recording 1. No limit specified, using maxsize=250MB as default.
Use jcmd 1 JFR.dump name=1 filename=FILEPATH to copy recording data to file.

At this point you can run any tests you want to capture any data in the profile report.

Once complete, you can dump the data to a file on the container: kubectl exec $podName -n $namespace -- jattach 1 jcmd JFR.dump If you don’t specify a name in the command, a time-stamped filename will be generated. The output will look something like this

Connected to remote JVM
Response code = 0
Dumped recording, 4.5 MB written to:
/opt/app/hotspot-pid-1-2022_01_20_14_32_49.jfr

Make a note of the filename for retrieving later

Run kubectl exec $podName -n $namespace -- jattach 1 jcmd "JFR.stop name=profile1" to stop the recording. You can use kubectl exec $podName -n $namespace -- jattach 1 jcmd JFR.check to list all in-flight recordings

Downloading the file

As our images don’t have a shell installed or tar (can’t use kubectl cp) it’s a little bit more involved to get the JFR report.

To figure out where the file is, you need to use nsenter to get a shell onto the node and find the mount point for the specific container you need.

kubectl node-shell $nodeName -n admin
spawning "nsenter-x8uq64" on "aks-linux-81166528-vmss00005t"
If you don't see a command prompt, try pressing enter.
root@aks-linux-81166528-vmss00005T:/#

Make a note of the name of the spawned instance (in this case nsenter-x8uq64).

At this point you’ll need your variables from earlier

namespace=rpe
containerName=rpe-send-letter-service-java
pod=rpe-send-letter-service-java-6fb55dd776-87b6f

You’ll also need some additional information out of containerd using crictl.

podId=$(crictl pods --namespace $namespace --name $pod --state Ready --quiet)
containerId=$(crictl ps -p $podId --no-trunc --label io.kubernetes.container.name=$containerName -q)
targetPid=$(crictl inspect $containerId | jq .info.pid)

Now we can use nsenter to create us a shell at the mount point for the container

nsenter --target $targetPid --uts --net --pid unshare --mount-proc sh -c "cd /run/containerd/io.containerd.runtime.v2.task/k8s.io/$containerId/rootfs/opt/app && exec bash"
groups: cannot find name for group ID 11
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

When we list the files you can see the hotspot JFR file which was dumped by jattach earlier.

root@rpe-send-letter-service-java-6fb55dd776-87b6f:/run/containerd/io.containerd.runtime.v2.task/k8s.io/9704b2c0b9b559001eb23156a06e40d25e10ceeffc4849bb51b1688025da6f61/rootfs/opt/app# ls
AI-Agent.xml                         hotspot-pid-1-2022_01_20_14_32_49.jfr
applicationinsights-agent-2.5.1.jar  send-letter-service.jar

To download this file we need to go back to a terminal on our local machine and run kubectl cp to copy from the spawned instance (nsenter-x8uq64) using the path we just discovered above (/run/containerd/io.containerd.runtime.v2.task/k8s.io/9704b2c0b9b559001eb23156a06e40d25e10ceeffc4849bb51b1688025da6f61/rootfs/opt/app) appended by the file name hotspot-pid-1-2022_01_20_14_32_49.jfr like so:

kubectl cp admin/nsenter-x8uq64:/run/containerd/io.containerd.runtime.v2.task/k8s.io/9704b2c0b9b559001eb23156a06e40d25e10ceeffc4849bb51b1688025da6f61/rootfs/opt/app/hotspot-pid-1-2022_01_20_14_32_49.jfr ./hotspot-pid-1-2022_01_20_14_32_49.jfr
tar: Removing leading `/' from member names

You can now open this file locally using Java Mission Control and terminate your connection to the nsenter pod.

This page was last reviewed on 15 August 2024. It needs to be reviewed again on 15 February 2025 by the page owner platops-build-notices .
This page was set to be reviewed before 15 February 2025 by the page owner platops-build-notices. This might mean the content is out of date.