{"id":842,"date":"2024-06-26T12:10:52","date_gmt":"2024-06-26T12:10:52","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=842"},"modified":"2024-06-26T12:10:52","modified_gmt":"2024-06-26T12:10:52","slug":"prometheus-jmx-exporter-on-kubernetes","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=842","title":{"rendered":"How to Setup Prometheus JMX Exporter on Kubernetes"},"content":{"rendered":"<p>In this blog, we will look at the setup and configuration of the Prometheus <strong>JMX Exporter<\/strong> on a Kubernetes using a Java application.<\/p>\n<p>To better understand the JMX exporter, we will use a Spring Boot Java application and export all the JMX metrics to the Prometheus server.<\/p>\n<p>By the end of this guide, you will learn to:<\/p>\n<ol>\n<li>Setup JMX Exporter with Java application image<\/li>\n<li>Export JMX metrics to the Prometheus server<\/li>\n<li>Scrape JMX metrics of the app using Prometheus SD configs.<\/li>\n<li>Validate and query JMX metrics from the Prometheus dashboard.<\/li>\n<\/ol>\n<p>Let\u2019s dive right in.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<p>For setting up and testing this setup, you should have the following.<\/p>\n<ol>\n<li><a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\">Kubernetes cluster<\/a> with deployment permissions.<\/li>\n<li><a href=\"https:\/\/devopscube.com\/setup-prometheus-monitoring-on-kubernetes\/\">Prometheus Server running on Kubernetes<\/a>.<\/li>\n<\/ol>\n<h2 id=\"jmx-exporter\">JMX Exporter<\/h2>\n<p>First, let&#8217;s understand how the JMX exporter works.<\/p>\n<p>The following image shows the JMX Exporter workflow when deploying it on Kubernetes.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/jmx-on-kubernetes-1-1.gif\" class=\"kg-image\" alt=\"JMX Exporter workflow when deploying it on Kubernetes.\" loading=\"lazy\" width=\"776\" height=\"919\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/jmx-on-kubernetes-1-1.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/jmx-on-kubernetes-1-1.gif 776w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The MBean (Managed Bean) server, located within the Java Virtual Machine (JVM) of a Java application, acts as a repository for data related to the application&#8217;s health and performance.<\/p>\n<p>The MBean component exposes the application metrics such as status, actions, and operation information.<\/p>\n<p>Java agents can access the Mbean server and get the health and performance data. Prometheus JMX Exporter is also a Java agent, which is capable of accessing the MBean server to access the data and transform that data into the Prometheus metric format.<\/p>\n<p>Prometheus then scrapes the metrics from the JMX Exporter&#8217;s default metric storage path, which is <strong><code>\/metrics<\/code>.<\/strong><\/p>\n<h2 id=\"jmx-exporter-setup-on-kubernetes\"><strong>JMX Exporter Setup on Kubernetes<\/strong><\/h2>\n<p>Follow the steps given below to setup JMX exporter with java applications on Kubernetes.<\/p>\n<h3 id=\"step-1-build-application-docker-image-with-jmx-agent\">Step 1: Build Application Docker Image With JMX Agent<\/h3>\n<p>JMX Exporter Java agent should be run with the application on the runtime.<\/p>\n<p>You can download the JMX exporter agent jar file from here -&gt; <a href=\"https:\/\/github.com\/prometheus\/jmx_exporter\/releases?ref=devopscube.com\" rel=\"noreferrer noopener\">JMX Exporter Download<\/a>.<\/p>\n<p>So when you build the Docker image for you application, you should add the JMX exporter along with the image.<\/p>\n<p>Here is an example of a <a href=\"https:\/\/devopscube.com\/create-dockerfile-using-docker-init\/\">Dockerfile<\/a> that has both Java application jar and<strong> jmx-agent<\/strong> jar file.<\/p>\n<pre><code>FROM techiescamp\/jre-17:1.0.0\n\nENV APP_NAME=jmx-java-app\nENV JMX_PORT=5556\n\nWORKDIR \/app\n\nCOPY \/java-agent\/*.jar \/app\/jmx-agent.jar\nCOPY \/java-app\/*.jar \/app\/java.jar\nCOPY config.yaml \/app\/\n\nCMD java -javaagent:jmx-agent.jar=${JMX_PORT}:config.yaml -jar java.jar<\/code><\/pre>\n<p>I have already built the docker image and pushed it to Dockerhub. If you are learning ot testing JMX, you can use the following image directly.<\/p>\n<pre><code>techiescamp\/jmx-pet-clinic-app:latest<\/code><\/pre>\n<h3 id=\"step-2-create-configmap-for-the-jmx-exporter\">Step 2: Create ConfigMap for the JMX Exporter<\/h3>\n<p>We need to create a <strong>ConfigMap<\/strong> named <code>jmx-exporter-configmap.yaml<\/code> object for the JMX configs. In this file, we will provide information to filter the required metrics from the <strong>MBean server<\/strong>.<\/p>\n<p>This config avoids unnecessary metric collection limiting it to only the metrics we need.<\/p>\n<p>Create <code>jmx-exporter-configmap.yaml<\/code><\/p>\n<pre><code>apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: jmx-exporter-config\n  namespace: monitoring\ndata:\n  config.yaml: |-\n    lowercaseOutputName: true\n    lowercaseOutputLabelNames: true\n    whitelistObjectNames: [\"java.lang:type=Memory\", \"java.lang:type=GarbageCollector,*\"]<\/code><\/pre>\n<p>Here, you can see that I have mentioned that the <strong>namespace value is monitoring<\/strong>, this is because I have all my monitoring components in that particular namespace.<\/p>\n<p>If you want to use this same configuration, create a namespace with the same name before beginning the configuration.<\/p>\n<p>In the data section, we can define what kind of metrics the JMX Exporter has to collect from the <strong>MBean server<\/strong>. In this configuration I have given a small example to filter memory-related metrics of the application.<\/p>\n<p>To know more about the configuration, please visit the <a href=\"https:\/\/github.com\/prometheus\/jmx_exporter?ref=devopscube.com\">official documentation<\/a>.<\/p>\n<p>To deploy this configuration in the Kubernetes, use the following command.<\/p>\n<pre><code>kubectl apply -f jmx-exporter-configmap.yaml<\/code><\/pre>\n<p>To view the list configmaps in the monitoring namespace.<\/p>\n<pre><code>kubectl get configmaps -n monitoring<\/code><\/pre>\n<h3 id=\"step-3-create-a-deployment-manifest-for-the-application\">Step 3: Create a Deployment Manifest for the Application<\/h3>\n<p>When creating the <strong>Deployment<\/strong> manifest, we have to expose the application and the metric ports, only then can we access the application and metrics over the Internet.<\/p>\n<p>Create a deployment YAML file <code>app-deployment.yaml.<\/code><\/p>\n<pre><code>apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: jmx-java-app\n  namespace: monitoring\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: jmx-java-app\n  template:\n    metadata:\n      labels:\n        app: jmx-java-app\n    spec:\n      containers:\n        - name: jmx-java-app-container\n          image: techiescamp\/jmx-pet-clinic-app:latest\n          ports:\n            - containerPort: 8080\n            - containerPort: 5556\n          volumeMounts:\n            - name: jmx-exporter-config-volume\n              mountPath: \/app\/config.yaml\n              subPath: config.yaml\n      volumes:\n        - name: jmx-exporter-config-volume\n          configMap:\n            name: jmx-exporter-config<\/code><\/pre>\n<p>Here, you can see I am exposing ports <strong>8080 <\/strong>and<strong> 5556<\/strong> outside the container.<\/p>\n<p>One important thing here is the <strong>volumeMounts<\/strong> section. The mountPath is uses the configmap as volumes. It overwrites the container default <code>\/app\/config.yaml<\/code><\/p>\n<p>Now deploy the manifest.<\/p>\n<pre><code>kubectl apply -f app-deployment.yaml<\/code><\/pre>\n<p>To view the list of Deployments in the monitoring Namespace.<\/p>\n<pre><code>kubectl get deployments -n monitoring<\/code><\/pre>\n<p>To view the list of Pods in the monitoring Namespace.<\/p>\n<pre><code>kubectl get pods -n monitoring -o wide<\/code><\/pre>\n<p>For testing purposes, I have given two replicas, so we can see two Java application Pods running in the monitoring Namespace.<\/p>\n<h3 id=\"step-4-create-a-service-for-application\">Step 4: Create a Service for Application<\/h3>\n<p>Create a Service manifest for the application <code>app-service.yaml<\/code><\/p>\n<pre><code>apiVersion: v1\nkind: Service\nmetadata:\n  name: jmx-java-app-nodeport\n  namespace: monitoring\nspec:\n  selector:\n    app: jmx-java-app\n  ports:\n  - name: http\n    protocol: TCP\n    port: 8080\n    targetPort: 8080\n    nodePort: 30750  \n  - name: jmx\n    protocol: TCP\n    port: 5556\n    targetPort: 5556\n    nodePort: 30850  \n  type: NodePort<\/code><\/pre>\n<p>I want to access the application and the metrics over the internet so I am using the <code>spec.type: NodePort<\/code>, and defining the nodePort numbers such as <strong>30750 <\/strong>for the application and <strong>30850<\/strong> for the metrics<strong>.<\/strong><\/p>\n<p>To apply this configuration, use the following command.<\/p>\n<pre><code>kubectl apply -f app-service.yaml<\/code><\/pre>\n<p>To view the list of Services in the monitoring Namespace.<\/p>\n<pre><code>kubectl get svc -n monitoring<\/code><\/pre>\n<p>Now, let&#8217;s try to access the application and metrics over the internet, for that, you need the <strong>Public IP<\/strong> of any one of the instances and the <strong>port numbers.<\/strong><\/p>\n<p>The output of the <strong>application<\/strong><\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-111-7.png\" class=\"kg-image\" alt=\"petclinic java app output\" loading=\"lazy\" width=\"1377\" height=\"1067\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-111-7.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-111-7.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-111-7.png 1377w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The output of the <strong>metrics.<\/strong><\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-112-7.png\" class=\"kg-image\" alt=\"jmx exporter dashboard\" loading=\"lazy\" width=\"1342\" height=\"1125\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-112-7.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-112-7.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-112-7.png 1342w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h3 id=\"step-5-add-scrape-config-in-prometheus\">Step 5: Add Scrape Config in Prometheus<\/h3>\n<blockquote><p><strong>Note<\/strong>: This configuration has to be done on the Prometheus server.<\/p><\/blockquote>\n<p>The JMX Exporter is successfully exposing the metrics of the <strong>Java application<\/strong>.<\/p>\n<p>Now we add a job in the <strong>Prometheus server configuration<\/strong> file to scrape these metrics, and then only we can store them in <strong>TSDB<\/strong> or make queries.<\/p>\n<p>Find the Prometheus ConfigMap, in my setup, the file name is <code>config-map.yaml<\/code>. Open it with a text editor.<\/p>\n<pre><code>vim config-map.yaml<\/code><\/pre>\n<p>To view the list of configmaps in the monitoring namespace.<\/p>\n<pre><code>kubectl get configmaps -n monitoring<\/code><\/pre>\n<p>Add the following contents inside the configuration file, under the <strong>scrape_configs.<\/strong><\/p>\n<p>If you are using a label selector in the deployment manifest, replace all the occurrences of <strong><code>jmx-java-app<\/code><\/strong> with the respective label.<\/p>\n<pre><code>scrape_configs:\n    - job_name: 'jmx-java-app'\n        kubernetes_sd_configs:\n        - role: pod\n          namespaces:\n            names:\n              - monitoring\n        relabel_configs:\n        - source_labels: [__meta_kubernetes_pod_label_app]\n          action: keep\n          regex: 'jmx-java-app'\n        - source_labels: [__meta_kubernetes_namespace]\n          target_label: namespace\n        - source_labels: [__meta_kubernetes_pod_name]\n          target_label: pod_name\n        - target_label: __address__\n          replacement: \"jmx-java-app-nodeport:5556\"<\/code><\/pre>\n<p>In this promtheus configuration, I am using the <code>kubernetes_sd_configs<\/code> <a href=\"https:\/\/devopscube.com\/service-discovery-explained\/\">service discovery <\/a>method that automatically identifies the pods that belong to the j<strong><code>mx-java-app<\/code><\/strong> deployment using <strong><code>jmx-java-app<\/code><\/strong> label. So you don&#8217;t need to manually provide the target details for scrapping the JMX metrics.<\/p>\n<p>To apply this configuration, use the following command.<\/p>\n<pre><code>kubectl apply -f config-map.yaml<\/code><\/pre>\n<p>It might take a few minutes to replicate the changes in the Prometheus dashboard. If you dont see the changes, then perform a deployment <strong>rollout<\/strong> using the following command. Change the <strong>prometheus-deployment<\/strong> to your actual Prometheus deployment name.<\/p>\n<pre><code>kubectl rollout restart deployment prometheus-deployment -n monitoring<\/code><\/pre>\n<h3 id=\"step-6-validate-jmx-metrics-from-prometheus-dashboard\">Step 6: Validate JMX Metrics from Prometheus Dashboard<\/h3>\n<p>You can validate the JMX targets from the Prometheus dashbaord.<\/p>\n<p>Under the <strong>Targets<\/strong> section, we can see the endpoint details, status, and other information as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-113-5.png\" class=\"kg-image\" alt=\"Prometheus dashboard with JMX targets\" loading=\"lazy\" width=\"873\" height=\"556\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-113-5.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-113-5.png 873w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>In the Graph section, we can make queries and get results. For example, the following dashboard shows<strong><code> jvm_memory_bytes_used<\/code><\/strong> metrics from the app.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-114-3.png\" class=\"kg-image\" alt=\"prometheus query for JMX metrics\" loading=\"lazy\" width=\"879\" height=\"570\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-114-3.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-114-3.png 879w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h3 id=\"step-6-grafana-dashboard\">Step 6: Grafana Dashboard<\/h3>\n<p>We can <strong>visualize<\/strong> the JMX metrics in Grafana dashboards as per our requirements.<\/p>\n<p>If you have a <a href=\"https:\/\/devopscube.com\/setup-grafana-kubernetes\/\">Grafana setup<\/a> and added <a href=\"https:\/\/devopscube.com\/integrate-visualize-prometheus-grafana\/\">Prometheus as a data source<\/a>, you can create dashboards using the <a href=\"https:\/\/grafana.com\/grafana\/dashboards\/7727-jvm-overview\/?ref=devopscube.com\" rel=\"noreferrer noopener\">JMX metrics templates<\/a>. You check out visualizing Promtheus metrics on Grafana blog to know more.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-115-2.png\" class=\"kg-image\" alt=\"Grafana dashboard for Promtheus JMX metrics\" loading=\"lazy\" width=\"1078\" height=\"704\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-115-2.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-115-2.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-115-2.png 1078w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"conclusion\"><strong>Conclusion<\/strong><\/h2>\n<p>In this blog we have covered steps to implement JMX Exporter on the <strong>Kubernetes<\/strong> cluster. You can make further configuration change that is suitable for your application.<\/p>\n<p>Also, in the exporter Configmap, you can add other filter settings for the JMX Exporter to collect other metrics that is required for the monitoring.<\/p>\n<p>Also, for setting for JMX exporter on <a href=\"https:\/\/devopscube.com\/install-configure-prometheus-linux\/\" rel=\"noreferrer noopener\">Linux VM based prometheus setup<\/a>, please <a href=\"https:\/\/devopscube.com\/setup-prometheus-jmx-exporter\/\" rel=\"noreferrer noopener\">refer this guide.<\/a><\/p>\n<p>If you face any errors or need clarification on the setup, drop a comment below.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/prometheus-jmx-exporter-on-kubernetes\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Setup Prometheus JMX Exporter on Kubernetes \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/prometheus-jmx-exporter-on-kubernetes\/<\/p>\n","protected":false},"author":1,"featured_media":843,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-842","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/842","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=842"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/842\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/843"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=842"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=842"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=842"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}