Skip to content

feature: Implement Event stream for Application/Instance/Service #1472#1487

Open
ambiguous-pointer wants to merge 3 commits into
apache:developfrom
ambiguous-pointer:feature/k8s-event
Open

feature: Implement Event stream for Application/Instance/Service #1472#1487
ambiguous-pointer wants to merge 3 commits into
apache:developfrom
ambiguous-pointer:feature/k8s-event

Conversation

@ambiguous-pointer

Copy link
Copy Markdown
Contributor

This change introduces a complete event timeline feature for applications, instances, and services
based on discussion #1474 and
issue #1472.

It ingests Kubernetes events via the cluster API and records platform events from ZooKeeper
and Nacos registration centers, merging them into a unified, time-sorted timeline with full
frontend visualization.


📌 What's New

Kubernetes Event Ingestion

  • K8sEvent resource type
    K8sEventResource with proto K8sEvent spec (api/mesh/v1alpha1/k8s_event.proto +
    Go generated code). Captures namespace, reason, message, type, involved object kind/name,
    source component/host, first/last timestamps, count, and a static EventSource: "KUBERNETES" marker.

  • K8sEventListerWatcher
    New K8sEventListerWatcher (pkg/engine/kubernetes/listerwatcher/k8s_event.go) watches
    api/v1/events across all namespaces using client-go's ListWatch + TransformFunc.
    Registered in the K8s EngineFactory alongside the existing PodListerWatcher.

  • K8sEvent store indexes (pkg/core/store/index/k8s_event.go)
    ByK8sEventInvolvedObjKind, ByK8sEventInvolvedObjName, ByK8sEventType, ByK8sEventSource
    — enables efficient queries by mesh, involved object, event type, and source.

Platform Event Recording (ZooKeeper & Nacos)

  • PlatformEvent resource type
    PlatformEventResource with PlatformEvent spec (pkg/core/resource/apis/mesh/v1alpha1/platformevent_types.go).
    Carries AppName, InstanceName, InstanceIP, ServiceName, Type (normal/warning),
    Message, Source (Zookeeper/Nacos), SourceType (provider/consumer/config), Category,
    Action, and EventTime.

  • Shared platform event recorder (pkg/core/discovery/subscriber/platform_event_recorder.go)
    recordPlatformEvent() utility that creates PlatformEventResource instances and persists
    them directly to the store via storeRouter.ResourceKindRoute(PlatformEventKind).

  • ZooKeeper config change events
    ZKConfigEventSubscriber (pkg/core/discovery/subscriber/zk_config.go) records
    tag-route, condition-route, and dynamic-config added/updated/deleted events
    via recordConfigPlatformEvent().

  • ZooKeeper metadata change events
    ZKMetadataEventSubscriber (pkg/core/discovery/subscriber/zk_metadata.go) records
    provider and consumer metadata added/updated events via recordMetadataPlatformEvent().

  • Nacos instance registration/deregistration events
    NacosServiceEventSubscriber (pkg/core/discovery/subscriber/nacos_service.go)
    records Nacos instance registered, deregistered, and updated events with
    application name, IP, and port via recordNacosInstanceEvents().

  • Nacos consumer metadata change events
    Same subscriber records Nacos consumer metadata added / updated events
    via recordNacosConsumerEvents().

  • PlatformEvent store indexes (pkg/core/store/index/platform_event.go)
    ByPlatformEventAppName, ByPlatformEventInstanceName, ByPlatformEventInstanceIP,
    ByPlatformEventServiceName, ByPlatformEventSourceType — enables per-entity event queries.

Unified Event Timeline Query Service

  • Console API endpoints (pkg/console/service/event.go)
Endpoint Description
GET /application/event Lists K8s + Platform events for an application
GET /instance/event Lists K8s + Platform events for an instance (by name/IP)
GET /service/event Lists K8s + Platform events for a service

Each endpoint queries both K8sEvent and PlatformEvent stores in parallel,
merges results, sorts by time descending, and returns paginated unified timeline entries.

{
    "code": "Success",
    "message": "success",
    "data": {
        "list": [
            {
                "time": "2026-06-07 17:39:14",
                "type": "warning",
                "message": "Error updating Endpoint Slices...",
                "source": "endpoint-slice-controller"
            },
            {
                "time": "2026-06-07 10:25:04",
                "type": "normal",
                "message": "Zookeeper provider metadata added: demo-app → com.example.TestService",
                "source": "Zookeeper"
            },
            {
                "time": "2026-06-07 10:25:05",
                "type": "normal",
                "message": "Nacos instance registered: shop-order 10.244.1.29:20882",
                "source": "Nacos"
            }
        ],
        "total": 78
    }
}

Frontend Event Timeline Visualization

  • EventTimeline shared component (ui-vue3/src/components/EventTimeline.vue)
    Reusable Vue component with alternating left/right timeline layout using Ant Design.
    Each event node displays: timestamp, icon (check-circle for normal, warning for warnings),
    message text, and source tag. Supports infinite scroll with "load more" pagination.
    Degraded/expired events show an empty state with "过期事件不会存储" (expired events
    are not stored) watermark.

  • Application event tab (ui-vue3/src/views/resources/applications/tabs/event.vue)
    Wires the EventTimeline component into the application detail page as an "事件" tab.
    Queries events filtered by appName and current mesh.

  • Instance event tab (ui-vue3/src/views/resources/instances/tabs/event.vue)
    Wires the EventTimeline component into the instance detail page as an "事件" tab.
    Queries events filtered by instanceName and instanceIP.

  • Service event tab (ui-vue3/src/views/resources/services/tabs/event.vue)
    Wires the EventTimeline component into the service detail page as an "事件" tab.
    Queries events filtered by serviceName.

  • Event tab restored in routes (ui-vue3/src/router/defaultRoutes.ts)
    Event tab routes previously commented out are now enabled for all three resource types.

Minor Improvements

  • EventBus log level (pkg/core/events/component.go)
    "no subscriber for resource" message downgraded from INFO to DEBUG to reduce
    log noise (991 occurrences per 5 minutes for K8sEvent and Service).

📁 Affected Areas / Review Checklist

Area Files What to Review
API api/mesh/v1alpha1/k8s_event.go, k8s_event.proto New proto + generated code for K8sEvent
Console API pkg/console/service/event.go, handler/event.go, model/event.go, router/router.go Event query logic, merge + sort + pagination
Core Component pkg/core/events/component.go Log level change
Core Component pkg/core/resource/apis/mesh/v1alpha1/k8sevent_types.go, platformevent_types.go Resource type definitions
Core Component pkg/core/store/index/k8s_event.go, platform_event.go Index definitions for queries
Discovery Subscriber pkg/core/discovery/subscriber/nacos_service.go, zk_config.go, zk_metadata.go PlatformEvent recording side-effects
Discovery Subscriber pkg/core/discovery/subscriber/platform_event_recorder.go Shared recording utility
Engine pkg/engine/kubernetes/factory.go, listerwatcher/k8s_event.go K8s Event ListWatcher registration + transform
Frontend ui-vue3/src/components/EventTimeline.vue Reusable timeline component
Frontend ui-vue3/src/views/resources/{applications,instances,services}/tabs/event.vue Event tab pages
Frontend ui-vue3/src/api/service/{app,instance,service}.ts Event API calls
Frontend ui-vue3/src/router/defaultRoutes.ts Route restoration
Frontend ui-vue3/src/types/api.ts EventItem type definition

🔄 Data Flow Architecture

┌──────────────┐   ┌──────────────────┐   ┌─────────────────┐
│ K8s API      │   │ ZK /services/*   │   │ Nacos naming    │
│ /api/v1/     │   │ /dubbo/metadata/ │   │ service list    │
│ events       │   │ /dubbo/config/   │   │ / config page   │
└──────┬───────┘   └────────┬─────────┘   └────────┬────────┘
       │                    │                      │
       ▼                    ▼                      ▼
┌─────────────────────────────────────────────────────────┐
│ ListerWatcher (List + Watch via client-go / ZK client / │
│               nacos-sdk-go)                             │
│                                                         │
│  K8sEventLW  PodLW  ZKLW(x4)  NacosLW(x6)             │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│ Informer (DeltaFIFO → Store + EventBus)                 │
│  HandleDeltas():                                        │
│    indexer.Add/Update/Delete(resource)  → Store         │
│    EmitEvent()  → eventBus.Send(ResourceChangedEvent)   │
└────────────────────────┬────────────────────────────────┘
                         │
            ┌────────────┴────────────┐
            ▼                         ▼
┌────────────────────┐   ┌────────────────────────────────┐
│ Store (PG/Memory/  │   │ EventBus → Subscribers          │
│  MySQL)            │   │                                 │
│                    │   │ ZKConfigEventSubscriber         │
│ K8sEvent           │   │   └→ recordConfigPlatformEvent()│
│ PlatformEvent      │   │ ZKMetadataEventSubscriber       │
│ Application        │   │   └→ recordMetadataPlatformEvt()│
│ Instance           │   │ NacosServiceEventSubscriber     │
│ Service            │   │   └→ recordNacosInstanceEvents()│
└────────┬───────────┘   │   └→ recordNacosConsumerEvents()│
         │               └───────────────┬────────────────┘
         │                               │
         │              ┌────────────────▼─────────────┐
         │              │ recordPlatformEvent()        │
         │              │  → eventStore.Add(           │
         │              │      PlatformEventResource)  │
         │              └──────────────┬───────────────┘
         │                             │
         └──────────────┬──────────────┘
                        ▼
┌─────────────────────────────────────────────────────────┐
│ Console API (event.go)                                   │
│  ListApplicationEvents() / ListInstanceEvents() /        │
│  ListServiceEvents()                                     │
│                                                          │
│  Query K8sEvent + PlatformEvent stores                   │
│  → Merge → Sort by time DESC → Paginate                  │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│ Frontend EventTimeline.vue                               │
│  Alternating L/R timeline with infinite scroll           │
│  ┌──────────────────────────────────────────────────┐   │
│  │ 2026-06-07 17:39:14                              │   │
│  │ ⚠ Error updating Endpoint Slices...              │   │
│  │                               endpoint-slice-ctrl│   │
│  ├──────────────────────────────────────────────────┤   │
│  │ 2026-06-07 10:25:04                              │   │
│  │ ✓ Zookeeper provider metadata added:             │   │
│  │   demo-app → com.example.TestService             │   │
│  │                                    Zookeeper     │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

🧪 E2E Verification

Tested against a 6-node kind cluster (cluster-ha-new, k8s v1.35.1) with:

Component Status Events Recorded
K8s Engine 78 K8sEvent rows Pod scheduling, creation, readiness probes, restart
ZooKeeper discovery 11 PlatformEvent rows Consumer/provider metadata added/updated
Nacos2 discovery 2 PlatformEvent rows Instance registered/deregistered
PostgreSQL persistence 9 PlatformEvent rows persisted across restart Events survive backend restart
Frontend event tabs All 3 resource types verified Timeline, source tags, icons render correctly
Registry dropdown Switching between ZK / Nacos / default mesh Events filter correctly by mesh

🔮 Known Limitations

  1. K8s events use mesh "default" — K8s events are not associated with any discovery
    mesh. The frontend registry selector must be set to "default" (or unset) to view
    K8s events. There is a UX gap: the registry dropdown does not show "default" as an
    explicit option.

  2. No event TTL / cleanup — Events accumulate indefinitely in the store. There is
    no retention policy, TTL, or automatic cleanup mechanism for any store backend.

  3. No subscriber for K8sEvent and Service — K8sEvent and Service resources are
    stored but no event subscriber processes them (by design — they only need storage).
    The EventBus emits a "no subscriber" debug log for these kinds.

  4. PlatformEvent bypasses EventBus — PlatformEvents are created as side-effects
    within subscriber ProcessEvent methods and stored directly via
    eventStore.Add(), bypassing the EventBus entirely. This means PlatformEvents
    themselves cannot be observed by downstream subscribers.

@ambiguous-pointer ambiguous-pointer changed the title [Feature] Implement Event stream for Application/Instance/Service #1472 feature: Implement Event stream for Application/Instance/Service #1472 Jun 7, 2026
@robocanic

Copy link
Copy Markdown
Contributor

Please fix the CI problem @ambiguous-pointer

@sonarqubecloud

Copy link
Copy Markdown

@ambiguous-pointer

Copy link
Copy Markdown
Contributor Author

Please fix the CI problem @ambiguous-pointer

Apologies for the formatting oversight. I have run the linter locally, fixed the Vue component styles, and force-pushed the updates. The CI check is now green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an end-to-end “event timeline” feature across the backend and ui-vue3, ingesting Kubernetes Event objects and recording registry-side (ZooKeeper/Nacos) platform events, then exposing unified, time-sorted, paginated timelines for Applications / Instances / Services with frontend visualization.

Changes:

  • Introduces new resource kinds (K8sEvent, PlatformEvent) with store indexes, plus K8s lister-watcher ingestion and registry subscriber-side PlatformEvent recording.
  • Adds Console API endpoints (/application/event, /instance/event, /service/event) that merge and paginate K8s + Platform events.
  • Adds a reusable Vue timeline component and wires event tabs + routes + i18n + mocks to display the unified event stream.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
ui-vue3/src/views/resources/services/tabs/event.vue Replaces placeholder timeline with EventTimeline + paginated API loading for service events
ui-vue3/src/views/resources/instances/tabs/event.vue Adds EventTimeline-based instance event tab with pagination
ui-vue3/src/views/resources/instances/tabs/detail.vue Adds “instance missing/offline” empty state and silences detail fetch errors
ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue Silences errors when fetching instance lifecycle state
ui-vue3/src/views/resources/applications/tabs/event.vue Replaces old custom timeline UI with shared EventTimeline + pagination
ui-vue3/src/types/api.ts Adds EventItem type for unified timeline entries
ui-vue3/src/router/defaultRoutes.ts Enables event tabs in routes for application/instance/service
ui-vue3/src/mocks/handlers/service.ts Adds mock /service/event response
ui-vue3/src/mocks/handlers/instance.ts Adds mock /instance/event response
ui-vue3/src/mocks/handlers/app.ts Updates mock application events to unified EventItem format with totals
ui-vue3/src/components/EventTimeline.vue New shared timeline component with infinite-scroll loading
ui-vue3/src/base/i18n/zh.ts Adds eventExpiryHint translation
ui-vue3/src/base/i18n/en.ts Adds eventExpiryHint translation
ui-vue3/src/base/http/request.ts Adds silentError support for suppressing toast/log output
ui-vue3/src/api/service/service.ts Adds listServiceEvent() client
ui-vue3/src/api/service/instance.ts Adds silentError option for getInstanceDetail() and adds listInstanceEvent()
ui-vue3/src/api/service/app.ts Tightens listApplicationEvent() params typing (adds pagination params)
pkg/engine/kubernetes/listerwatcher/k8s_event.go New watcher to list/watch core/v1 Events and transform into K8sEvent resources
pkg/engine/kubernetes/factory.go Registers the new K8sEvent lister-watcher in the K8s engine
pkg/core/store/index/platform_event.go Adds store indexes for PlatformEvent queries
pkg/core/store/index/k8s_event.go Adds store indexes for K8sEvent queries
pkg/core/resource/apis/mesh/v1alpha1/platformevent_types.go Defines PlatformEvent resource schema + list
pkg/core/resource/apis/mesh/v1alpha1/k8sevent_types.go Defines K8sEvent resource schema + list
pkg/core/events/component.go Downgrades “no subscriber” log from INFO to DEBUG
pkg/core/discovery/subscriber/zk_metadata.go Records PlatformEvent entries for ZK metadata add/update
pkg/core/discovery/subscriber/zk_config.go Records PlatformEvent entries for ZK config add/update/delete
pkg/core/discovery/subscriber/platform_event_recorder.go Adds shared PlatformEvent store-write utility
pkg/core/discovery/subscriber/nacos_service.go Records PlatformEvent entries for Nacos instance/consumer metadata changes
pkg/console/service/event.go Implements merged K8s + Platform event querying and pagination
pkg/console/router/router.go Adds /application/event, /instance/event, /service/event routes
pkg/console/model/event.go Adds request/response models for event queries
pkg/console/handler/event.go Adds handlers for the new event endpoints
api/mesh/v1alpha1/k8s_event.proto Adds proto definition for K8sEvent spec
api/mesh/v1alpha1/k8s_event.go Adds Go struct + Clone for K8sEvent spec

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +159 to +163
res, ok := items[i].(*K8sEventResource)
if !ok {
logger.Errorf("unexpected resource type, expected: %s, get %s", K8sEventKind, res.ResourceKind())
continue
}
Comment on lines +103 to +108
watch(
() => [props.hasMore, props.loading, props.loadingMore, props.events.length],
() => {
tryLoadMore()
}
)
Comment thread pkg/console/service/event.go Outdated
Comment on lines +281 to +289
total := len(entries)
start := pageReq.PageOffset
if start > total {
start = total
}
end := start + pageReq.PageSize
if pageReq.PageSize <= 0 || end > total {
end = total
}
Comment on lines +77 to +92
mesh := constants.DefaultMesh
res := meshresource.NewK8sEventResourceWithAttributes(k8sEvent.Namespace+"/"+k8sEvent.Name, mesh)
res.Spec = &meshproto.K8sEvent{
Namespace: k8sEvent.Namespace,
Reason: k8sEvent.Reason,
Message: k8sEvent.Message,
Type: k8sEvent.Type,
InvolvedObjKind: k8sEvent.InvolvedObject.Kind,
InvolvedObjName: k8sEvent.InvolvedObject.Name,
SourceComponent: k8sEvent.Source.Component,
SourceHost: k8sEvent.Source.Host,
FirstTimestamp: k8sEvent.FirstTimestamp.Format(constants.TimeFormatStr),
LastTimestamp: k8sEvent.LastTimestamp.Format(constants.TimeFormatStr),
Count: k8sEvent.Count,
EventSource: "KUBERNETES",
}
Comment thread pkg/console/service/event.go Outdated
Comment on lines +55 to +76
func ListInstanceEvents(ctx consolectx.Context, req *model.EventQueryReq) (*model.EventListResp, error) {
k8sEvents, err := manager.ListByIndexes[*meshresource.K8sEventResource](
ctx.ResourceManager(),
meshresource.K8sEventKind,
[]index.IndexCondition{
{IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals},
},
)
if err != nil {
return nil, err
}

platformEvents, err := manager.ListByIndexes[*meshresource.PlatformEventResource](
ctx.ResourceManager(),
meshresource.PlatformEventKind,
[]index.IndexCondition{
{IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals},
},
)
if err != nil {
return nil, err
}

@robocanic robocanic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great Work! Thanks a lot for your PR! I've reviewed the PR generally and the whole structrue is totally fine. There are some issues both I and Qoder found, please check the review and find them if make sense.

completeness-review.md
correctness-review.md
impact-review.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: 这个文件应该要由插件自动生成的,见:Resource的定义与更新

Comment thread api/mesh/v1alpha1/k8s_event.proto Outdated

import "api/mesh/options.proto";

message K8sEvent {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: PlatformEvent为什么没有proto定义?

Comment thread pkg/console/service/event.go Outdated
}

func buildApplicationK8sConditions(req *model.EventQueryReq) []index.IndexCondition {
conditions := []index.IndexCondition{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction: 这里的AppName参数需要前端必传,不传需要抛异常,不然这里的过滤条件太宽泛了,会把整个mesh的所有事件全部捞出来。下面的过滤条件也是一样

…ent ingestion ([apache#1472](apache#1472))

- Add K8sEvent resource type (proto + Go spec) and store indexes
- Add K8sEventListerWatcher to watch K8s /api/v1/events via client-go
- Register K8sEventListerWatcher in Kubernetes EngineFactory
- Add /application/event, /instance/event, /service/event console API endpoints
- Add EventTimeline shared Vue component with normal/warning node styles
- Wire up event tabs for application, instance, and service detail pages
- Un-hide event tab routes in frontend router
- Add mock event handlers for development
- Add PlatformEvent resource type (platformevent_types.go) with store indexes
- Add shared platform_event_recorder utility for event recording
- Record ZK config change events (tag-route, condition-route, dynamic-config)
- Record ZK metadata events (provider/consumer metadata added/updated)
- Record Nacos instance registration/deregistration events
- Record Nacos consumer metadata change events
- Merge K8s events and Platform events into unified timeline in event query service
- Downgrade "no subscriber" log level from INFO to DEBUG to reduce log noise
@robocanic

Copy link
Copy Markdown
Contributor

@ambiguous-pointer please merge the develop and resolve the conflicts.

…, watch spam, silent drops, delete-before-add

- K8sEventListerWatcher: check IsZero() before formatting FirstTimestamp/LastTimestamp
  to avoid emitting synthetic "0001-01-01 00:00:00" values that break timeline ordering.
- EventTimeline.vue: remove unconditional watch() that triggered cascading loadMore calls;
  rely solely on IntersectionObserver for scroll-based pagination.
- RecordRegistryEvent: log a warning when dropping events due to empty Mesh or Message
  instead of silently discarding them.
- K8sEventSubscriber.writeEvent: delete the informer-written (original-key) entry only
  after a successful Add of the timestamp-prefixed entry, preventing permanent event
  loss when Add fails.
@ambiguous-pointer

Copy link
Copy Markdown
Contributor Author

@robocanic Conflicts resolved after merging develop. The build is now passing and all checks are green. Please review.

@ambiguous-pointer

ambiguous-pointer commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

概述

本 PR 为 Dubbo 应用、实例和服务引入了统一的事件时间线功能,将 Kubernetes 原生事件注册中心侧生命周期事件(ZooKeeper / Nacos)统一摄入到 K8sEventResource 存储中,通过 Console API 端点暴露,并配有前端时间线可视化组件。

Closes #1472。设计方案讨论见 #1474


架构设计

统一事件模型

所有事件——无论是来自 K8s 还是注册中心——都以 K8sEventResource 形式存储,通过 EventSource 字段区分来源:

EventSource 来源 描述
"KUBERNETES" K8s api/v1/events Pod 调度、镜像拉取、就绪探针、重启等
"REGISTRY" ZK / Nacos 订阅器 实例注册/注销/更新、配置/元数据增删改

K8sEvent proto 定义统一承载两种来源:

message K8sEvent {
  string namespace        = 1;  // K8s 命名空间(注册中心事件为空)
  string reason           = 2;  // K8s reason 或注册中心 action
  string message          = 3;  // 可读描述
  string type             = 4;  // "Normal" | "Warning"
  string involvedObjKind  = 5;  // K8s: "Pod" | 注册中心: "registry" | "config" | "metadata"
  string involvedObjName  = 6;  // K8s: Pod 名 | 注册中心: 层级编码(见下文)
  string sourceComponent  = 7;  // K8s: source component | 注册中心: "zookeeper" | "nacos"
  string sourceHost       = 8;  // K8s: source host | 注册中心: 实例 IP
  string firstTimestamp   = 9;
  string lastTimestamp    = 10;
  int32  count            = 11;
  string eventSource      = 12; // "KUBERNETES" | "REGISTRY"
}

数据流

┌──────────────────┐     ┌──────────────────────┐     ┌──────────────────────┐
│   K8s API Server │     │  ZK /services/*      │     │  Nacos naming        │
│   /api/v1/events  │     │  /dubbo/metadata/    │     │  service list        │
│   (仅 Pod 事件)   │     │  /dubbo/config/      │     │  / config page       │
└────────┬─────────┘     └──────────┬───────────┘     └──────────┬───────────┘
         │                          │                            │
         ▼                          ▼                            ▼
┌────────────────────────────────────────────────────────────────────┐
│                     Informer(List + Watch)                       │
│                                                                    │
│  K8sEventListerWatcher   ZKLW(x4)   NacosLW(x6)                  │
│    │                         │            │                        │
│    │ TransformFunc:          │            │                        │
│    │  k8s Event →            │            │                        │
│    │  K8sEventResource       │            │                        │
│    │  (mesh=engineID,        │            │                        │
│    │   EventSource=          │            │                        │
│    │   "KUBERNETES")         │            │                        │
│    │                         │            │                        │
└────┼─────────────────────────┼────────────┼────────────────────────┘
     │                         │            │
     └──────────┬──────────────┴────────────┘
                ▼
     ┌─────────────────────┐
     │  Informer 写入       │
     │  Store + EventBus    │
     └─────────┬───────────┘
               │
     ┌─────────▼──────────┐
     │     EventBus        │
     │  (异步分发)        │
     └─┬──────────────┬────┘
       │              │
       ▼              ▼
┌────────────┐  ┌──────────────────────────────┐
│  其他      │  │  K8sEventSubscriber           │
│  订阅器    │  │                              │
│  (instance,│  │  对于 KUBERNETES 事件:       │
│   service, │  │   1. 按 DubboAppId 过滤       │
│   ...)     │  │   2. 仅保留 Pod 类型          │
│            │  │   3. alignMeshFromRtInstance()│
│            │  │   4. prefixTimestampKey()     │
│            │  │   5. eventStore.Add()         │
│            │  │                              │
│            │  │  对于 REGISTRY 事件:         │
│            │  │   → 直接 writeEvent()        │
│            │  │   (已包含完整信息)           │
└────────────┘  └──────────────┬───────────────┘
                               │
              ┌────────────────┘
              ▼
┌─────────────────────────────────────────────────────────┐
│              K8sEvent 存储(PG / Memory)                 │
│                                                         │
│  索引:mesh, involvedObjKind, involvedObjName,          │
│        eventType, eventSource                           │
│                                                         │
│  Key 前缀为降序纳秒时间戳:                               │
│    "{sortablePrefix}-{namespace}/{name}"                │
│  → 按 Key 字母序排列 = 按时间倒序(最新在前)             │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│              Console API(event.go)                     │
│                                                         │
│  GET /application/event  — 应用关联的 Pod K8s 事件       │
│  GET /instance/event     — 实例关联的 Pod K8s 事件       │
│  GET /service/event      — 服务关联的注册中心事件         │
│                            (配置/元数据)                │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│           前端 EventTimeline.vue                         │
│  左右交替时间线布局,支持无限滚动                          │
│  ┌──────────────────────────────────────────────────┐   │
│  │ 2026-06-07 17:39:14                              │   │
│  │ ⚠ Error updating Endpoint Slices...              │   │
│  │                               endpoint-slice-ctrl│   │
│  ├──────────────────────────────────────────────────┤   │
│  │ 2026-06-07 10:25:04                              │   │
│  │ ✓ Zookeeper provider metadata added:             │   │
│  │   demo-app → com.example.TestService             │   │
│  │                                    Zookeeper     │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

核心设计决策

1. 统一 K8sEventResource(非双资源模型)

注册中心事件作为独立的 PlatformEventResource 类型存储。RecordRegistryEvent() 直接创建 EventSource = "REGISTRY"K8sEventResource 实例。优点:

  • 单一存储、单一 API:Console 查询只需访问一种资源类型即可覆盖 K8s 和注册中心事件。
  • 统一分页PageListByIndexes 对两种来源一致工作。
  • 无需 EventBus 绕行:注册中心事件通过 RecordRegistryEvent()eventStore.Add() 直接写入存储(不走 EventBus 往返)。下游订阅器仍可观测 K8sEvent 资源。

2. 层级化 InvolvedObjName 编码

注册中心事件将实体标识编码到 InvolvedObjName 中,支持单索引前缀匹配:

事件类别 InvolvedObjName 示例
实例(注册中心) {appName}/{ip} test-event/10.244.1.89
配置(ZK) {appName}/{serviceName} test-event/com.example.TestService
元数据(ZK) {appName}/{serviceName} test-event/com.example.TestService

K8s 事件直接使用 Pod 名作为 InvolvedObjName(如 test-event-app-7cd9897566-xlxpf)。

Console API 的 HasPrefix 查询可以用单一索引条件匹配两种编码风格。

3. 时间戳前缀 Key 实现时序排序

PageListByIndexes 按资源 Key 的字母序排列。为实现时间序,prefixTimestampKey() 在 Key 前追加降序纳秒时间戳:

key = "{MAX_INT64 - timestampNano}_{namespace}/{podName}"

越新的事件前缀越小 → 排序越靠前。注册中心事件在 RecordRegistryEvent() 中已包含纳秒时间戳。

4. K8s 事件的 Mesh 对齐

K8s Event 对象没有"Dubbo mesh"的概念。K8sEventListerWatcher 以 engine ID 作为初始 mesh,随后 K8sEventSubscriber.alignMeshFromRuntimeInstance() 通过 Pod 名跨所有 mesh 查找对应的 RuntimeInstance(使用 ByRuntimeInstanceNameIndex 索引),将事件 mesh 对齐为正确的 discovery mesh。

5. K8s 事件过滤(仅 Pod、仅 Dubbo)

  • Field SelectorinvolvedObject.kind=Pod — 在 K8s API 层面过滤,避免集群级噪声(Node 事件、Namespace 事件等)。
  • DubboAppIdentifier:在 K8sEventSubscriber 中,通过配置的 DubboAppIdentifier(如按 label app 匹配)丢弃非 Dubbo Pod 的事件,不进入存储。

文件清单

API 层

文件 说明
api/mesh/v1alpha1/k8s_event.proto 统一 K8sEvent 消息的 proto 定义
api/mesh/v1alpha1/k8s_event.go 手写 Go 类型,含 Clone() 方法

核心资源类型与索引

文件 说明
pkg/core/resource/apis/mesh/v1alpha1/k8sevent_types.go K8sEventResource + K8sEventResourceList 类型定义,含 DeepCopy、SetItems 等
pkg/core/store/index/k8s_event.go 四个索引:ByK8sEventInvolvedObjKindByK8sEventInvolvedObjNameByK8sEventTypeByK8sEventSource
pkg/core/store/index/runtime_instance.go 新增 ByRuntimeInstanceNameIndex 索引,支持跨 mesh 按 Pod 名查找 RuntimeInstance

K8s Engine

文件 说明
pkg/engine/kubernetes/factory.go 在已有 PodListerWatcher 旁注册 K8sEventListerWatcher
pkg/engine/kubernetes/listerwatcher/k8s_event.go K8sEventListerWatcher — List+Watch api/v1/events(仅 Pod),TransformFunc 含零值时间戳保护,转换为 K8sEventResource

Discovery 订阅器

文件 说明
pkg/core/discovery/subscriber/k8s_event.go K8sEventSubscriber — 按 DubboAppIdentifier + Pod kind 过滤 K8s 事件,从 RuntimeInstance 对齐 mesh,为 Key 添加时间戳前缀。REGISTRY 事件直接透传。含原地修复:delete 移至 Add 成功后执行,防止数据丢失。
pkg/core/discovery/subscriber/registry_event_adapter.go RecordRegistryEvent() — 共享工具函数,创建 EventSource="REGISTRY"K8sEventResource,编码层级化 InvolvedObjName。Mesh 或 Message 为空时 Warn 日志而非静默丢弃。
pkg/core/discovery/subscriber/zk_config.go ZKConfigEventSubscriber — 记录 tag-route / condition-route / dynamic-config 的增删改事件
pkg/core/discovery/subscriber/zk_metadata.go ZKMetadataEventSubscriber — 记录 provider/consumer 元数据的增改事件
pkg/core/discovery/subscriber/nacos_service.go NacosServiceEventSubscriber — 记录实例注册/注销/更新及 consumer 元数据增删改事件
pkg/core/discovery/component.go K8sEventSubscriber 接入 discovery 生命周期(仅当 engine 类型为 Kubernetes 时)

Console API

文件 说明
pkg/console/service/event.go 核心查询逻辑:ListApplicationEventsListInstanceEventsListServiceEvents。分别从 RuntimeInstance 解析 Pod 名 → 查询 K8sEvent 存储 → 按 involved object name 过滤 → 分页返回。负数 offset 和零 pageSize 已安全处理。
pkg/console/handler/event.go HTTP 处理器:GetApplicationEventsGetInstanceEventsGetServiceEvents
pkg/console/model/event.go 请求/响应类型:EventQueryReqEventItemEventListResp
pkg/console/router/router.go 路由注册:GET /instance/eventGET /application/eventGET /service/event

前端

文件 说明
ui-vue3/src/components/EventTimeline.vue 可复用时间线组件,基于 Ant Design a-timeline,左右交替布局,normal/warning 图标区分,IntersectionObserver 驱动无限滚动(已移除 watch 无条件触发 loadMore)
ui-vue3/src/views/resources/applications/tabs/event.vue 应用事件 Tab — 接入 EventTimeline,传入 appName + mesh
ui-vue3/src/views/resources/instances/tabs/event.vue 实例事件 Tab — 接入 EventTimeline,传入 instanceName + ip + appName + mesh
ui-vue3/src/views/resources/services/tabs/event.vue 服务事件 Tab — 接入 EventTimeline,传入 serviceName + appName + mesh
ui-vue3/src/api/service/app.ts listApplicationEvent() API 函数
ui-vue3/src/api/service/instance.ts listInstanceEvent() API 函数
ui-vue3/src/api/service/service.ts listServiceEvent() API 函数
ui-vue3/src/types/api.ts EventItem TypeScript 类型定义
ui-vue3/src/router/defaultRoutes.ts 恢复之前被注释的事件 Tab 路由

配置与小幅改动

文件 说明
pkg/core/events/component.go "no subscriber for resource" 日志级别从 INFO 降为 DEBUG,减少 K8sEventService 类型(有意不设订阅器)的日志噪音

API 参考

GET /api/v1/application/event

查询参数:

参数 必填 说明
mesh Dubbo mesh 名称
appName 应用名称
pageOffset 分页偏移量(默认 0,负值自动修正为 0)
pageSize 每页条数(默认 20)

GET /api/v1/instance/event

参数 必填 说明
mesh Dubbo mesh 名称
ip 实例 IP 地址
instanceName 实例名称(作为额外匹配候选项)
appName 应用名称(用于匹配注册中心事件)
pageOffset 分页偏移量
pageSize 每页条数

GET /api/v1/service/event

参数 必填 说明
mesh Dubbo mesh 名称
serviceName 服务名称
appName 应用名称
pageOffset 分页偏移量
pageSize 每页条数

EventItem 响应格式

{
  "time": "2026-07-26 15:40:54",
  "type": "normal",
  "message": "Started container agnhost",
  "source": "kubelet"
}

E2E 验证

在 3 节点集群上测试:

场景 结果
应用事件(mesh=dubbo-samples-shopappName=test-event 返回 96 条
实例事件(mesh=dubbo-samples-shopip=10.244.1.93 每个 Pod 返回 4 条
时间线排序 按时间倒序排列 ✓
source 标签(kubelet、default-scheduler) 正确渲染 ✓
Warning 事件 黄色图标 + 橙色标签 ✓
空状态 "暂无事件" 占位提示 ✓
无限滚动 IntersectionObserver 驱动 loadMore ✓

@robocanic

Copy link
Copy Markdown
Contributor

既然Event包括了K8s侧的和registry侧的,感觉是不是换个统一的名字更好?

K8sEvent was originally named for K8s-sourced events only, but now also
carries registry-side events (ZK/Nacos) via the EventSource discriminator.
Rename to LifecycleEvent to accurately reflect its role as a unified
lifecycle event type covering both K8s and registry origins.

- Proto: K8sEvent → LifecycleEvent
- Resource: K8sEventResource → LifecycleEventResource
- Kind: K8sEventKind → LifecycleEventKind
- Subscriber: K8sEventSubscriber → LifecycleEventSubscriber
- Index constants: ByK8sEvent* → ByLifecycleEvent*
- Files: k8s_event.* → lifecycle_event.*
- Retained: K8sEventListerWatcher (K8s-specific component)
@sonarqubecloud

Copy link
Copy Markdown

@ambiguous-pointer

Copy link
Copy Markdown
Contributor Author

@robocanic 已经安装要求修改了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants