はじめに
自宅サーバーで運用している Kubernetes クラスタでは、Pod の異常終了や Scheduling の失敗といった Kubernetes イベントを Slack に通知するために kubernetes-event-exporter を利用していました。ただし、2 年ほど前から開発が止まっていることと、将来的に特定のイベントを metrics として時系列データ化したいなど kubernetes-event-exporter だけでは実現できないユースケースがあったため、 OpenTelemetry Collector をベースとしたイベント通知の基盤に置き換えることにしました。
Kubernetes イベントの収集には OpenTelemetry Collector Contrib の k8seventsreceiver を利用し、Slack への通知部分は既存のコンポーネントでは要件を満たせなかったため、notificationexporter という Exporter を自作しています。
本記事では、kubernetes-event-exporter からこの OpenTelemetry Collector ベースの通知基盤に移行した内容について紹介します。
前提
本記事では以下のバージョンを前提としています。
- OpenTelemetry Collector(Contrib コンポーネント): v0.160.0
- OpenTelemetry Collector Core: v1.66.0
- Go: 1.26
自作した notificationexporter は、以下のリポジトリで公開しています。
kubernetes-event-exporter による通知
これまでは、kubernetes-event-exporter で以下のような設定を行い、Slack へ Kubernetes のイベント通知を行っていました。
config:
route:
routes:
- match:
- receiver: "slack"
type: "Warning"
receivers:
- name: "slack"
slack:
token: "$SLACK_TOKEN"
channel: "#k8s-events"
message: "{{ .Message }}"
fields:
cluster: "{{ .ClusterName }}"
namespace: "{{ .InvolvedObject.Namespace }}"
kind: "{{ .InvolvedObject.Kind }}"
name: "{{ .InvolvedObject.Name }}"
reason: "{{ .Reason }}"
count: "{{ .Count }}"
type: "Warning" の Kubernetes イベントのみを対象に、Slack Bot Token を使って #k8s-events チャンネルに通知するというシンプルな構成です。

OpenTelemetry Collector ベースの通知基盤
新しい通知基盤は、以下のようなパイプラインで構成しています。
k8s_events (Receiver)
→ memory_limiter (Processor)
→ filter (Processor)
→ transform (Processor)
→ routing (Connector)
→ notification (Exporter)
k8s_events Receiver で Kubernetes イベントを収集する
Kubernetes イベントの収集には、OpenTelemetry Collector Contrib が提供している k8seventsreceiver を利用しています。
receivers:
k8s_events:
auth_type: serviceAccount
dedup_interval: 10m
auth_type: serviceAccount を指定することで、Collector に紐付けた ServiceAccount の権限で Kubernetes API から Event リソースを watch します。RBAC は以下のように、events リソースへの get/list/watch のみを許可する ClusterRole を用意しています。
apiVersion: v1
kind: ServiceAccount
metadata:
name: otel-collector-k8s-events
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: otel-collector-k8s-events
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: otel-collector-k8s-events
subjects:
- kind: ServiceAccount
name: otel-collector-k8s-events
namespace: opentelemetry
roleRef:
kind: ClusterRole
name: otel-collector-k8s-events
apiGroup: rbac.authorization.k8s.io
Filter / Transform Processor で通知対象を絞り込む
収集した Kubernetes イベントは filter Processor で Warning のみに絞り込み、transform Processor で通知用の属性を組み立てています。
processors:
filter/warning_only:
error_mode: ignore
log_conditions:
- 'log.severity_text != "Warning"'
transform/enrich:
error_mode: ignore
log_statements:
- 'set(log.attributes["notify.summary"], Concat(["[", log.attributes["k8s.namespace.name"], "] ", resource.attributes["k8s.object.kind"], "/", resource.attributes["k8s.object.name"], ": ", log.attributes["k8s.event.reason"]], ""))'
OTTL(OpenTelemetry Transformation Language)でイベントの属性から通知サマリを組み立てておくことで、後段の Exporter 側のテンプレートをシンプルに保つことができます。
kubernetes-event-exporter では、例えば OOM の Event は Node に対して発行されるため Namespace や Pod の情報を持っておらず、プロセス名しか出力されないため、それだけではどのワークロードで発生した問題か切り分けにくいという課題がありました。しかし、Transform Processor 等を利用すれば容易に付加情報を付与できるため、こうした課題にも対応しやすくなります。
filter Processor の条件も OTTL で記述するため、log.severity_text 以外の属性(Namespace や Reason、対象リソースの種類など)を組み合わせた柔軟なフィルタクエリを追加することも可能です。現状は Warning のみを対象にしていますが、今後特定の Namespace や Reason を通知対象から除外・追加するといった要件にも、このフィルタ条件を拡張するだけで対応できます。
Routing Connector で通知先を振り分ける
routing Connector を挟んでいるのが、この構成の特徴の一つです。現状は以下のように全件を単一の Pipeline に流す設定のみですが、将来的に Namespace や Reason ごとに条件を追加し、複数の通知先に振り分けられるように設計しています。
connectors:
routing/notify:
default_pipelines: [logs/notify_default]
table:
- context: log
condition: "true"
pipelines: [logs/notify_default]
notificationexporter で Slack に通知する
Slack への通知部分には、既存の Exporter では要件を満たせなかったため notificationexporter を自作しました。ログレコードをユーザー定義の Go template で描画し、任意の HTTP エンドポイントに POST するだけのシンプルな Exporter です。
type Config struct {
Method string `mapstructure:"method"`
Type string `mapstructure:"type"` // webhook / slack_app / discord_webhook / discord_bot
SlackApp SlackAppConfig `mapstructure:"slack_app"`
DiscordBot DiscordBotConfig `mapstructure:"discord_bot"`
BodyTemplate string `mapstructure:"body_template"`
BodyTemplateFile string `mapstructure:"body_template_file"`
Condition string `mapstructure:"condition"` // OTTL の log_record 条件
}
BodyTemplate は文字列を直接埋め込む場合のフィールドですが、今回のようにファイルとして管理したい場合は BodyTemplateFile(body_template_file)を利用します。
type に webhook を指定すると Slack Incoming Webhook 宛にそのまま送信でき、slack_app を指定すると Slack の Web API(chat.postMessage)を Bot Token 経由で呼び出すこともできます。今回は Incoming Webhook を利用しており、設定は以下のようになっています。
exporters:
notification/default:
type: webhook
endpoint: "${env:SLACK_WEBHOOK_DEFAULT}"
body_template_file: /etc/otelcol/templates/slack-default.tmpl
timeout: 5s
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 60s
max_elapsed_time: 5m
sending_queue:
enabled: true
num_consumers: 1
queue_size: 256
body_template_file で指定したテンプレートでは Slack の Block Kit を使ったメッセージを組み立てており、Severity に応じて色を変える、Namespace や対象リソース名をフィールドとして表示するなど、kubernetes-event-exporter のときよりも見やすい通知に変更しています。テンプレートに Kubernetes イベントの任意の文字列(Body や Reason など)をそのまま埋め込むと JSON が壊れる可能性があるため、toJson という template 関数でエスケープするようにしています。
{{ printf "[%s] %s" .SeverityText .Body | toJson }}
OCB でカスタムディストリビューションをビルドする
k8seventsreceiver と notificationexporter を組み合わせた Collector のバイナリは、OpenTelemetry Collector Builder(OCB) を使ってビルドしています。
dist:
name: otelcol-k8s-events
version: 0.1.0
receivers:
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8seventsreceiver v0.160.0
processors:
- gomod: go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.160.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.160.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.160.0
connectors:
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector v0.160.0
exporters:
- gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.160.0
- gomod: github.com/ucpr/opentelemetry-collector-components/exporters/notificationexporter v0.0.0-...
コンポーネントのバージョンは、クラスタ内で既に稼働している別用途の OpenTelemetry Collector(otel/opentelemetry-collector-contrib:0.160.0)に合わせて v0.160.0 に固定しています。ビルドした Collector は FROM scratch の Docker イメージとして non-root ユーザーで実行するようにし、GitHub Actions で ghcr.io/ucpr/otelcol-k8s-events に push しています。
移行してみて
kubernetes-event-exporter を削除する前に、新旧の通知基盤を並行稼働させて動作を確認してから切り替えを行いました。

旧環境では Slack Bot Token による通知のみに対応していましたが、notificationexporter は Slack Incoming Webhook・Slack App・Discord Webhook・Discord Bot に対応した汎用的な Exporter として作ったため、今後 Kubernetes イベント以外の通知にも同じコンポーネントを流用できそうです。
おわりに
本記事では、kubernetes-event-exporter から OpenTelemetry Collector と自作の notificationexporter を使った Kubernetes イベントの Slack 通知基盤への移行について紹介しました。OpenTelemetry Collector の Receiver / Processor / Connector を組み合わせることで、kubernetes-event-exporter が提供していた機能を再現しつつ、Slack 以外の通知先にも対応できる拡張性のある構成にできたと感じています。
本記事において、異なっている説明や表現がありましたらご連絡ください。
参考
- mustafaakin/kubernetes-event-exporter
- Kubernetes Eventを保持したりSlack通知させたりする
- open-telemetry/opentelemetry-collector-contrib - k8seventsreceiver
- open-telemetry/opentelemetry-collector-contrib - routingconnector
- open-telemetry/opentelemetry-operator
- open-telemetry/opentelemetry-collector - builder
- ucpr/opentelemetry-collector-components