ambiguous-pointer commented on PR #1487:
URL: https://github.com/apache/dubbo-admin/pull/1487#issuecomment-5082867850

   ## Summary
   
   This PR introduces a **unified event timeline** for Dubbo applications, 
instances, and services, ingesting both **Kubernetes native events** and 
**registry-side lifecycle events** (ZooKeeper / Nacos) into a single 
`K8sEventResource` store, and exposing them through Console API endpoints with 
a frontend timeline visualization.
   
   Closes [#1472](https://github.com/apache/dubbo-admin/issues/1472). Design 
discussed in [#1474](https://github.com/apache/dubbo-admin/discussions/1474).
   
   ---
   
   ## Architecture
   
   ### Unified Event Model
   
   All events — whether from K8s or from a registry — are stored as 
`K8sEventResource` with an `EventSource` discriminator:
   
   | EventSource | Source | Description |
   |-------------|--------|-------------|
   | `"KUBERNETES"` | K8s `api/v1/events` | Pod scheduling, image pull, 
readiness probe, restarts, etc. |
   | `"REGISTRY"` | ZK / Nacos subscribers | Instance 
registered/deregistered/updated, config/metadata added/updated/deleted |
   
   The `K8sEvent` proto spec captures both origins in a single schema:
   
   ```protobuf
   message K8sEvent {
     string namespace        = 1;  // K8s namespace (empty for registry events)
     string reason           = 2;  // K8s reason or registry action
     string message          = 3;  // human-readable description
     string type             = 4;  // "Normal" | "Warning"
     string involvedObjKind  = 5;  // K8s: "Pod" | Registry: "registry" | 
"config" | "metadata"
     string involvedObjName  = 6;  // K8s: pod name | Registry: hierarchical 
encoding (see below)
     string sourceComponent  = 7;  // K8s: source component | Registry: 
"zookeeper" | "nacos"
     string sourceHost       = 8;  // K8s: source host | Registry: instance IP
     string firstTimestamp   = 9;
     string lastTimestamp    = 10;
     int32  count            = 11;
     string eventSource      = 12; // "KUBERNETES" | "REGISTRY"
   }
   ```
   
   ### Data Flow
   
   ```
   ┌──────────────────┐     ┌──────────────────────┐     
┌──────────────────────┐
   │   K8s API Server │     │  ZK /services/*      │     │  Nacos naming        
│
   │   /api/v1/events  │     │  /dubbo/metadata/    │     │  service list       
 │
   │   (Pod only)      │     │  /dubbo/config/      │     │  / config page      
 │
   └────────┬─────────┘     └──────────┬───────────┘     
└──────────┬───────────┘
            │                          │                            │
            ▼                          ▼                            ▼
   ┌────────────────────────────────────────────────────────────────────┐
   │                     Informer (List + Watch)                        │
   │                                                                    │
   │  K8sEventListerWatcher   ZKLW(x4)   NacosLW(x6)                  │
   │    │                         │            │                        │
   │    │ TransformFunc:          │            │                        │
   │    │  k8s Event →            │            │                        │
   │    │  K8sEventResource       │            │                        │
   │    │  (mesh=engineID,        │            │                        │
   │    │   EventSource=          │            │                        │
   │    │   "KUBERNETES")         │            │                        │
   │    │                         │            │                        │
   └────┼─────────────────────────┼────────────┼────────────────────────┘
        │                         │            │
        └──────────┬──────────────┴────────────┘
                   ▼
        ┌─────────────────────┐
        │  Informer writes to  │
        │  Store + EventBus    │
        └─────────┬───────────┘
                  │
        ┌─────────▼──────────┐
        │     EventBus        │
        │  (async dispatch)   │
        └─┬──────────────┬────┘
          │              │
          ▼              ▼
   ┌────────────┐  ┌──────────────────────────────┐
   │  Other     │  │  K8sEventSubscriber           │
   │  subscribers│  │                              │
   │  (instance,│  │  For KUBERNETES events:       │
   │   service, │  │   1. Filter by DubboAppId     │
   │   ...)     │  │   2. Filter Pod kind only     │
   │            │  │   3. alignMeshFromRtInstance()│
   │            │  │   4. prefixTimestampKey()     │
   │            │  │   5. eventStore.Add()         │
   │            │  │                              │
   │            │  │  For REGISTRY events:         │
   │            │  │   → writeEvent() directly     │
   │            │  │   (already enriched)          │
   └────────────┘  └──────────────┬───────────────┘
                                  │
                 ┌────────────────┘
                 ▼
   ┌─────────────────────────────────────────────────────────┐
   │              K8sEvent store (PG / Memory)                │
   │                                                         │
   │  indexed by: mesh, involvedObjKind, involvedObjName,    │
   │              eventType, eventSource                     │
   │                                                         │
   │  keys prefixed with descending nano-timestamp:          │
   │    "{sortablePrefix}-{namespace}/{name}"                │
   │  → alphabetical key sort = chronological (newest first) │
   └────────────────────────┬────────────────────────────────┘
                            │
                            ▼
   ┌─────────────────────────────────────────────────────────┐
   │              Console API (event.go)                      │
   │                                                         │
   │  GET /application/event  — K8s events for app pods      │
   │  GET /instance/event     — K8s events for instance pod  │
   │  GET /service/event      — Registry config/metadata     │
   │                             events for service          │
   └────────────────────────┬────────────────────────────────┘
                            │
                            ▼
   ┌─────────────────────────────────────────────────────────┐
   │           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     │   │
   │  └──────────────────────────────────────────────────┘   │
   └─────────────────────────────────────────────────────────┘
   ```
   
   ---
   
   ## Key Design Decisions
   
   ### 1. Unified `K8sEventResource` (not dual-resource)
   
   Registry events are NOT stored as a separate `PlatformEventResource` type. 
Instead, `RecordRegistryEvent()` creates `K8sEventResource` instances with 
`EventSource = "REGISTRY"`. This has several advantages:
   
   - **Single store, single API**: Console queries hit one resource kind for 
both K8s and registry events.
   - **Unified pagination**: `PageListByIndexes` works identically for both 
sources.
   - **No EventBus bypass**: Registry events use `RecordRegistryEvent()` → 
`eventStore.Add()`, writing directly to the store (no round-trip through 
EventBus). Downstream subscribers can still observe K8sEvent resources if 
needed.
   
   ### 2. Hierarchical `InvolvedObjName` encoding
   
   Registry events encode entity identity into `InvolvedObjName` for 
single-index prefix matching:
   
   | Event category | InvolvedObjName | Example |
   |---|---|---|
   | Instance (registry) | `{appName}/{ip}` | `test-event/10.244.1.89` |
   | Config (ZK) | `{appName}/{serviceName}` | 
`test-event/com.example.TestService` |
   | Metadata (ZK) | `{appName}/{serviceName}` | 
`test-event/com.example.TestService` |
   
   K8s events use the Pod name as `InvolvedObjName` (e.g., 
`test-event-app-7cd9897566-xlxpf`).
   
   The Console API's `HasPrefix` query can match both encoding styles with a 
single index condition.
   
   ### 3. Timestamp-prefixed keys for chronological ordering
   
   `PageListByIndexes` sorts by resource key alphabetically. To make this 
chronological, `prefixTimestampKey()` prepends a descending nano-timestamp:
   
   ```
   key = "{MAX_INT64 - timestampNano}_{namespace}/{podName}"
   ```
   
   More recent events have smaller prefixes → sort first. Registry events 
include a nano-timestamp in their name from `RecordRegistryEvent()`.
   
   ### 4. Mesh alignment for K8s events
   
   K8s `Event` objects have no concept of a "Dubbo mesh". The 
`K8sEventListerWatcher` assigns the engine ID as the initial mesh, then 
`K8sEventSubscriber.alignMeshFromRuntimeInstance()` resolves the correct 
discovery mesh by looking up the corresponding `RuntimeInstance` via the Pod 
name, using the `ByRuntimeInstanceNameIndex` to search across all meshes.
   
   ### 5. K8s Event filtering (Pod only, Dubbo only)
   
   - **Field selector**: `involvedObject.kind=Pod` — filters at the K8s API 
level, avoiding cluster-wide noise (Node events, Namespace events, etc.).
   - **DubboAppIdentifier**: In `K8sEventSubscriber`, non-Dubbo Pods are 
discarded by checking the configured `DubboAppIdentifier` (e.g., by label 
`app`). Events from Pods without the identifier are skipped before reaching the 
store.
   
   ---
   
   ## File-by-File Breakdown
   
   ### API Layer
   
   | File | Description |
   |------|-------------|
   | `api/mesh/v1alpha1/k8s_event.proto` | Proto definition for unified 
`K8sEvent` message |
   | `api/mesh/v1alpha1/k8s_event.go` | Hand-written Go type with `Clone()` (no 
protobuf generation needed for simple struct) |
   
   ### Core Resource Types & Indexes
   
   | File | Description |
   |------|-------------|
   | `pkg/core/resource/apis/mesh/v1alpha1/k8sevent_types.go` | 
`K8sEventResource` + `K8sEventResourceList` types with DeepCopy, SetItems, etc. 
|
   | `pkg/core/store/index/k8s_event.go` | Indexes: 
`ByK8sEventInvolvedObjKind`, `ByK8sEventInvolvedObjName`, `ByK8sEventType`, 
`ByK8sEventSource` |
   | `pkg/core/store/index/runtime_instance.go` | Added 
`ByRuntimeInstanceNameIndex` for cross-mesh RuntimeInstance lookup by pod name |
   
   ### K8s Engine
   
   | File | Description |
   |------|-------------|
   | `pkg/engine/kubernetes/factory.go` | Registers `K8sEventListerWatcher` 
alongside existing `PodListerWatcher` |
   | `pkg/engine/kubernetes/listerwatcher/k8s_event.go` | 
`K8sEventListerWatcher` — List+Watch `api/v1/events` (Pod only), transforms to 
`K8sEventResource` with zero-timestamp guard |
   
   ### Discovery Subscribers
   
   | File | Description |
   |------|-------------|
   | `pkg/core/discovery/subscriber/k8s_event.go` | `K8sEventSubscriber` — 
filters K8s events by DubboAppIdentifier + Pod kind, aligns mesh from 
RuntimeInstance, prefixes timestamp keys for chronological sort. Passes 
REGISTRY events through directly. |
   | `pkg/core/discovery/subscriber/registry_event_adapter.go` | 
`RecordRegistryEvent()` — shared utility that creates `K8sEventResource` with 
`EventSource="REGISTRY"` and hierarchical `InvolvedObjName`. Warns on empty 
Mesh/Message instead of silently dropping. |
   | `pkg/core/discovery/subscriber/zk_config.go` | `ZKConfigEventSubscriber` — 
records tag-route / condition-route / dynamic-config added/updated/deleted 
events via `recordConfigPlatformEvent()` |
   | `pkg/core/discovery/subscriber/zk_metadata.go` | 
`ZKMetadataEventSubscriber` — records provider/consumer metadata added/updated 
events via `recordMetadataPlatformEvent()` |
   | `pkg/core/discovery/subscriber/nacos_service.go` | 
`NacosServiceEventSubscriber` — records instance 
registered/deregistered/updated and consumer metadata added/removed/updated 
events via `recordNacosInstanceEvents()` / `recordNacosConsumerEvents()` |
   | `pkg/core/discovery/component.go` | Wires `K8sEventSubscriber` into 
discovery lifecycle (only when engine type is Kubernetes) |
   
   ### Console API
   
   | File | Description |
   |------|-------------|
   | `pkg/console/service/event.go` | Core query logic: 
`ListApplicationEvents`, `ListInstanceEvents`, `ListServiceEvents`. Each 
resolves pod names from RuntimeInstances, queries K8sEvent store, filters by 
involved object name, and returns paginated results. Negative offset and zero 
pageSize are safely clamped. |
   | `pkg/console/handler/event.go` | HTTP handlers: `GetApplicationEvents`, 
`GetInstanceEvents`, `GetServiceEvents` |
   | `pkg/console/model/event.go` | Request/response types: `EventQueryReq`, 
`EventItem`, `EventListResp` |
   | `pkg/console/router/router.go` | Routes: `GET /instance/event`, `GET 
/application/event`, `GET /service/event` |
   
   ### Frontend
   
   | File | Description |
   |------|-------------|
   | `ui-vue3/src/components/EventTimeline.vue` | Reusable timeline component 
with Ant Design `a-timeline`, alternating layout, normal/warning icons, 
IntersectionObserver-based infinite scroll (no watch-driven preloading) |
   | `ui-vue3/src/views/resources/applications/tabs/event.vue` | Application 
event tab — wires EventTimeline with `appName` + `mesh` params |
   | `ui-vue3/src/views/resources/instances/tabs/event.vue` | Instance event 
tab — wires EventTimeline with `instanceName` + `ip` + `appName` + `mesh` 
params |
   | `ui-vue3/src/views/resources/services/tabs/event.vue` | Service event tab 
— wires EventTimeline with `serviceName` + `appName` + `mesh` params |
   | `ui-vue3/src/api/service/app.ts` | `listApplicationEvent()` API function |
   | `ui-vue3/src/api/service/instance.ts` | `listInstanceEvent()` API function 
|
   | `ui-vue3/src/api/service/service.ts` | `listServiceEvent()` API function |
   | `ui-vue3/src/types/api.ts` | `EventItem` TypeScript type |
   | `ui-vue3/src/router/defaultRoutes.ts` | Restored event tab routes 
(previously commented out) |
   
   ### Config & Minor Changes
   
   | File | Description |
   |------|-------------|
   | `pkg/core/events/component.go` | "no subscriber for resource" log level: 
`INFO` → `DEBUG` to reduce noise for `K8sEvent` and `Service` kinds that 
intentionally have no subscriber |
   
   ---
   
   ## API Reference
   
   ### `GET /api/v1/application/event`
   
   Query parameters:
   
   | Param | Required | Description |
   |-------|----------|-------------|
   | `mesh` | Yes | Dubbo mesh name |
   | `appName` | Yes | Application name |
   | `pageOffset` | No | Pagination offset (default 0, clamped to ≥0) |
   | `pageSize` | No | Page size (default 20) |
   
   Returns: `{ code, message, data: { list: EventItem[], total: number } }`
   
   ### `GET /api/v1/instance/event`
   
   | Param | Required | Description |
   |-------|----------|-------------|
   | `mesh` | Yes | Dubbo mesh name |
   | `ip` | Yes | Instance IP address |
   | `instanceName` | No | Instance name (used as additional match candidate) |
   | `appName` | No | Application name (used for registry event matching) |
   | `pageOffset` | No | Pagination offset |
   | `pageSize` | No | Page size |
   
   ### `GET /api/v1/service/event`
   
   | Param | Required | Description |
   |-------|----------|-------------|
   | `mesh` | Yes | Dubbo mesh name |
   | `serviceName` | Yes | Service name |
   | `appName` | Yes | Application name |
   | `pageOffset` | No | Pagination offset |
   | `pageSize` | No | Page size |
   
   ### EventItem schema
   
   ```json
   {
     "time": "2026-07-26 15:40:54",
     "type": "normal",
     "message": "Started container agnhost",
     "source": "kubelet"
   }
   ```
   
   ---
   
   ## E2E Verification
   
   Tested against a 3-node cluster:
   
   | Scenario | Result |
   |----------|--------|
   | Application events (`mesh=dubbo-samples-shop`, `appName=test-event`) | 96 
events returned |
   | Instance events (`mesh=dubbo-samples-shop`, `ip=10.244.1.93`) | 4 events 
per pod returned |
   | Timeline ordering | Events sorted by time descending ✓ |
   | Source tags (kubelet, default-scheduler) | Rendered correctly ✓ |
   | Warning events | Yellow icon + orange tag ✓ |
   | Empty state | "暂无事件" placeholder ✓ |
   | Infinite scroll | IntersectionObserver-based loadMore ✓ |
   
   ---
   
   ## Known Limitations
   
   1. **No event TTL / cleanup** — Events accumulate indefinitely in the store. 
No retention policy, TTL, or automatic cleanup for any store backend.
   
   2. **No subscriber for K8sEvent on EventBus** — K8sEvent and Service 
resources are stored but no downstream subscriber processes them (by design — 
they only need storage). The EventBus emits a DEBUG-level "no subscriber" log.
   
   3. **Registry events bypass EventBus** — `RecordRegistryEvent()` writes 
directly to the store rather than emitting through EventBus. This is 
intentional (avoids a round-trip), but means registry events are not observable 
by downstream subscribers of K8sEvent resources.
   
   4. **Service events are registry-only** — K8s has no native "service" 
concept, so `/service/event` returns only registry-side config and metadata 
events. K8s-sourced events naturally do not appear here.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to