# ============================================================================= # Kubernetes Event Exporter - Phase O-2.2 # ============================================================================= # 建立者: Claude Code (首席架構師) # 日期: 2026-04-02 (台北時間) # 用途: 將 K8s Event 轉為結構化 Log 送往 SigNoz,保留 30 天 # 解決: K8s Event 預設僅保留 ~1 小時的致命盲區 # ============================================================================= --- apiVersion: v1 kind: ServiceAccount metadata: name: event-exporter namespace: observability --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: event-exporter rules: - apiGroups: [""] resources: ["events"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: event-exporter roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: event-exporter subjects: - kind: ServiceAccount name: event-exporter namespace: observability --- apiVersion: v1 kind: ConfigMap metadata: name: event-exporter-config namespace: observability data: config.yaml: | # Phase O-2.2: K8s Event → SigNoz (OTLP HTTP) logLevel: info logFormat: json # P0-OBS-001: deploy bursts can reach the watcher 5-15 seconds late. # Preserve them for RCA and avoid one API GET per involved object; the OTLP # layout below only consumes fields already present on the Event itself. maxEventAgeSeconds: 120 omitLookup: true route: routes: # Warning/Error 全量保留;只排除已知高頻、低診斷價值的 Normal 事件。 # 單一 receiver 避免未列出的 Normal 事件被兩條 route 重複送出。 - match: - receiver: signoz-otlp drop: - type: "Normal" reason: "Scheduled" - type: "Normal" reason: "Pulling" - type: "Normal" reason: "Pulled" - type: "Normal" reason: "Created" - type: "Normal" reason: "Started" receivers: - name: signoz-otlp webhook: endpoint: http://192.168.0.188:24318/v1/logs headers: Content-Type: application/json # OTLP/HTTP JSON envelope。舊版的平面 JSON 雖會得到 HTTP 200, # 但 collector 會把它當成空請求,無任何 durable log write。 layout: resourceLogs: - resource: attributes: - key: service.name value: stringValue: kubernetes-event-exporter - key: k8s.cluster.name value: stringValue: awoooi-k3s scopeLogs: - scope: name: awoooi.k8s.events logRecords: - severityText: "{{ .Type }}" body: stringValue: "{{ .Message }}" attributes: - key: k8s.event.reason value: stringValue: "{{ .Reason }}" - key: k8s.event.type value: stringValue: "{{ .Type }}" - key: k8s.event.action value: stringValue: "{{ .Action }}" - key: k8s.event.count value: stringValue: "{{ .Count }}" - key: k8s.namespace.name value: stringValue: "{{ .InvolvedObject.Namespace }}" - key: k8s.object.kind value: stringValue: "{{ .InvolvedObject.Kind }}" - key: k8s.object.name value: stringValue: "{{ .InvolvedObject.Name }}" - key: k8s.event.source.component value: stringValue: "{{ .Source.Component }}" - key: k8s.event.source.host value: stringValue: "{{ .Source.Host }}" - key: k8s.event.first_timestamp value: stringValue: "{{ .FirstTimestamp }}" - key: k8s.event.last_timestamp value: stringValue: "{{ .LastTimestamp }}" --- apiVersion: apps/v1 kind: Deployment metadata: name: event-exporter namespace: observability labels: app.kubernetes.io/name: event-exporter phase: o-2 spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: event-exporter template: metadata: labels: app.kubernetes.io/name: event-exporter spec: serviceAccountName: event-exporter # PSA restricted 合規 securityContext: runAsNonRoot: true runAsUser: 65534 seccompProfile: type: RuntimeDefault containers: - name: event-exporter image: 192.168.0.110:5000/awoooi/runtime-mirror/event-exporter@sha256:d90516e5a8277a725867df3e1d084fe6533c63c49bc527696f45dc2e6d7d7ddb args: - -conf=/etc/event-exporter/config.yaml securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] resources: requests: cpu: 20m memory: 32Mi limits: cpu: 100m memory: 64Mi volumeMounts: - name: config mountPath: /etc/event-exporter readOnly: true volumes: - name: config configMap: name: event-exporter-config