• /
  • EnglishEspañolFrançais日本語한국어Português
  • Log inStart now

Monitor Elasticsearch on Kubernetes with OpenTelemetry

Monitor your Elasticsearch clusters in Kubernetes by deploying the OpenTelemetry Collector with automatic pod discovery. This integration uses the elasticsearchreceiver and receivercreator to automatically discover and monitor Elasticsearch pods without manual configuration.

To get started, select the collector distribution that best fits your Kubernetes environment:

You can choose between three collector options:

  • NRDOT: New Relic Distribution of OpenTelemetry
  • OTel Collector Contrib: Standard OpenTelemetry Collector with community-contributed components
  • Prometheus Receiver: For environments already running a Prometheus Elasticsearch exporter

Installation options

Choose the collector distribution that matches your needs:

Before you begin

Before deploying the NRDOT collector on Kubernetes, ensure you have:

Required access privileges:

  • Your New Relic
  • kubectl access to your Kubernetes cluster
  • Elasticsearch cluster admin privileges with monitor or manage cluster privilege (see Elasticsearch security privileges documentation for details)

System requirements:

  • Elasticsearch version 7.16 or higher - This integration requires a modern Elasticsearch cluster
  • Kubernetes cluster - A running Kubernetes cluster where Elasticsearch is deployed
  • Network connectivity - Outbound HTTPS (port 443) to New Relic's OTLP ingest endpoint

Elasticsearch pod requirements:

  • Pod labels (Required) - Each Elasticsearch pod must have the label app: elasticsearch for automatic discovery to work. Without this label, the collector will not detect or monitor your pods.

Important

How to add labels to Elasticsearch pods:

If you're using a StatefulSet or Deployment for Elasticsearch, add the label in the pod template:

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
spec:
template:
metadata:
labels:
app: elasticsearch # Required for auto-discovery
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:8.x.x

For existing pods without labels, update your StatefulSet/Deployment and restart the pods:

bash
$
kubectl label pods -l <your-existing-selector> app=elasticsearch -n <namespace>

You can verify labels are set correctly:

bash
$
kubectl get pods -n <namespace> --show-labels

Choose your installation method

You can install the NRDOT Collector using Kubernetes manifests or Helm charts. Choose the method that best fits your workflow:

Manifest install:

  • More control over individual Kubernetes resources
  • Requires completing the base Kubernetes OpenTelemetry manifest installation first
  • Best for customized deployments

Helm install:

  • Simpler deployment with single command
  • Easier to manage and upgrade
  • Best for standard deployments

Proceed to the next step for detailed instructions for your chosen method.

Install and configure NRDOT Collector

Verify deployment and data collection

Verify that the NRDOT collector is running and collecting Elasticsearch data:

  1. Check that the collector pods are running:

    bash
    $
    kubectl get pods -n newrelic --watch

    For manifest install: You should see pods with names like nr-k8s-otel-collector-deployment-<hash> in a Running state.

    For Helm install: You should see pods with names like elasticsearch-nrdot-collector-<hash> in a Running state.

  2. Check the collector logs for any errors:

    For manifest install:

    bash
    $
    kubectl logs -n newrelic -l app.kubernetes.io/name=nr-k8s-otel-collector -f

    For Helm install:

    bash
    $
    kubectl logs -n newrelic -l app.kubernetes.io/name=opentelemetry-collector -f

    Look for successful connections to Elasticsearch pods and New Relic. If you see errors, refer to the troubleshooting guide.

  3. Run an NRQL query in New Relic to confirm data is arriving (replace elasticsearch-cluster with your cluster name):

    FROM Metric
    SELECT *
    WHERE metricName LIKE 'elasticsearch.%'
    AND instrumentation.provider = 'opentelemetry'
    AND k8s.cluster.name = 'elasticsearch-cluster'
    SINCE 10 minutes ago

Before you begin

Before deploying the OTel Collector Contrib on Kubernetes, ensure you have:

Required access privileges:

System requirements:

  • Elasticsearch version 7.16 or higher - This integration requires a modern Elasticsearch cluster
  • Kubernetes cluster - A running Kubernetes cluster where Elasticsearch is deployed
  • Helm 3.0 or higher - Helm installed on your system
  • Network connectivity - Outbound HTTPS (port 443) to New Relic's OTLP ingest endpoint

Elasticsearch pod requirements:

  • Pod labels (Required) - Each Elasticsearch pod must have the label app: elasticsearch for automatic discovery to work. Without this label, the collector will not detect or monitor your pods.

Important

How to add labels to Elasticsearch pods:

If you're using a StatefulSet or Deployment for Elasticsearch, add the label in the pod template:

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
spec:
template:
metadata:
labels:
app: elasticsearch # Required for auto-discovery
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:8.x.x

For existing pods without labels, update your StatefulSet/Deployment and restart the pods:

bash
$
kubectl label pods -l <your-existing-selector> app=elasticsearch -n <namespace>

You can verify labels are set correctly:

bash
$
kubectl get pods -n <namespace> --show-labels

Create Kubernetes secret for credentials

Create a Kubernetes secret to store your New Relic credentials securely:

  1. Create the namespace:
bash
$
kubectl create namespace newrelic
  1. Create the secret:
bash
$
kubectl create secret generic newrelic-licenses \
>
--from-literal=NEWRELIC_LICENSE_KEY=YOUR_LICENSE_KEY_HERE \
>
--from-literal=NEWRELIC_OTLP_ENDPOINT=https://otlp.nr-data.net:4318 \
>
--from-literal=NEW_RELIC_MEMORY_LIMIT_MIB=100 \
>
-n newrelic

Update the values:

  • Replace YOUR_LICENSE_KEY_HERE with your actual New Relic license key
  • Replace https://otlp.nr-data.net:4318 with your region's endpoint (refer to OTLP endpoint documentation)
  • Replace 100 with your desired memory limit in MiB for the collector (default: 100 MiB). Adjust based on your environment's needs

Configure Elasticsearch monitoring

Create a values.yaml file to configure the OpenTelemetry Collector for Elasticsearch monitoring:

Tip

Customize for your environment: Update the following values in the configuration:

Required changes:

  • Pod label rule - The rule labels["app"] == "elasticsearch" must match your pod labels. If your Elasticsearch pods use different labels (e.g., app: es-cluster), update the rule accordingly:
    rule: type == "pod" && labels["app"] == "es-cluster"
  • Cluster name - Replace elasticsearch-cluster with a unique name to identify your cluster in New Relic. This name will be used to create and identify your Elasticsearch entities in the New Relic UI. Choose a name that's unique across your New Relic account (e.g., prod-es-k8s, staging-elasticsearch)

Optional changes:

  • Port - Update 9200 if Elasticsearch runs on a different port
  • Authentication - Add credentials if your Elasticsearch cluster is secured
mode: deployment
image:
repository: otel/opentelemetry-collector-contrib
pullPolicy: IfNotPresent
command:
name: otelcol-contrib
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
extraEnvs:
- name: NEWRELIC_LICENSE_KEY
valueFrom:
secretKeyRef:
name: newrelic-licenses
key: NEWRELIC_LICENSE_KEY
- name: NEWRELIC_OTLP_ENDPOINT
valueFrom:
secretKeyRef:
name: newrelic-licenses
key: NEWRELIC_OTLP_ENDPOINT
- name: NEW_RELIC_MEMORY_LIMIT_MIB
valueFrom:
secretKeyRef:
name: newrelic-licenses
key: NEW_RELIC_MEMORY_LIMIT_MIB
- name: K8S_CLUSTER_NAME
value: "elasticsearch-cluster"
clusterRole:
create: true
rules:
- apiGroups: [""]
resources: ["pods", "nodes", "nodes/stats", "nodes/proxy"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["get", "list", "watch"]
config:
extensions:
health_check:
endpoint: 0.0.0.0:13133
k8s_observer:
auth_type: serviceAccount
observe_pods: true
observe_nodes: true
receivers:
receiver_creator/elasticsearch:
watch_observers: [k8s_observer]
receivers:
elasticsearch:
rule: type == "pod" && labels["app"] == "elasticsearch"
config:
endpoint: 'http://`endpoint`:9200'
collection_interval: 30s
metrics:
elasticsearch.os.cpu.usage:
enabled: true
elasticsearch.cluster.data_nodes:
enabled: true
elasticsearch.cluster.health:
enabled: true
elasticsearch.cluster.in_flight_fetch:
enabled: true
elasticsearch.cluster.nodes:
enabled: true
elasticsearch.cluster.pending_tasks:
enabled: true
elasticsearch.cluster.shards:
enabled: true
elasticsearch.cluster.state_update.time:
enabled: true
elasticsearch.index.documents:
enabled: true
elasticsearch.index.operations.merge.current:
enabled: true
elasticsearch.index.operations.time:
enabled: true
elasticsearch.node.cache.count:
enabled: true
elasticsearch.node.cache.evictions:
enabled: true
elasticsearch.node.cache.memory.usage:
enabled: true
elasticsearch.node.shards.size:
enabled: true
elasticsearch.node.cluster.io:
enabled: true
elasticsearch.node.documents:
enabled: true
elasticsearch.node.disk.io.read:
enabled: true
elasticsearch.node.disk.io.write:
enabled: true
elasticsearch.node.fs.disk.available:
enabled: true
elasticsearch.node.fs.disk.total:
enabled: true
elasticsearch.node.http.connections:
enabled: true
elasticsearch.node.ingest.documents.current:
enabled: true
elasticsearch.node.ingest.operations.failed:
enabled: true
elasticsearch.node.open_files:
enabled: true
elasticsearch.node.operations.completed:
enabled: true
elasticsearch.node.operations.current:
enabled: true
elasticsearch.node.operations.get.completed:
enabled: true
elasticsearch.node.operations.time:
enabled: true
elasticsearch.node.shards.reserved.size:
enabled: true
elasticsearch.index.shards.size:
enabled: true
elasticsearch.os.cpu.load_avg.1m:
enabled: true
elasticsearch.os.cpu.load_avg.5m:
enabled: true
elasticsearch.os.cpu.load_avg.15m:
enabled: true
elasticsearch.os.memory:
enabled: true
jvm.gc.collections.count:
enabled: true
jvm.gc.collections.elapsed:
enabled: true
jvm.memory.heap.max:
enabled: true
jvm.memory.heap.used:
enabled: true
jvm.memory.heap.utilization:
enabled: true
jvm.threads.count:
enabled: true
elasticsearch.index.segments.count:
enabled: true
elasticsearch.index.operations.completed:
enabled: true
elasticsearch.node.script.cache_evictions:
enabled: false
elasticsearch.node.cluster.connections:
enabled: false
elasticsearch.node.pipeline.ingest.documents.preprocessed:
enabled: false
elasticsearch.node.thread_pool.tasks.queued:
enabled: false
elasticsearch.cluster.published_states.full:
enabled: false
jvm.memory.pool.max:
enabled: false
elasticsearch.node.script.compilation_limit_triggered:
enabled: false
elasticsearch.node.shards.data_set.size:
enabled: false
elasticsearch.node.pipeline.ingest.documents.current:
enabled: false
elasticsearch.cluster.state_update.count:
enabled: false
elasticsearch.node.fs.disk.free:
enabled: false
jvm.memory.nonheap.used:
enabled: false
jvm.memory.pool.used:
enabled: false
elasticsearch.node.translog.size:
enabled: false
elasticsearch.node.thread_pool.threads:
enabled: false
elasticsearch.cluster.state_queue:
enabled: false
elasticsearch.node.translog.operations:
enabled: false
elasticsearch.memory.indexing_pressure:
enabled: false
elasticsearch.node.ingest.documents:
enabled: false
jvm.classes.loaded:
enabled: false
jvm.memory.heap.committed:
enabled: false
elasticsearch.breaker.memory.limit:
enabled: false
elasticsearch.indexing_pressure.memory.total.replica_rejections:
enabled: false
elasticsearch.breaker.memory.estimated:
enabled: false
elasticsearch.cluster.published_states.differences:
enabled: false
jvm.memory.nonheap.committed:
enabled: false
elasticsearch.node.translog.uncommitted.size:
enabled: false
elasticsearch.node.script.compilations:
enabled: false
elasticsearch.node.pipeline.ingest.operations.failed:
enabled: false
elasticsearch.indexing_pressure.memory.limit:
enabled: false
elasticsearch.breaker.tripped:
enabled: false
elasticsearch.indexing_pressure.memory.total.primary_rejections:
enabled: false
elasticsearch.node.thread_pool.tasks.finished:
enabled: false
processors:
memory_limiter:
check_interval: 60s
limit_mib: ${env:NEW_RELIC_MEMORY_LIMIT_MIB}
cumulativetodelta: {}
resource/cluster:
attributes:
- key: k8s.cluster.name
value: "${env:K8S_CLUSTER_NAME}"
action: insert
resource/cluster_name_override:
attributes:
- key: elasticsearch.cluster.name
value: "${env:K8S_CLUSTER_NAME}"
action: upsert
resourcedetection:
detectors: [env, system]
system:
resource_attributes:
host.name:
enabled: true
host.id:
enabled: true
os.type:
enabled: true
batch:
timeout: 10s
send_batch_size: 1024
attributes/cardinality_reduction:
actions:
- key: process.pid
action: delete
- key: process.parent_pid
action: delete
- key: k8s.pod.uid
action: delete
transform/metadata_nullify:
metric_statements:
- context: metric
statements:
- set(description, "")
- set(unit, "")
exporters:
otlphttp:
endpoint: "${env:NEWRELIC_OTLP_ENDPOINT}"
headers:
api-key: "${env:NEWRELIC_LICENSE_KEY}"
service:
extensions: [health_check, k8s_observer]
pipelines:
metrics/elasticsearch:
receivers: [receiver_creator/elasticsearch]
processors: [memory_limiter, resourcedetection, resource/cluster, resource/cluster_name_override, attributes/cardinality_reduction, cumulativetodelta, transform/metadata_nullify, batch]
exporters: [otlphttp]

Tip

For secured Elasticsearch clusters: If your Elasticsearch cluster requires authentication, add credentials to the receiver configuration:

receiver_creator/elasticsearch:
watch_observers: [k8s_observer]
receivers:
elasticsearch:
rule: type == "pod" && labels["app"] == "elasticsearch"
config:
endpoint: 'https://`endpoint`:9200'
username: "your_elasticsearch_username"
password: "your_elasticsearch_password"
tls:
insecure_skip_verify: false

Store credentials securely using Kubernetes secrets rather than hardcoding them in the values file.

Install with Helm

Install the OpenTelemetry Collector using Helm with your values.yaml configuration:

bash
$
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
$
helm repo update
$
helm upgrade --install elasticsearch-otel-collector open-telemetry/opentelemetry-collector \
>
--namespace newrelic \
>
--create-namespace \
>
-f values.yaml

Verify deployment and data collection

Verify that the OpenTelemetry Collector is running and collecting Elasticsearch data:

  1. Check that the collector pods are running:

    bash
    $
    kubectl get pods -n newrelic --watch

    You should see pods with names like elasticsearch-otel-collector-<hash> in a Running state.

  2. Check the collector logs for any errors:

    bash
    $
    kubectl logs -n newrelic -l app.kubernetes.io/name=opentelemetry-collector -f

    Look for successful connections to Elasticsearch pods and New Relic. If you see errors, refer to the troubleshooting guide.

  3. Run an NRQL query in New Relic to confirm data is arriving (replace elasticsearch-cluster with your cluster name):

    FROM Metric
    SELECT *
    WHERE metricName LIKE 'elasticsearch.%'
    AND instrumentation.provider = 'opentelemetry'
    AND k8s.cluster.name = 'elasticsearch-cluster'
    SINCE 10 minutes ago

Use this approach if you already have a Prometheus Elasticsearch exporter running in your Kubernetes cluster, or if you're migrating from a Prometheus-based monitoring stack.

Tip

Recommended: If you don't already have a Prometheus exporter running, use the NRDOT Collector or OTel Collector Contrib tabs instead. They connect directly to the Elasticsearch API without needing an additional exporter component.

Deploy the Elasticsearch exporter

If you don't already have the exporter running, deploy it using Helm:

bash
$
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$
helm repo update
$
$
helm install elasticsearch-exporter prometheus-community/prometheus-elasticsearch-exporter \
>
--namespace monitoring \
>
--create-namespace \
>
--set es.uri=http://elasticsearch.default.svc.cluster.local:9200

Replace elasticsearch.default.svc.cluster.local:9200 with your Elasticsearch service address.

Verify the exporter is running:

bash
$
kubectl get pods -n monitoring -l app=prometheus-elasticsearch-exporter

Create the credentials Secret

bash
$
kubectl create secret generic newrelic-credentials \
>
--namespace monitoring \
>
--from-literal=NEWRELIC_LICENSE_KEY=YOUR_NEWRELIC_LICENSE_KEY \
>
--from-literal=NEWRELIC_OTLP_ENDPOINT=https://otlp.nr-data.net:4318

Replace YOUR_NEWRELIC_LICENSE_KEY with your .

Tip

For EU accounts, use NEWRELIC_OTLP_ENDPOINT=https://otlp.eu01.nr-data.net:4318

Create the collector ConfigMap

Create a ConfigMap with the collector configuration. This works with both NRDOT (newrelic/nrdot-collector) and OTel Collector Contrib (otel/opentelemetry-collector-contrib) container images. The configuration scrapes metrics from the Elasticsearch exporter and translates Prometheus metric names to OpenTelemetry-compatible names that power the New Relic Elasticsearch dashboard.

Replace the following values in the configuration:

  • <elasticsearch-cluster-name>: Your Elasticsearch cluster name
  • elasticsearch-exporter-prometheus-elasticsearch-exporter.monitoring.svc.cluster.local:9114: Your exporter's Kubernetes service address

Deploy the collector

Deploy the collector using either the NRDOT or OTel Collector Contrib image. Update the image field below based on your choice:

  • NRDOT: newrelic/nrdot-collector:latest
  • OTel Collector Contrib: otel/opentelemetry-collector-contrib:latest

Save the following as otel-collector-deployment.yaml and apply with kubectl apply -f otel-collector-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector-elasticsearch
namespace: monitoring
labels:
app: otel-collector-elasticsearch
spec:
replicas: 1
selector:
matchLabels:
app: otel-collector-elasticsearch
template:
metadata:
labels:
app: otel-collector-elasticsearch
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:latest
args:
- "--config=/etc/otel/config.yaml"
env:
- name: NEWRELIC_LICENSE_KEY
valueFrom:
secretKeyRef:
name: newrelic-credentials
key: NEWRELIC_LICENSE_KEY
- name: NEWRELIC_OTLP_ENDPOINT
valueFrom:
secretKeyRef:
name: newrelic-credentials
key: NEWRELIC_OTLP_ENDPOINT
volumeMounts:
- name: config
mountPath: /etc/otel
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
volumes:
- name: config
configMap:
name: otel-collector-prometheus-es

Verify the deployment

  1. Check the collector pod is running:

    bash
    $
    kubectl get pods -n monitoring -l app=otel-collector-elasticsearch
  2. Check collector logs:

    bash
    $
    kubectl logs -n monitoring -l app=otel-collector-elasticsearch -f
  3. Verify data in New Relic:

    FROM Metric SELECT count(*)
    WHERE metricName LIKE 'elasticsearch.%'
    AND elasticsearch.cluster.name = '<elasticsearch-cluster-name>'
    SINCE 10 minutes ago

Tip

Correlate APM with Elasticsearch: To connect your APM application and Elasticsearch cluster, include the resource attribute es.cluster.name="your-cluster-name" in your APM metrics. This enables cross-service visibility and faster troubleshooting within New Relic.

Troubleshooting

If you encounter issues during installation or don't see data in New Relic, see our comprehensive troubleshooting guide for step-by-step solutions to common problems.

For Kubernetes-specific issues like pod discovery, RBAC permissions, or network connectivity, refer to the Kubernetes troubleshooting section.

Copyright © 2026 New Relic Inc.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.