ARTIFACTORY: How to capture thread dumps for different services of Artifactory in a Kubernetes cluster
Introduction
When a microservice (such as Artifactory, Access, Topology or JFConfig) fails to start or experiences performance degradation, Thread Dumps are essential for identifying deadlocks, resource contention, or hung processes. In a Kubernetes environment, capturing these requires specific steps to target the correct container within the Artifactory pod.
For other use cases on capturing thread dumps, please refer to the following:
How to Collect Thread Dumps Before an Artifactory Pod Crashes or is Restarted - Capturing thread dumps automatically before a pod crashes.
How to collect Artifactory thread dumps - Capturing thread dumps on a VM setup.
What to do if I would like to capture Goroutine dumps for Golang written services (Metadata, Router, Event, Observability, etc)?
Please refer to our detailed documentation - How to Collect Goroutine Dumps and Runtime Profiles in Artifactory
Goal
This article provides a comprehensive guide to capturing a series of thread dumps from Java-based services (Artifactory, Access, JFConfig, and Topology) in a Kubernetes environment. It includes both manual commands and an automated script, offering a step-by-step approach to make this process as seamless as possible..
Prerequisites
- kubectl access to the cluster where Artifactory is running.
- Permissions to exec into pods in the Artifactory namespace.
- Basic familiarity with the Linux command line.
Method 1: Manual Capture
Use this method if you prefer to take thread dumps manually, without utilizing any scripts.
1. Identify your Pod and Container
List your pods to ensure you have the correct name:
kubectl get pods -n <namespace>
2. Access the Container
Execute into the specific service container (e.g., artifactory, access, jfconfig, or topology):
kubectl exec -it <Artifactory-pod> -n <Artifactory-namespace> -c <container-name> -- /bin/bash #Example: kubectl exec -it artifactory-0 -n artifactory -c access -- /bin/bash
3. Locate JCMD and the Java PID
Navigate to the Java binary directory and find the Process ID (PID) of the service:
cd /opt/jfrog/artifactory/app/third-party/java/bin
------------------------------------------------------
# Find the PID
ps -ef | grep '<container-name>/tomcat' | grep -v grep | awk '{print $2}'
#Example:
ps -ef | grep 'access/tomcat' | grep -v grep | awk '{print $2}'
4. Create a Log Directory and Capture
Create a dedicated folder for the dumps and run the command:
mkdir -p /opt/jfrog/artifactory/var/log/${CONTAINER_NAME}_thread_dumps
#Example:
mkdir -p /opt/jfrog/artifactory/var/log/access_thread_dumps
------------------------------------------------------
./jcmd <PID> Thread.print > "/opt/jfrog/artifactory/var/log/${CONTAINER_NAME}_thread_dumps/${CONTAINER_NAME}.$(date +%Y%m%d%H%M%S).td"
#Example:
./jcmd 24 Thread.print > "/opt/jfrog/artifactory/var/log/access_thread_dumps/access.$(date +%Y%m%d%H%M%S).td"
5. Zip the thread dumps, remove them and copy outside of the containertar -czf /opt/jfrog/artifactory/var/log/thread_dumps_<CONTAINER_NAME>.tar.gz -C /opt/jfrog/artifactory/var/log/<CONTAINER_NAME>_thread_dumps . #Example: tar -czf /opt/jfrog/artifactory/var/log/thread_dumps_access.tar.gz -C /opt/jfrog/artifactory/var/log/access_thread_dumps . ------------------------------------------------------ #Remove the thread dumps: rm /opt/jfrog/artifactory/var/log/<CONTAINER_NAME>_thread_dumps/*.td ------------------------------------------------------ #Copy outside of the container: kubectl cp <namespace>/<pod-name>:/opt/jfrog/artifactory/var/log/thread_dumps_<CONTAINER_NAME>.tar.gz ./thread_dumps_<CONTAINER_NAME>.tar.gz -c <container-name> #Example: kubectl cp artifactory/artifactory-0:/opt/jfrog/artifactory/var/log/thread_dumps_access.tar.gz ./thread_dumps_access.tar.gz -c access
Note: A single dump is rarely enough. It is standard practice to capture 10 dumps with a 5-second interval to see changes in thread states over time.
Method 2: Automated Capture (Recommended)
This script automates the PID discovery, directory creation, and the 10-dump loop.
1. Create the Script
Create a file named take_thread_dumps.sh on your local machine that is able to execute inside your kubernetes cluster:
#!/bin/bash
# --- Configuration ---
POD_NAME="artifactory-0" #Change to the name of your pod.
NAMESPACE="artifactory" #Change to the name of your namespace.
echo "----------------------------------------------------------"
echo " JFrog Artifactory Thread Dump Collector (K8s)"
echo "----------------------------------------------------------"
read -p "Enter container name (artifactory, access, topology, jfconfig): " CONTAINER_NAME
# Paths inside the JFrog containers
DUMP_DIR="/opt/jfrog/artifactory/var/log/${CONTAINER_NAME}_thread_dumps"
JCMD_PATH="/opt/jfrog/artifactory/app/third-party/java/bin/jcmd"
# 1. Fetch PID and Setup Directory
echo "Connecting to $CONTAINER_NAME..."
PID=$(kubectl exec -n "${NAMESPACE}" "${POD_NAME}" -c "${CONTAINER_NAME}" -- \
bash -c "mkdir -p ${DUMP_DIR} && ps -ef | grep '${CONTAINER_NAME}/tomcat' | grep -v grep | awk '{print \$2}'")
if [ -z "$PID" ]; then
echo "Error: Could not find Java PID for $CONTAINER_NAME."
exit 1
fi
echo "Target PID Found: $PID"
# 2. Collect Preferences
read -p "Number of dumps? [default 10]: " DUMP_COUNT
DUMP_COUNT=${DUMP_COUNT:-10}
read -p "Interval (seconds)? [default 5]: " INTERVAL
INTERVAL=${INTERVAL:-5}
# 3. Execution Loop
for i in $(seq 1 "$DUMP_COUNT"); do
TS=$(date +%Y%m%d%H%M%S)
FILE_NAME="${CONTAINER_NAME}.${TS}.td"
echo "[$i/$DUMP_COUNT] Capturing thread dump..."
kubectl exec -n "${NAMESPACE}" "${POD_NAME}" -c "${CONTAINER_NAME}" -- \
bash -c "${JCMD_PATH} ${PID} Thread.print > ${DUMP_DIR}/${FILE_NAME}"
[[ $i -lt $DUMP_COUNT ]] && sleep "$INTERVAL"
done
# 4. Compression Phase
echo "----------------------------------------------------------"
echo "Compressing thread dumps..."
ARCHIVE_NAME="thread_dumps_${CONTAINER_NAME}_$(date +%Y%m%d%H%M%S).tar.gz"
kubectl exec -n "${NAMESPACE}" "${POD_NAME}" -c "${CONTAINER_NAME}" -- \
bash -c "cd ${DUMP_DIR} && tar -czf ../${ARCHIVE_NAME} *.td && rm *.td"
echo "Done!"
echo "Archive created at: /opt/jfrog/artifactory/var/log/${ARCHIVE_NAME}"
echo "----------------------------------------------------------"
echo "To download the file to your current machine, run:"
echo "kubectl cp ${NAMESPACE}/${POD_NAME}:/opt/jfrog/artifactory/var/log/${ARCHIVE_NAME} ./${ARCHIVE_NAME} -c ${CONTAINER_NAME}"
2. Run the Script
chmod +x take_thread_dumps.sh ./take_thread_dumps.sh
Next Steps: Retrieving the Dumps
Once captured, the dumps reside inside the pod. To analyze them or send them to JFrog Support, copy them to your local machine:
kubectl cp ${NAMESPACE}/${POD_NAME}:${DUMP_DIR} ./local_thread_dumps -c ${CONTAINER_NAME}
#Example:
kubectl cp artifactory/artifactory-0:/opt/jfrog/artifactory/var/log/thread_dumps_jfconfig_20251225173711.tar.gz ./thread_dumps_jfconfig_20251225173711.tar.gz -c jfconfig
Summary Table of Service Locations
|
Service |
Container Name |
Typical Log Path (inside pod) |
|
Artifactory |
artifactory |
/opt/jfrog/artifactory/var/log/artifactory_thread_dumps |
|
Access |
access |
/opt/jfrog/artifactory/var/log/access_thread_dumps |
|
JFConfig |
jfconfig |
/opt/jfrog/artifactory/var/log/jfconfig_thread_dumps |
|
Topology |
topology |
/opt/jfrog/artifactory/var/log/topology_thread_dumps |