New to KubeDB? Please start here.
Monitoring Etcd with builtin Prometheus
This tutorial shows how to monitor a KubeDB-managed etcd cluster with a plain Prometheus server — no Prometheus operator, no CRDs, just annotation-based service discovery.
Read this first: unlike most KubeDB databases, etcd has no exporter sidecar. etcd serves Prometheus metrics natively on its own metrics listener at port
2381. When you setspec.monitor, KubeDB adds nothing to the pod; it only creates and annotates a statsServiceso Prometheus can find what etcd is already serving. See the monitoring overview.
Before You Begin
At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using kind.
Install KubeDB operator in your cluster following the steps here. Etcd support is behind an alpha feature gate, so make sure the operator was installed with
--set featureGates.Etcd=true.If you are not familiar with how to configure Prometheus to scrape metrics from Kubernetes resources, please read the tutorial here.
To learn how Prometheus monitoring works with KubeDB in general, please visit here.
To keep Prometheus resources isolated, we are going to use a separate namespace called
monitoringfor the monitoring stack, and deploy the database indemo.$ kubectl create ns monitoring namespace/monitoring created $ kubectl create ns demo namespace/demo created
Note: YAML files used in this tutorial are stored in docs/examples/etcd folder in GitHub repository kubedb/docs.
Deploy Etcd with Monitoring Enabled
apiVersion: kubedb.com/v1alpha2
kind: Etcd
metadata:
name: builtin-prom-etcd
namespace: demo
spec:
version: 3.6.4
replicas: 3
storageType: Durable
storage:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
monitor:
agent: prometheus.io/builtin
deletionPolicy: WipeOut
Here,
spec.monitor.agent: prometheus.io/builtintells KubeDB to annotate the stats Service for annotation-based discovery.
Create it:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/examples/etcd/monitoring/builtin-prom-etcd.yaml
etcd.kubedb.com/builtin-prom-etcd created
Wait for the cluster to become Ready:
$ kubectl get etcd -n demo builtin-prom-etcd
NAME VERSION STATUS AGE
builtin-prom-etcd 3.6.4 Ready 2m
Note the member pods report 1/1, not 2/2 — there is no exporter container:
$ kubectl get pod -n demo -l app.kubernetes.io/instance=builtin-prom-etcd
NAME READY STATUS RESTARTS AGE
builtin-prom-etcd-0 1/1 Running 0 2m
builtin-prom-etcd-1 1/1 Running 0 2m
builtin-prom-etcd-2 1/1 Running 0 2m
The stats Service
KubeDB creates a separate stats Service named {Etcd crd name}-stats:
$ kubectl get svc -n demo --selector="app.kubernetes.io/instance=builtin-prom-etcd"
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
builtin-prom-etcd ClusterIP 10.102.7.190 <none> 2379/TCP 2m
builtin-prom-etcd-pods ClusterIP None <none> 2379/TCP,2380/TCP 2m
builtin-prom-etcd-stats ClusterIP 10.102.128.153 <none> 2381/TCP 2m
Let’s describe the stats Service:
$ kubectl describe svc -n demo builtin-prom-etcd-stats
Name: builtin-prom-etcd-stats
Namespace: demo
Labels: app.kubernetes.io/component=database
app.kubernetes.io/instance=builtin-prom-etcd
app.kubernetes.io/managed-by=kubedb.com
app.kubernetes.io/name=etcds.kubedb.com
kubedb.com/role=stats
Annotations: monitoring.appscode.com/agent: prometheus.io/builtin
prometheus.io/path: /metrics
prometheus.io/port: 2381
prometheus.io/scheme: http
prometheus.io/scrape: true
Selector: app.kubernetes.io/instance=builtin-prom-etcd,app.kubernetes.io/managed-by=kubedb.com,app.kubernetes.io/name=etcds.kubedb.com
Type: ClusterIP
IP: 10.102.128.153
Port: metrics 2381/TCP
TargetPort: metrics/TCP
Endpoints: 10.244.1.7:2381,10.244.2.9:2381,10.244.3.5:2381
Session Affinity: None
Events: <none>
The annotations that matter for discovery are:
prometheus.io/scrape: true
prometheus.io/scheme: http
prometheus.io/path: /metrics
prometheus.io/port: 2381
The Prometheus server will discover the Service endpoints using these and scrape metrics straight from etcd — the TargetPort: metrics resolves to port 2381 on each member pod. Note there is one endpoint per member: each etcd member reports its own view of the cluster, so scraping all of them is the point.
prometheus.io/scheme is http even if you have TLS enabled on the cluster, because the metrics listener is a separate socket from the mutually authenticated client API. See the monitoring overview for why.
Configure Prometheus Server
Now we have to configure a Prometheus scraping job to scrape metrics using this Service. We are going to configure a scraping job similar to this kubernetes-service-endpoints job that scrapes metrics from the endpoints of a service.
- job_name: 'kubedb-databases'
honor_labels: true
scheme: http
kubernetes_sd_configs:
- role: endpoints
# by default Prometheus server selects all Kubernetes services as possible targets.
# relabel_config is used to filter only the desired endpoints
relabel_configs:
# keep only those services that have the "prometheus.io/scrape" and "prometheus.io/port" annotations
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape, __meta_kubernetes_service_annotation_prometheus_io_port]
separator: ;
regex: true;(.*)
action: keep
# KubeDB stats services use the "http" scheme, so drop any service that uses "https".
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: drop
regex: https
# only keep the stats services created by KubeDB for monitoring purposes, which have a "-stats" suffix
- source_labels: [__meta_kubernetes_service_name]
separator: ;
regex: (.*-stats)
action: keep
# services created by KubeDB have the "app.kubernetes.io/name" label. keep only those services.
- source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name]
separator: ;
regex: (.*)
action: keep
# read the metric path from the "prometheus.io/path: <path>" annotation
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
# read the port from the "prometheus.io/port: <port>" annotation and update the scraping address accordingly
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
# add the service namespace as a label on the scraped metrics
- source_labels: [__meta_kubernetes_namespace]
separator: ;
regex: (.*)
target_label: namespace
replacement: $1
action: replace
# add the service name as a label on the scraped metrics
- source_labels: [__meta_kubernetes_service_name]
separator: ;
regex: (.*)
target_label: service
replacement: $1
action: replace
# add the pod name as a label, so you can tell which etcd member a sample came from
- source_labels: [__meta_kubernetes_pod_name]
separator: ;
regex: (.*)
target_label: pod
replacement: $1
action: replace
# add the stats service's labels to the scraped metrics
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
The pod relabeling rule at the end is worth keeping for etcd specifically: every member is scraped separately and reports its own view of the cluster, so without a pod label you cannot tell which member reported etcd_server_has_leader 0.
Configure Existing Prometheus Server
If you already have a Prometheus server running, add the scraping job above to the ConfigMap used to configure it, then restart it for the updated configuration to take effect.
If you don’t use a persistent volume for Prometheus storage, you will lose your previously scraped data on restart.
Deploy New Prometheus Server
If you don’t have a Prometheus server yet, deploy one in the monitoring namespace to collect metrics using this stats Service.
Create ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
labels:
app: prometheus-demo
namespace: monitoring
data:
prometheus.yml: |-
global:
scrape_interval: 5s
evaluation_interval: 5s
scrape_configs:
- job_name: 'kubedb-databases'
honor_labels: true
scheme: http
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape, __meta_kubernetes_service_annotation_prometheus_io_port]
separator: ;
regex: true;(.*)
action: keep
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: drop
regex: https
- source_labels: [__meta_kubernetes_service_name]
separator: ;
regex: (.*-stats)
action: keep
- source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name]
separator: ;
regex: (.*)
action: keep
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
- source_labels: [__meta_kubernetes_namespace]
separator: ;
regex: (.*)
target_label: namespace
replacement: $1
action: replace
- source_labels: [__meta_kubernetes_service_name]
separator: ;
regex: (.*)
target_label: service
replacement: $1
action: replace
- source_labels: [__meta_kubernetes_pod_name]
separator: ;
regex: (.*)
target_label: pod
replacement: $1
action: replace
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/examples/etcd/monitoring/prom-config.yaml
configmap/prometheus-config created
Create RBAC:
If you are using an RBAC-enabled cluster, give Prometheus the necessary permissions:
$ kubectl apply -f https://github.com/appscode/third-party-tools/raw/master/monitoring/prometheus/builtin/artifacts/rbac.yaml
clusterrole.rbac.authorization.k8s.io/prometheus created
serviceaccount/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created
YAML for the RBAC resources created above can be found here.
Deploy Prometheus:
$ kubectl apply -f https://github.com/appscode/third-party-tools/raw/master/monitoring/prometheus/builtin/artifacts/deployment.yaml
deployment.apps/prometheus created
Verify Monitoring Metrics
Prometheus listens on port 9090. Use port forwarding to reach its dashboard.
$ kubectl get pod -n monitoring -l=app=prometheus
NAME READY STATUS RESTARTS AGE
prometheus-8568c86d86-95zhn 1/1 Running 0 77s
$ kubectl port-forward -n monitoring prometheus-8568c86d86-95zhn 9090
Forwarding from 127.0.0.1:9090 -> 9090
Open http://localhost:9090/targets. Under the kubedb-databases job you should see three targets — one per etcd member — each with an address ending in :2381, and each carrying the service="builtin-prom-etcd-stats", namespace="demo" and pod="builtin-prom-etcd-N" labels.
Now try a few queries on the graph page. These are upstream etcd’s own metric names, since etcd is doing the exporting:
# 1 for every member that currently sees a leader. Any 0 here is a problem.
etcd_server_has_leader
# Current backend database size, per member.
etcd_mvcc_db_total_size_in_bytes
# How close the backend is to its configured --quota-backend-bytes.
etcd_mvcc_db_total_size_in_bytes / etcd_server_quota_backend_bytes
# Leader elections over the last hour. Should be flat in a healthy cluster.
increase(etcd_server_leader_changes_seen_total[1h])
You can also use this Prometheus server as a data source for Grafana. KubeDB does not ship a Grafana dashboard for etcd, but since these are upstream etcd metric names, community etcd dashboards work as-is.
Cleaning up
$ kubectl delete etcd -n demo builtin-prom-etcd
$ kubectl delete -n monitoring deployment.apps/prometheus
$ kubectl delete -n monitoring configmap/prometheus-config
$ kubectl delete clusterrole.rbac.authorization.k8s.io/prometheus
$ kubectl delete -n monitoring serviceaccount/prometheus
$ kubectl delete clusterrolebinding.rbac.authorization.k8s.io/prometheus
$ kubectl delete ns demo
$ kubectl delete ns monitoring
Next Steps
- Monitor your etcd cluster with KubeDB using the
out-of-the-boxPrometheus operator. - Run your etcd cluster with TLS/SSL encryption.
- Tune the backend quota and compaction policy with custom configuration.
- Want to hack on KubeDB? Check our contribution guidelines.
































