Actively maintained fork of resmoio/kubernetes-events-exporter (itself a fork of the original Opsgenie exporter). The upstream repository has been dormant since Resmo was acquired by JumpCloud in 2024. This fork consolidates the best fixes and features from multiple community forks (ClickHouse, LinkedIn, honestica) into a single, maintained project.
This tool allows exporting the often missed Kubernetes events to various outputs so that they can be used for observability or alerting purposes. You won't believe what you are missing.
- Update event handling — The watcher now processes event updates (not just additions), fixing the #1 cause of missing events. Previously, when Kubernetes aggregated events and incremented
count, the update was silently dropped. (ported from ClickHouse fork) - Improved event age calculation — Uses
max(creationTimestamp, lastTimestamp)to determine event age, preventing false discards when timestamps are inconsistent. (ported from LinkedIn fork) - Cache lookup validation — Validates that
UIDandResourceVersionexist before using them as cache keys, preventing panics on events with incomplete references. (ported from LinkedIn fork) - Loki TLS transport fix — The Loki sink now correctly uses its configured TLS transport instead of ignoring it.
- Prometheus sink — Convert Kubernetes events into Prometheus gauge metrics, filterable by resource kind and reason. Enables alerting on events via Prometheus/Grafana.
- Loki basic auth — New
usernameandpasswordfields for Loki, supporting Grafana Enterprise/Cloud. - Loki stream label templates — Stream labels now support Go templates (e.g.,
"{{ .InvolvedObject.Namespace }}"). - ClusterEnvironment field — New config option to tag events with an environment label (prod, staging, etc.).
- SNS FIFO support — New
messageGroupIdfield enables ordered delivery via AWS SNS FIFO topics.
- Restructured codebase from
pkg/tocmd/+internal/following standard Go project layout - Updated to Go 1.26, Elasticsearch client v8, IBM/sarama (Kafka)
- Fixed pre-existing build issues (LRU cache API, ES v8 compatibility)
# Install from this repo
helm install kubernetes-events-exporter ./charts/kubernetes-events-exporter \
--namespace monitoring --create-namespace
# Or with custom values
helm install kubernetes-events-exporter ./charts/kubernetes-events-exporter \
--namespace monitoring --create-namespace \
-f my-values.yamlMinimal my-values.yaml to send events to Loki:
config:
logLevel: info
logFormat: json
maxEventAgeSeconds: 60
route:
routes:
- match:
- receiver: "loki"
receivers:
- name: "loki"
loki:
url: http://loki.monitoring:3100/loki/api/v1/push
streamLabels:
app: kube-events
namespace: "{{ .InvolvedObject.Namespace }}"See charts/kubernetes-events-exporter/values.yaml for all options including resources, tolerations, serviceMonitor, and leader election.
Note: The Bitnami Helm chart was deprecated in August 2025. This is the replacement first-party chart.
Configuration is done via a YAML file (ConfigMap when running in Kubernetes). The tool watches all events and lets you filter, route, and deliver them to various sinks.
logLevel: info # debug | info | warn | error
logFormat: json # json | text
maxEventAgeSeconds: 60 # discard events older than this (default: 5)
kubeQPS: 100 # K8s API rate limit (QPS)
kubeBurst: 500 # K8s API rate limit (burst)
namespace: "" # watch a specific namespace (default: all)
clusterName: "prod-us-1" # optional, added to every event as clusterName
clusterEnvironment: "prod" # optional, added to every event as clusterEnvironment
metricsNamePrefix: "event_exporter_" # prefix for Prometheus metrics
omitLookup: false # skip enriching events with object metadata
cacheSize: 1024 # LRU cache size for object metadata
leaderElection:
enabled: true # enable leader election for HA deployments
leaderElectionID: "kubernetes-events-exporter"The routing configuration is a tree that allows flexible event filtering and delivery.
route:
routes:
# Dump all events
- match:
- receiver: dump
# Drop test namespaces and Normal events, send the rest to alerting
- drop:
- namespace: "test-.*"
- type: "Normal"
match:
- receiver: "critical-events-queue"
# Route specific kinds to owners via Slack
- match:
- kind: "Pod|Deployment|ReplicaSet"
labels:
version: "dev"
receiver: "slack"- A
matchrule is exclusive: all conditions must match. droprules are evaluated first to filter out events.matchrules in a route are independent. If an event matches a rule, it goes down its subtree.- Routes can have sub-routes, forming a tree. Routing starts from the root.
Reference environment variables in config with ${VAR_NAME}:
receivers:
- name: "es"
elasticsearch:
apiKey: ${ELASTIC_API_KEY}receivers:
- name: "dump"
elasticsearch:
hosts:
- http://localhost:9200
index: kube-events
indexFormat: "kube-events-{2006-01-02}" # time-based indices
username: # optional
password: # optional
cloudID: # optional
apiKey: # optional
headers: # optional
useEventID: true|false # update same doc (useful for event count)
deDot: true|false # replace dots in label keys with underscores
layout: # optional, custom payload
tls:
insecureSkipVerify: true|false
serverName: # optional
caFile: # optionalreceivers:
- name: "dump"
opensearch:
hosts:
- http://localhost:9200
index: kube-events
indexFormat: "kube-events-{2006-01-02}"
username: # optional
password: # optional
useEventID: true|false
deDot: true|false
layout: # optional
tls:
insecureSkipVerify: true|false
serverName: # optional
caFile: # optionalSupports basic auth, TLS, custom headers, and Go template stream labels.
receivers:
- name: "loki"
loki:
url: http://127.0.0.1:3100/loki/api/v1/push
username: # optional, for basic auth
password: # optional, for basic auth
headers: # optional
X-Scope-OrgID: tenantID
streamLabels:
app: kube-events
# Go templates are supported in stream labels:
namespace: "{{ .InvolvedObject.Namespace }}"
kind: "{{ .InvolvedObject.Kind }}"
layout: # optional
tls:
insecureSkipVerify: true|false # for self-signed certs
caFile: # optionalExpose Kubernetes events as Prometheus gauge metrics. Useful for alerting on events via Prometheus/Grafana without needing a separate log pipeline.
receivers:
- name: "prometheus"
prometheus:
eventsMetricsNamePrefix: "event_exporter_" # default
reasonFilter:
Pod:
- "FailedMount"
- "CrashLoopBackOff"
- "OOMKilled"
Node:
- "NotReady"
- "MemoryPressure"This creates gauge metrics like event_exporter_pod_event_count{pod="nginx-abc", namespace="default", reason="CrashLoopBackOff"}.
receivers:
- name: "alerts"
opsgenie:
apiKey: xxx
priority: "P3"
message: "Event {{ .Reason }} for {{ .InvolvedObject.Namespace }}/{{ .InvolvedObject.Name }}"
alias: "{{ .UID }}"
description: "<pre>{{ toPrettyJson . }}</pre>"
tags:
- "event"
- "{{ .Reason }}"
- "{{ .InvolvedObject.Kind }}"receivers:
- name: "slack"
slack:
token: YOUR-API-TOKEN-HERE
channel: "#alerts"
message: "{{ .Message }}"
color: # optional
title: # optional
fields:
namespace: "{{ .Namespace }}"
reason: "{{ .Reason }}"receivers:
- name: "alerts"
webhook:
endpoint: "https://my-service.com/events"
headers:
X-API-KEY: "123"
User-Agent: kubernetes-events-exporter 1.0
layout: # optionalreceivers:
- name: "kafka"
kafka:
clientId: "kubernetes"
topic: "kube-event"
brokers:
- "localhost:9092"
compressionCodec: "snappy"
tls:
enable: true
certFile: "kafka-client.crt"
keyFile: "kafka-client.key"
caFile: "kafka-ca.crt"
sasl:
enable: true
username: "producer"
password: "password"
mechanism: "sha512" # plain, sha256, sha512
layout: # optionalreceivers:
- name: "kinesis"
kinesis:
streamName: "events-pipeline"
region: us-west-2
layout: # optionalreceivers:
- name: "firehose"
firehose:
deliveryStreamName: "events-pipeline"
region: us-west-2
layout: # optionalSupports standard and FIFO topics.
receivers:
- name: "sns"
sns:
topicARN: "arn:aws:sns:us-east-1:123456789:mytopic"
region: "us-west-2"
messageGroupId: "k8s-events" # optional, for FIFO topics
layout: # optionalreceivers:
- name: "sqs"
sqs:
queueName: "kube-events"
region: us-west-2
layout: # optionalreceivers:
- name: "pubsub"
pubsub:
gcloud_project_id: "my-project"
topic: "kube-event"
create_topic: falsereceivers:
- name: "ms_teams"
teams:
endpoint: "https://outlook.office.com/webhook/..."
layout: # optionalreceivers:
- name: "eventbridge"
eventbridge:
detailType: "deployment"
source: "cd"
eventBusName: "default"
region: "ap-southeast-1"
details:
message: "{{ .Message }}"
namespace: "{{ .Namespace }}"receivers:
- name: "bigquery"
bigquery:
location: "US"
project: "my-project"
dataset: "kube_events"
table: "events"
credentials_path: "/secrets/gcp.json"
batch_size: 100
max_retries: 3
interval_seconds: 30
timeout_seconds: 60receivers:
- name: "file"
file:
path: "/tmp/events.log"
layout: # optionalreceivers:
- name: "dump"
stdout:
deDot: true|falsereceivers:
- name: "pipe"
pipe:
path: "/dev/stdout"
deDot: true|falsereceivers:
- name: "syslog"
syslog:
network: "tcp"
address: "127.0.0.1:11514"
tag: "k8s.event"receivers:
- name: "alerts"
opscenter:
title: "{{ .Message }}"
category: "{{ .Reason }}"
description: "Event in {{ .InvolvedObject.Namespace }}/{{ .InvolvedObject.Name }}"
region: "us-east-1"
source: "production"
severity: "6" # optional
priority: "6" # optional
notifications: # optional, SNS ARNs
- "arn:aws:sns:..."Receivers that support layout allow custom payloads using Go templates with Sprig functions. This supports recursive map definitions for building arbitrary JSON:
layout:
eventType: "kubernetes-event"
createdAt: "{{ .GetTimestampMs }}"
details:
message: "{{ .Message }}"
reason: "{{ .Reason }}"
type: "{{ .Type }}"
count: "{{ .Count }}"
kind: "{{ .InvolvedObject.Kind }}"
name: "{{ .InvolvedObject.Name }}"
namespace: "{{ .Namespace }}"
labels: "{{ toJson .InvolvedObject.Labels }}"-
If you see
client-side throttlingwarnings, increase API rate limits:kubeQPS: 100 # ~1/5 of burst kubeBurst: 500 # roughly match your events per minute
-
If events are still dropped without throttling, increase the age cutoff:
maxEventAgeSeconds: 60
This happens when the involved object is deleted before metadata can be enriched. These events are still processed but without labels/annotations. If you see this constantly, consider setting omitLookup: true to skip metadata enrichment entirely.
If you notice events are missing, ensure the watcher is processing updates (this fork fixes this by default). For high-volume clusters, also check:
kubeQPSandkubeBurstare high enoughmaxEventAgeSecondsis set appropriately (too low = events dropped, too high = stale events on restart)- Leader election is configured for HA deployments to avoid duplicate/missed events
make build # go build (runs tidy + vet first)
make test # go test -cover -v ./...
make fmt # gofmt -s -l -w .
make build-image # docker buildThis project is licensed under the Apache License 2.0 - see the original repository for details.