This is an automated email from the ASF dual-hosted git repository.
robocanic pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git
The following commit(s) were added to refs/heads/develop by this push:
new d9ebf33a feat: unify and enhance the lifecycle of an instance (#1440)
d9ebf33a is described below
commit d9ebf33aedf967205f22c453882a612ab71cf46d
Author: LunaRain_079 <[email protected]>
AuthorDate: Sun Apr 12 19:22:42 2026 +0800
feat: unify and enhance the lifecycle of an instance (#1440)
* feat: add lifecycle state management and color coding for instance
statuses
* feat: enhance instance lifecycle management with state derivation and UI
updates
* feat: improve resource handling in informer with enhanced error reporting
* feat: enhance instance lifecycle logging with detailed merge and delete
events
* feat: implement ResourceKeyProvider interface for consistent key
generation in informers
* refactor: refactor instance resource handling and key function resolution
in informers
* refactor: remove unused deployState and registerState columns from
instance table
* feat: enhance runtime instance retrieval with improved matching logic and
fallback handling
* feat: improve runtime instance identification with enhanced error logging
and filtering
* feat: add pod watch selector and RPC port identifiers for improved
service configuration
* feat: add refresh button and localization support for improved user
interaction
* fix: fix lint
* feat: enhance runtime instance retrieval with improved fallback handling
and logging for ambiguous matches
* fix: enhance instance lifecycle and deployment state management with new
types and improved data handling
---
api/mesh/v1alpha1/runtime_instance_helper.go | 6 +-
pkg/console/model/application.go | 23 +--
pkg/console/model/instance.go | 195 ++++++++++++++++-----
pkg/console/service/application.go | 5 +-
pkg/core/controller/informer.go | 42 +++--
pkg/core/controller/listwatcher.go | 6 +
pkg/core/discovery/subscriber/rpc_instance.go | 61 ++++++-
pkg/core/engine/component.go | 12 +-
pkg/core/engine/subscriber/runtime_instance.go | 139 ++++++++++++---
.../resource/apis/mesh/v1alpha1/instance_helper.go | 55 ++++++
.../kubernetes/listerwatcher/runtime_instance.go | 105 ++++++++++-
.../dubbo-samples-shop/dubbo-samples-shop-all.yaml | 22 +++
release/kubernetes/dubbo-system/dubbo-admin.yaml | 14 ++
ui-vue3/src/base/constants.ts | 18 +-
ui-vue3/src/base/i18n/en.ts | 2 +
ui-vue3/src/base/i18n/zh.ts | 2 +
ui-vue3/src/components/SearchTable.vue | 11 +-
.../views/resources/applications/tabs/instance.vue | 32 +++-
ui-vue3/src/views/resources/instances/index.vue | 43 +++--
.../instances/slots/InstanceTabHeaderSlot.vue | 39 ++++-
.../src/views/resources/instances/tabs/detail.vue | 18 +-
21 files changed, 699 insertions(+), 151 deletions(-)
diff --git a/api/mesh/v1alpha1/runtime_instance_helper.go
b/api/mesh/v1alpha1/runtime_instance_helper.go
index c69c002a..3bffb4cf 100644
--- a/api/mesh/v1alpha1/runtime_instance_helper.go
+++ b/api/mesh/v1alpha1/runtime_instance_helper.go
@@ -23,4 +23,8 @@ const (
StartupProbe = "startup"
)
-const InstanceTerminating = "Terminating"
+const (
+ InstanceStarting = "Starting"
+ InstanceCrashing = "Crashing"
+ InstanceTerminating = "Terminating"
+)
diff --git a/pkg/console/model/application.go b/pkg/console/model/application.go
index d11943f6..ee70a038 100644
--- a/pkg/console/model/application.go
+++ b/pkg/console/model/application.go
@@ -131,17 +131,18 @@ func NewApplicationTabInstanceInfoReq()
*ApplicationTabInstanceInfoReq {
}
type AppInstanceInfoResp struct {
- AppName string `json:"appName"`
- CreateTime string `json:"createTime"`
- DeployState string `json:"deployState"`
- DeployClusters string `json:"deployClusters"`
- IP string `json:"ip"`
- Labels map[string]string `json:"labels"`
- Name string `json:"name"`
- RegisterCluster string `json:"registerCluster"`
- RegisterState string `json:"registerState"`
- RegisterTime string `json:"registerTime"`
- WorkloadName string `json:"workloadName"`
+ AppName string `json:"appName"`
+ CreateTime string `json:"createTime"`
+ LifecycleState InstanceLifecycleState `json:"lifecycleState"`
+ DeployState InstanceDeployState `json:"deployState"`
+ DeployClusters string `json:"deployClusters"`
+ IP string `json:"ip"`
+ Labels map[string]string `json:"labels"`
+ Name string `json:"name"`
+ RegisterCluster string `json:"registerCluster"`
+ RegisterState InstanceRegisterState `json:"registerState"`
+ RegisterTime string `json:"registerTime"`
+ WorkloadName string `json:"workloadName"`
}
type ApplicationServiceReq struct {
diff --git a/pkg/console/model/instance.go b/pkg/console/model/instance.go
index e933d779..907fea27 100644
--- a/pkg/console/model/instance.go
+++ b/pkg/console/model/instance.go
@@ -20,6 +20,7 @@ package model
import (
"github.com/duke-git/lancet/v2/strutil"
+ meshproto "github.com/apache/dubbo-admin/api/mesh/v1alpha1"
"github.com/apache/dubbo-admin/pkg/config/app"
meshresource
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
@@ -54,17 +55,18 @@ func NewSearchPaginationResult() *SearchPaginationResult {
}
type SearchInstanceResp struct {
- Ip string `json:"ip"`
- Name string `json:"name"`
- WorkloadName string `json:"workloadName"`
- AppName string `json:"appName"`
- DeployState string `json:"deployState"`
- DeployCluster string `json:"deployCluster"`
- RegisterState string `json:"registerState"`
- RegisterClusters []string `json:"registerClusters"`
- CreateTime string `json:"createTime"`
- RegisterTime string `json:"registerTime"`
- Labels map[string]string `json:"labels"`
+ Ip string `json:"ip"`
+ Name string `json:"name"`
+ WorkloadName string `json:"workloadName"`
+ AppName string `json:"appName"`
+ LifecycleState InstanceLifecycleState `json:"lifecycleState"`
+ DeployState InstanceDeployState `json:"deployState"`
+ DeployCluster string `json:"deployCluster"`
+ RegisterState InstanceRegisterState `json:"registerState"`
+ RegisterClusters []string `json:"registerClusters"`
+ CreateTime string `json:"createTime"`
+ RegisterTime string `json:"registerTime"`
+ Labels map[string]string `json:"labels"`
}
func NewSearchInstanceResp() *SearchInstanceResp {
@@ -85,13 +87,10 @@ func (r *SearchInstanceResp)
FromInstanceResource(instanceResource *meshresource
if cfg.Engine != nil && cfg.Engine.ID == instance.SourceEngine {
r.DeployCluster = cfg.Engine.Name
}
- if r.RegisterTime != "" {
- r.RegisterState = "Registered"
- } else {
- r.RegisterState = "UnRegistered"
- }
+ r.RegisterState = DeriveInstanceRegisterState(instance)
r.Labels = instance.Tags
- r.DeployState = instance.DeployState
+ r.DeployState = DeriveInstanceDeployState(instance)
+ r.LifecycleState = DeriveInstanceLifecycleState(instance,
r.DeployState, r.RegisterState)
r.WorkloadName = instance.WorkloadName
r.AppName = instance.AppName
return r
@@ -104,23 +103,74 @@ type State struct {
Value string `json:"value"`
}
+// InstanceDeployState describes the runtime deployment state reported by the
platform.
+type InstanceDeployState string
+
+const (
+ // InstanceDeployStateUnknown indicates the deployment state cannot be
derived from runtime metadata.
+ InstanceDeployStateUnknown InstanceDeployState = "Unknown"
+ // InstanceDeployStatePending indicates the workload has been accepted
but is not running yet.
+ InstanceDeployStatePending InstanceDeployState = "Pending"
+ // InstanceDeployStateStarting indicates the workload is running but
not ready to serve.
+ InstanceDeployStateStarting InstanceDeployState = "Starting"
+ // InstanceDeployStateRunning indicates the workload is running and
ready.
+ InstanceDeployStateRunning InstanceDeployState = "Running"
+ // InstanceDeployStateTerminating indicates the workload is shutting
down.
+ InstanceDeployStateTerminating InstanceDeployState = "Terminating"
+ // InstanceDeployStateFailed indicates the workload has failed.
+ InstanceDeployStateFailed InstanceDeployState = "Failed"
+ // InstanceDeployStateSucceeded indicates the workload has completed
successfully and exited.
+ InstanceDeployStateSucceeded InstanceDeployState = "Succeeded"
+ // InstanceDeployStateCrashing indicates the workload is repeatedly
crashing or restarting.
+ InstanceDeployStateCrashing InstanceDeployState = "Crashing"
+)
+
+// InstanceRegisterState describes whether the instance is visible to the
registry.
+type InstanceRegisterState string
+
+const (
+ // InstanceRegisterStateRegistered indicates the instance has been
registered to the registry.
+ InstanceRegisterStateRegistered InstanceRegisterState = "Registered"
+ // InstanceRegisterStateUnregistered indicates the instance has not
registered yet or has been removed.
+ InstanceRegisterStateUnregistered InstanceRegisterState = "UnRegistered"
+)
+
+// InstanceLifecycleState describes the user-facing lifecycle synthesized from
deploy/register signals.
+type InstanceLifecycleState string
+
+const (
+ // InstanceLifecycleStateStarting indicates the instance is still
warming up.
+ InstanceLifecycleStateStarting InstanceLifecycleState = "Starting"
+ // InstanceLifecycleStateServing indicates the instance is both running
and registered.
+ InstanceLifecycleStateServing InstanceLifecycleState = "Serving"
+ // InstanceLifecycleStateDraining indicates the instance is running but
has started unregistering.
+ InstanceLifecycleStateDraining InstanceLifecycleState = "Draining"
+ // InstanceLifecycleStateTerminating indicates the instance is shutting
down.
+ InstanceLifecycleStateTerminating InstanceLifecycleState = "Terminating"
+ // InstanceLifecycleStateError indicates the instance is in an
unexpected or failed state.
+ InstanceLifecycleStateError InstanceLifecycleState = "Error"
+ // InstanceLifecycleStateUnknown indicates the lifecycle cannot be
inferred from current signals.
+ InstanceLifecycleStateUnknown InstanceLifecycleState = "Unknown"
+)
+
type InstanceDetailResp struct {
- RpcPort int64 `json:"rpcPort"`
- Ip string `json:"ip"`
- AppName string `json:"appName"`
- WorkloadName string `json:"workloadName"`
- Labels map[string]string `json:"labels"`
- CreateTime string `json:"createTime"`
- ReadyTime string `json:"readyTime"`
- RegisterTime string `json:"registerTime"`
- RegisterClusters []string `json:"registerClusters"`
- DeployCluster string `json:"deployCluster"`
- DeployState string `json:"deployState"`
- RegisterState string `json:"registerState"`
- Node string `json:"node"`
- Image string `json:"image"`
- Probes ProbeStruct `json:"probes"`
- Tags map[string]string `json:"tags"`
+ RpcPort int64 `json:"rpcPort"`
+ Ip string `json:"ip"`
+ AppName string `json:"appName"`
+ WorkloadName string `json:"workloadName"`
+ Labels map[string]string `json:"labels"`
+ CreateTime string `json:"createTime"`
+ ReadyTime string `json:"readyTime"`
+ RegisterTime string `json:"registerTime"`
+ RegisterClusters []string `json:"registerClusters"`
+ DeployCluster string `json:"deployCluster"`
+ LifecycleState InstanceLifecycleState `json:"lifecycleState"`
+ DeployState InstanceDeployState `json:"deployState"`
+ RegisterState InstanceRegisterState `json:"registerState"`
+ Node string `json:"node"`
+ Image string `json:"image"`
+ Probes ProbeStruct `json:"probes"`
+ Tags map[string]string `json:"tags"`
}
const (
@@ -158,16 +208,9 @@ func FromInstanceResource(res
*meshresource.InstanceResource, cfg app.AdminConfi
if cfg.Engine.ID == res.Spec.SourceEngine {
r.DeployCluster = cfg.Engine.Name
}
- if strutil.IsNotBlank(instance.DeployState) {
- r.DeployState = instance.DeployState
- } else {
- r.DeployState = "Unknown"
- }
- if strutil.IsBlank(r.RegisterTime) {
- r.RegisterState = "UnRegistered"
- } else {
- r.RegisterState = "Registered"
- }
+ r.DeployState = DeriveInstanceDeployState(instance)
+ r.RegisterState = DeriveInstanceRegisterState(instance)
+ r.LifecycleState = DeriveInstanceLifecycleState(instance,
r.DeployState, r.RegisterState)
r.Node = instance.Node
r.Image = instance.Image
r.Probes = ProbeStruct{}
@@ -196,3 +239,69 @@ func FromInstanceResource(res
*meshresource.InstanceResource, cfg app.AdminConfi
}
return r
}
+
+func DeriveInstanceDeployState(instance *meshproto.Instance)
InstanceDeployState {
+ if instance == nil || strutil.IsBlank(instance.DeployState) {
+ return InstanceDeployStateUnknown
+ }
+ deployState := InstanceDeployState(instance.DeployState)
+ switch deployState {
+ case InstanceDeployStateRunning:
+ if !isPodReady(instance) {
+ return InstanceDeployStateStarting
+ }
+ return InstanceDeployStateRunning
+ default:
+ return deployState
+ }
+}
+
+func DeriveInstanceRegisterState(instance *meshproto.Instance)
InstanceRegisterState {
+ if instance == nil || strutil.IsBlank(instance.RegisterTime) {
+ return InstanceRegisterStateUnregistered
+ }
+ return InstanceRegisterStateRegistered
+}
+
+func DeriveInstanceLifecycleState(
+ instance *meshproto.Instance,
+ deployState InstanceDeployState,
+ registerState InstanceRegisterState,
+) InstanceLifecycleState {
+ switch deployState {
+ case InstanceDeployStateCrashing, InstanceDeployStateFailed,
InstanceDeployStateUnknown, InstanceDeployStateSucceeded:
+ return InstanceLifecycleStateError
+ case InstanceDeployStateTerminating:
+ return InstanceLifecycleStateTerminating
+ }
+
+ if registerState == InstanceRegisterStateRegistered {
+ if deployState == InstanceDeployStateRunning {
+ return InstanceLifecycleStateServing
+ }
+ return InstanceLifecycleStateError
+ }
+
+ if instance != nil && deployState == InstanceDeployStateRunning &&
strutil.IsNotBlank(instance.UnregisterTime) {
+ return InstanceLifecycleStateDraining
+ }
+
+ switch deployState {
+ case InstanceDeployStatePending, InstanceDeployStateStarting,
InstanceDeployStateRunning:
+ return InstanceLifecycleStateStarting
+ default:
+ return InstanceLifecycleStateUnknown
+ }
+}
+
+func isPodReady(instance *meshproto.Instance) bool {
+ for _, condition := range instance.Conditions {
+ if condition == nil {
+ continue
+ }
+ if condition.Type == "Ready" {
+ return condition.Status == "True"
+ }
+ }
+ return false
+}
diff --git a/pkg/console/service/application.go
b/pkg/console/service/application.go
index cd295e2b..9a5ee89d 100644
--- a/pkg/console/service/application.go
+++ b/pkg/console/service/application.go
@@ -95,7 +95,8 @@ func buildAppInstanceInfoResp(instanceRes
*meshresource.InstanceResource, cfg ap
resp.Name = instance.Name
resp.AppName = instance.AppName
resp.CreateTime = instance.CreateTime
- resp.DeployState = instance.DeployState
+ resp.DeployState = model.DeriveInstanceDeployState(instance)
+ resp.LifecycleState = model.DeriveInstanceLifecycleState(instance,
resp.DeployState, model.DeriveInstanceRegisterState(instance))
if cfg.Engine.ID == instance.SourceEngine {
resp.DeployClusters = cfg.Engine.Name
}
@@ -104,7 +105,7 @@ func buildAppInstanceInfoResp(instanceRes
*meshresource.InstanceResource, cfg ap
if d := cfg.FindDiscovery(instanceRes.Mesh); d != nil {
resp.RegisterCluster = d.Name
}
- resp.RegisterState = "Registered"
+ resp.RegisterState = model.DeriveInstanceRegisterState(instance)
resp.RegisterTime = instance.RegisterTime
resp.WorkloadName = instance.WorkloadName
return resp
diff --git a/pkg/core/controller/informer.go b/pkg/core/controller/informer.go
index 92cdb693..cf0003e3 100644
--- a/pkg/core/controller/informer.go
+++ b/pkg/core/controller/informer.go
@@ -229,17 +229,10 @@ func (s *informer) HandleDeltas(obj interface{}, _ bool)
error {
}
// from oldest to newest
for _, d := range deltas {
- var resource model.Resource
- var object interface{}
- if o, ok := d.Object.(cache.DeletedFinalStateUnknown); ok {
- object = o.Obj
- } else {
- object = d.Object
- }
- resource, ok := object.(model.Resource)
- if !ok {
- logger.Errorf("object from ListWatcher is not conformed
to Resource, obj: %v", obj)
- return bizerror.NewAssertionError("Resource",
reflect.TypeOf(obj).Name())
+ resource, err := s.toResource(d.Object)
+ if err != nil {
+ logger.Errorf("object from ListWatcher is not conformed
to Resource, obj: %v, err: %v", obj, err)
+ return err
}
switch d.Type {
case cache.Sync, cache.Replaced, cache.Added, cache.Updated:
@@ -257,6 +250,8 @@ func (s *informer) HandleDeltas(obj interface{}, _ bool)
error {
s.EmitEvent(cache.Added, nil, resource)
}
case cache.Deleted:
+ logger.Infof("informer processing delete delta,
resource kind: %s, key: %s",
+ resource.ResourceKind().ToString(),
resource.ResourceKey())
if err := s.indexer.Delete(resource); err != nil {
logger.Errorf("failed to delete resource from
informer, cause %v, resource: %s,", err, resource.String())
return err
@@ -267,6 +262,31 @@ func (s *informer) HandleDeltas(obj interface{}, _ bool)
error {
return nil
}
+func (s *informer) toResource(obj interface{}) (model.Resource, error) {
+ object := obj
+ if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
+ logger.Debugf("informer resolved tombstone object during delete
handling, key: %s", tombstone.Key)
+ object = tombstone.Obj
+ }
+ if resource, ok := object.(model.Resource); ok {
+ return resource, nil
+ }
+ if s.transform != nil {
+ transformed, err := s.transform(object)
+ if err != nil {
+ return nil, err
+ }
+ if resource, ok := transformed.(model.Resource); ok {
+ return resource, nil
+ }
+ object = transformed
+ }
+ if object == nil {
+ return nil, bizerror.NewAssertionError("Resource", "nil")
+ }
+ return nil, bizerror.NewAssertionError("Resource",
reflect.TypeOf(object).Name())
+}
+
// EmitEvent emits an event to the event bus.
func (s *informer) EmitEvent(typ cache.DeltaType, oldObj model.Resource,
newObj model.Resource) {
event := events.NewResourceChangedEvent(typ, oldObj, newObj)
diff --git a/pkg/core/controller/listwatcher.go
b/pkg/core/controller/listwatcher.go
index e90580f0..4978f011 100644
--- a/pkg/core/controller/listwatcher.go
+++ b/pkg/core/controller/listwatcher.go
@@ -31,3 +31,9 @@ type ResourceListerWatcher interface {
// return nil if there is no need to transform, see
cache.SharedInformer for detail
TransformFunc() cache.TransformFunc
}
+
+// ResourceKeyProvider can be optionally implemented by a
ResourceListerWatcher when
+// raw watch objects need a key that is consistent with the transformed
resource key.
+type ResourceKeyProvider interface {
+ KeyFunc() cache.KeyFunc
+}
diff --git a/pkg/core/discovery/subscriber/rpc_instance.go
b/pkg/core/discovery/subscriber/rpc_instance.go
index 0343e5cf..4879d1b8 100644
--- a/pkg/core/discovery/subscriber/rpc_instance.go
+++ b/pkg/core/discovery/subscriber/rpc_instance.go
@@ -20,6 +20,7 @@ package subscriber
import (
"reflect"
+ "github.com/duke-git/lancet/v2/slice"
"github.com/duke-git/lancet/v2/strutil"
"k8s.io/client-go/tools/cache"
@@ -110,7 +111,12 @@ func (s *RPCInstanceEventSubscriber)
processUpsert(rpcInstanceRes *meshresource.
// We should merge the rpc info into it
if instanceRes != nil {
meshresource.MergeRPCInstanceIntoInstance(rpcInstanceRes,
instanceRes)
- return s.instanceStore.Update(instanceRes)
+ if err := s.instanceStore.Update(instanceRes); err != nil {
+ return err
+ }
+ logger.Infof("instance lifecycle merged rpc source, instance:
%s, rpc: %s, registerState: registered, deployState: %s",
+ instanceRes.ResourceKey(),
rpcInstanceRes.ResourceKey(), instanceRes.Spec.DeployState)
+ return nil
}
// Otherwise we can create a new instance resource by rpc instance
instanceRes = meshresource.FromRPCInstance(rpcInstanceRes)
@@ -119,6 +125,8 @@ func (s *RPCInstanceEventSubscriber)
processUpsert(rpcInstanceRes *meshresource.
logger.Errorf("add instance resource failed, instance: %s, err:
%s", instanceRes.ResourceKey(), err.Error())
return err
}
+ logger.Infof("instance lifecycle created rpc-only instance, instance:
%s, rpc: %s, registerState: registered",
+ instanceRes.ResourceKey(), rpcInstanceRes.ResourceKey())
instanceAddEvent := events.NewResourceChangedEvent(cache.Added, nil,
instanceRes)
s.eventEmitter.Send(instanceAddEvent)
@@ -135,10 +143,26 @@ func (s *RPCInstanceEventSubscriber)
processDelete(rpcInstanceRes *meshresource.
logger.Warnf("cannot find instance resource for rpc instance
%s, skipped deleting instance", rpcInstanceRes.Name)
return nil
}
+ meshresource.ClearRPCInstanceFromInstance(instanceRes)
+ if meshresource.HasRuntimeInstanceSource(instanceRes) {
+ if err := s.instanceStore.Update(instanceRes); err != nil {
+ logger.Errorf("update instance resource failed after
rpc delete, instance: %s, err: %s",
+ instanceRes.ResourceKey(), err.Error())
+ return err
+ }
+ logger.Infof("instance lifecycle rpc source removed, keep
instance by runtime source, instance: %s, rpc: %s, deployState: %s",
+ instanceRes.ResourceKey(),
rpcInstanceRes.ResourceKey(), instanceRes.Spec.DeployState)
+ instanceUpdateEvent :=
events.NewResourceChangedEvent(cache.Updated, instanceRes, instanceRes)
+ s.eventEmitter.Send(instanceUpdateEvent)
+ logger.Debugf("rpc instance delete trigger instance update
event, event: %s", instanceUpdateEvent.String())
+ return nil
+ }
if err := s.instanceStore.Delete(instanceRes); err != nil {
logger.Errorf("delete instance resource failed, instance: %s,
err: %s", instanceRes.ResourceKey(), err.Error())
return err
}
+ logger.Infof("instance lifecycle rpc source removed and no runtime
source remains, deleted instance: %s, rpc: %s",
+ instanceRes.ResourceKey(), rpcInstanceRes.ResourceKey())
instanceDeleteEvent := events.NewResourceChangedEvent(cache.Deleted,
instanceRes, nil)
s.eventEmitter.Send(instanceDeleteEvent)
logger.Debugf("rpc instance delete trigger instance delete event,
event: %s", instanceDeleteEvent.String())
@@ -173,7 +197,7 @@ func (s *RPCInstanceEventSubscriber) getRelatedInstanceRes(
func (s *RPCInstanceEventSubscriber)
findRelatedRuntimeInstanceAndMerge(instanceRes *meshresource.InstanceResource) {
switch s.engineCfg.Type {
case enginecfg.Kubernetes:
- rtInstance := s.getRuntimeInstanceByIp(instanceRes.Spec.Ip)
+ rtInstance := s.getRuntimeInstanceForInstance(instanceRes)
if rtInstance == nil {
logger.Warnf("cannot find runtime instance for instace
%s, skipping merging", instanceRes.ResourceKey())
return
@@ -184,7 +208,8 @@ func (s *RPCInstanceEventSubscriber)
findRelatedRuntimeInstanceAndMerge(instance
}
}
-func (s *RPCInstanceEventSubscriber) getRuntimeInstanceByIp(ip string)
*meshresource.RuntimeInstanceResource {
+func (s *RPCInstanceEventSubscriber) getRuntimeInstanceForInstance(instanceRes
*meshresource.InstanceResource) *meshresource.RuntimeInstanceResource {
+ ip := instanceRes.Spec.Ip
resources, err :=
s.rtInstanceStore.ListByIndexes([]index.IndexCondition{
{IndexName: index.ByRuntimeInstanceIPIndex, Value: ip,
Operator: index.Equals},
})
@@ -195,9 +220,31 @@ func (s *RPCInstanceEventSubscriber)
getRuntimeInstanceByIp(ip string) *meshreso
if len(resources) == 0 {
return nil
}
- runtimeInstanceRes, ok :=
resources[0].(*meshresource.RuntimeInstanceResource)
- if !ok {
- return nil
+ candidates := make([]*meshresource.RuntimeInstanceResource, 0,
len(resources))
+ for _, item := range resources {
+ res, ok := item.(*meshresource.RuntimeInstanceResource)
+ if !ok {
+ continue
+ }
+ candidates = append(candidates, res)
+ }
+ // Prefer exact runtime identity match by app + rpcPort + mesh.
+ for _, candidate := range candidates {
+ if candidate.Spec == nil {
+ continue
+ }
+ if candidate.Mesh == instanceRes.Mesh &&
+ candidate.Spec.AppName == instanceRes.Spec.AppName &&
+ candidate.Spec.RpcPort == instanceRes.Spec.RpcPort {
+ return candidate
+ }
+ }
+ if len(candidates) > 1 {
+ keys := slice.Map(candidates, func(_ int, item
*meshresource.RuntimeInstanceResource) string {
+ return item.ResourceKey()
+ })
+ logger.Warnf("multiple runtime instances share same ip %s,
fallback to first candidate, runtime keys: %v, target instance: %s",
+ ip, keys, instanceRes.ResourceKey())
}
- return runtimeInstanceRes
+ return candidates[0]
}
diff --git a/pkg/core/engine/component.go b/pkg/core/engine/component.go
index 88dc293e..8c893a9e 100644
--- a/pkg/core/engine/component.go
+++ b/pkg/core/engine/component.go
@@ -24,8 +24,6 @@ import (
"reflect"
"sync/atomic"
- "k8s.io/client-go/tools/cache"
-
"github.com/apache/dubbo-admin/pkg/common/bizerror"
enginecfg "github.com/apache/dubbo-admin/pkg/config/engine"
storecfg "github.com/apache/dubbo-admin/pkg/config/store"
@@ -37,6 +35,7 @@ import (
meshresource
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
"github.com/apache/dubbo-admin/pkg/core/runtime"
"github.com/apache/dubbo-admin/pkg/core/store"
+ "k8s.io/client-go/tools/cache"
)
func init() {
@@ -157,7 +156,7 @@ func (e *engineComponent) initInformers(cfg
*enginecfg.Config, emitter events.Em
if err != nil {
return fmt.Errorf("can not find store for resource kind
%s, %w", rk, err)
}
- informer := controller.NewInformerWithOptions(lw, emitter, rs,
cache.MetaNamespaceKeyFunc, controller.Options{ResyncPeriod: 0})
+ informer := controller.NewInformerWithOptions(lw, emitter, rs,
resolveInformerKeyFunc(lw), controller.Options{ResyncPeriod: 0})
if lw.TransformFunc() != nil {
err = informer.SetTransform(lw.TransformFunc())
if err != nil {
@@ -170,6 +169,13 @@ func (e *engineComponent) initInformers(cfg
*enginecfg.Config, emitter events.Em
return nil
}
+func resolveInformerKeyFunc(lw controller.ResourceListerWatcher) cache.KeyFunc
{
+ if provider, ok := lw.(controller.ResourceKeyProvider); ok {
+ return provider.KeyFunc()
+ }
+ return cache.MetaNamespaceKeyFunc
+}
+
func (e *engineComponent) initSubscribers(eventbus events.EventBus) error {
rs, err := e.storeRouter.ResourceKindRoute(meshresource.InstanceKind)
if err != nil {
diff --git a/pkg/core/engine/subscriber/runtime_instance.go
b/pkg/core/engine/subscriber/runtime_instance.go
index f7d38709..b39a0988 100644
--- a/pkg/core/engine/subscriber/runtime_instance.go
+++ b/pkg/core/engine/subscriber/runtime_instance.go
@@ -92,14 +92,7 @@ func (s *RuntimeInstanceEventSubscriber) ProcessEvent(event
events.Event) error
// processUpsert when runtime instance added or updated, we should add/update
the corresponding instance resource
func (s *RuntimeInstanceEventSubscriber) processUpsert(rtInstanceRes
*meshresource.RuntimeInstanceResource) error {
- var instanceResource *meshresource.InstanceResource
- var err error
- switch rtInstanceRes.Spec.SourceEngineType {
- case string(enginecfg.Kubernetes):
- instanceResource, err = s.getRelatedInstanceByIP(rtInstanceRes)
- default:
- instanceResource, err =
s.getRelatedInstanceByName(rtInstanceRes)
- }
+ instanceResource, err := s.getRelatedInstance(rtInstanceRes)
if err != nil {
return err
}
@@ -107,12 +100,19 @@ func (s *RuntimeInstanceEventSubscriber)
processUpsert(rtInstanceRes *meshresour
// so we should merge the runtime info into it
if instanceResource != nil {
meshresource.MergeRuntimeInstanceIntoInstance(rtInstanceRes,
instanceResource)
- return s.instanceStore.Update(instanceResource)
+ if err = s.instanceStore.Update(instanceResource); err != nil {
+ return err
+ }
+ logger.Infof("instance lifecycle merged runtime source,
instance: %s, runtime: %s, deployState: %s, hasRPCSource: %t",
+ instanceResource.ResourceKey(),
rtInstanceRes.ResourceKey(), instanceResource.Spec.DeployState,
+ meshresource.HasRPCInstanceSource(instanceResource))
+ return nil
}
// if instance resource does not exist, that is to say the rpc instance
does not exist in remote registry.
// we need to check whether the runtime instance resource is enough to
create a new instance resource
if !checkAttributesEnough(rtInstanceRes) {
- logger.Warnf("cannot identify runtime instance %s to a dubbo
instance, skipped creating new instance", rtInstanceRes.ResourceKey())
+ logger.Warnf("cannot identify runtime instance %s to a dubbo
instance, skipped creating new instance, reason: %s",
+ rtInstanceRes.ResourceKey(),
runtimeIdentityMissingReason(rtInstanceRes))
return nil
}
// if conditions met, we should create a new instance resource by
runtime instance
@@ -121,6 +121,8 @@ func (s *RuntimeInstanceEventSubscriber)
processUpsert(rtInstanceRes *meshresour
logger.Errorf("add instance resource failed, instance: %s, err:
%s", instanceRes.ResourceKey(), err.Error())
return err
}
+ logger.Infof("instance lifecycle created runtime-only instance,
instance: %s, runtime: %s, deployState: %s",
+ instanceRes.ResourceKey(), rtInstanceRes.ResourceKey(),
instanceRes.Spec.DeployState)
instanceAddEvent := events.NewResourceChangedEvent(cache.Added, nil,
instanceRes)
s.eventEmitter.Send(instanceAddEvent)
logger.Debugf("runtime instance upsert trigger instance add event,
event: %s", instanceAddEvent.String())
@@ -129,14 +131,7 @@ func (s *RuntimeInstanceEventSubscriber)
processUpsert(rtInstanceRes *meshresour
// processDelete when runtime instance deleted, we should delete the
corresponding instance resource
func (s *RuntimeInstanceEventSubscriber) processDelete(rtInstanceRes
*meshresource.RuntimeInstanceResource) error {
- var instanceResource *meshresource.InstanceResource
- var err error
- switch rtInstanceRes.Spec.SourceEngineType {
- case string(enginecfg.Kubernetes):
- instanceResource, err = s.getRelatedInstanceByIP(rtInstanceRes)
- default:
- instanceResource, err =
s.getRelatedInstanceByName(rtInstanceRes)
- }
+ instanceResource, err := s.getRelatedInstance(rtInstanceRes)
if err != nil {
return err
}
@@ -144,16 +139,76 @@ func (s *RuntimeInstanceEventSubscriber)
processDelete(rtInstanceRes *meshresour
logger.Warnf("cannot find instance resource by runtime instance
%s, skipped deleting instance", rtInstanceRes.ResourceKey())
return nil
}
+ meshresource.ClearRuntimeInstanceFromInstance(instanceResource)
+ if meshresource.HasRPCInstanceSource(instanceResource) {
+ if err = s.instanceStore.Update(instanceResource); err != nil {
+ logger.Errorf("update instance resource failed after
runtime delete, instance: %s, err: %s",
+ instanceResource.ResourceKey(), err.Error())
+ return err
+ }
+ logger.Infof("instance lifecycle runtime source removed, keep
instance by rpc source, instance: %s, runtime: %s, registerState: registered",
+ instanceResource.ResourceKey(),
rtInstanceRes.ResourceKey())
+ instanceUpdateEvent :=
events.NewResourceChangedEvent(cache.Updated, instanceResource,
instanceResource)
+ s.eventEmitter.Send(instanceUpdateEvent)
+ logger.Debugf("runtime instance delete trigger instance update
event, event: %s", instanceUpdateEvent.String())
+ return nil
+ }
if err = s.instanceStore.Delete(instanceResource); err != nil {
logger.Errorf("delete instance resource failed, instance: %s,
err: %s", instanceResource.ResourceKey(), err.Error())
return err
}
+ logger.Infof("instance lifecycle runtime source removed and no rpc
source remains, deleted instance: %s, runtime: %s",
+ instanceResource.ResourceKey(), rtInstanceRes.ResourceKey())
instanceDeleteEvent := events.NewResourceChangedEvent(cache.Deleted,
instanceResource, nil)
s.eventEmitter.Send(instanceDeleteEvent)
logger.Debugf("runtime instance delete trigger instance delete event,
event: %s", instanceDeleteEvent.String())
return nil
}
+func (s *RuntimeInstanceEventSubscriber) getRelatedInstance(
+ rtInstanceRes *meshresource.RuntimeInstanceResource)
(*meshresource.InstanceResource, error) {
+ if rtInstanceRes == nil || rtInstanceRes.Spec == nil {
+ return nil, nil
+ }
+ switch rtInstanceRes.Spec.SourceEngineType {
+ case string(enginecfg.Kubernetes):
+ return s.getRelatedKubernetesInstance(rtInstanceRes)
+ default:
+ return s.getRelatedInstanceByName(rtInstanceRes)
+ }
+}
+
+func (s *RuntimeInstanceEventSubscriber) getRelatedKubernetesInstance(
+ rtInstanceRes *meshresource.RuntimeInstanceResource)
(*meshresource.InstanceResource, error) {
+ if rtInstanceRes == nil || rtInstanceRes.Spec == nil {
+ return nil, nil
+ }
+ if hasRuntimeIdentity(rtInstanceRes) {
+ instanceResName :=
meshresource.BuildInstanceResName(rtInstanceRes.Spec.AppName,
rtInstanceRes.Spec.Ip, rtInstanceRes.Spec.RpcPort)
+ if strutil.IsNotBlank(rtInstanceRes.Mesh) &&
constants.DefaultMesh != rtInstanceRes.Mesh {
+ res, exists, err :=
s.instanceStore.GetByKey(coremodel.BuildResourceKey(rtInstanceRes.Mesh,
instanceResName))
+ if err != nil {
+ return nil, err
+ }
+ if exists {
+ instanceRes, ok :=
res.(*meshresource.InstanceResource)
+ if !ok {
+ return nil,
bizerror.NewAssertionError("InstanceResource", reflect.TypeOf(res).Name())
+ }
+ return instanceRes, nil
+ }
+ }
+ instanceRes, err := s.getRelatedInstanceByName(rtInstanceRes)
+ if err != nil {
+ return nil, err
+ }
+ if instanceRes != nil {
+ return instanceRes, nil
+ }
+ }
+ return s.getRelatedInstanceByIP(rtInstanceRes)
+}
+
func (s *RuntimeInstanceEventSubscriber) getRelatedInstanceByName(
rtInstanceRes *meshresource.RuntimeInstanceResource)
(*meshresource.InstanceResource, error) {
if rtInstanceRes.Spec == nil ||
strutil.IsBlank(rtInstanceRes.Spec.AppName) ||
@@ -171,21 +226,28 @@ func (s *RuntimeInstanceEventSubscriber)
getRelatedInstanceByName(
return nil, nil
}
instanceResList := make([]*meshresource.InstanceResource,
len(resources))
+ filtered := make([]*meshresource.InstanceResource, 0, len(resources))
for i, item := range resources {
res, ok := item.(*meshresource.InstanceResource)
if !ok {
return nil,
bizerror.NewAssertionError("InstanceResource", reflect.TypeOf(item).Name())
}
instanceResList[i] = res
+ if strutil.IsBlank(rtInstanceRes.Mesh) || res.Mesh ==
rtInstanceRes.Mesh {
+ filtered = append(filtered, res)
+ }
}
- if len(instanceResList) > 1 {
- resKeys := slice.Map(instanceResList, func(index int, item
*meshresource.InstanceResource) string {
+ if len(filtered) == 0 {
+ return nil, nil
+ }
+ if len(filtered) > 1 {
+ resKeys := slice.Map(filtered, func(index int, item
*meshresource.InstanceResource) string {
return item.ResourceKey()
})
logger.Warnf("there are more than two instances which have the
same name, instance keys: %s, name: %s", resKeys, instanceResName)
return nil, nil
}
- return instanceResList[0], nil
+ return filtered[0], nil
}
func (s *RuntimeInstanceEventSubscriber) getRelatedInstanceByIP(
@@ -225,3 +287,38 @@ func checkAttributesEnough(rtInstanceRes
*meshresource.RuntimeInstanceResource)
}
return true
}
+
+func runtimeIdentityMissingReason(rtInstanceRes
*meshresource.RuntimeInstanceResource) string {
+ if rtInstanceRes == nil {
+ return "runtime instance is nil"
+ }
+ if rtInstanceRes.Spec == nil {
+ return "runtime instance spec is nil"
+ }
+ if strutil.IsBlank(rtInstanceRes.Spec.AppName) {
+ return "appName is empty"
+ }
+ if strutil.IsBlank(rtInstanceRes.Spec.Ip) {
+ return "pod ip is empty"
+ }
+ if rtInstanceRes.Spec.RpcPort <= 0 {
+ return "rpcPort is not configured"
+ }
+ if strutil.IsBlank(rtInstanceRes.Mesh) {
+ return "mesh is empty"
+ }
+ if constants.DefaultMesh == rtInstanceRes.Mesh {
+ return "mesh is default, missing registry identifier"
+ }
+ return "unknown"
+}
+
+func hasRuntimeIdentity(rtInstanceRes *meshresource.RuntimeInstanceResource)
bool {
+ if rtInstanceRes == nil || rtInstanceRes.Spec == nil {
+ return false
+ }
+ return strutil.IsNotBlank(rtInstanceRes.Spec.AppName) &&
+ strutil.IsNotBlank(rtInstanceRes.Spec.Ip) &&
+ rtInstanceRes.Spec.RpcPort > 0 &&
+ strutil.IsNotBlank(rtInstanceRes.Mesh)
+}
diff --git a/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go
b/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go
index ffc72bab..f7b86c34 100644
--- a/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go
+++ b/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go
@@ -19,6 +19,9 @@ package v1alpha1
import (
"fmt"
+ "time"
+
+ "github.com/apache/dubbo-admin/pkg/common/constants"
)
func BuildInstanceResName(appName string, ip string, rpcPort int64) string {
@@ -79,3 +82,55 @@ func MergeRuntimeInstanceIntoInstance(
instanceRes.Spec.Conditions = rtInstanceRes.Spec.Conditions
instanceRes.Spec.SourceEngine = rtInstanceRes.Spec.SourceEngine
}
+
+func ClearRPCInstanceFromInstance(instanceRes *InstanceResource) {
+ if instanceRes == nil || instanceRes.Spec == nil {
+ return
+ }
+ instanceRes.Spec.ReleaseVersion = ""
+ instanceRes.Spec.RegisterTime = ""
+ instanceRes.Spec.UnregisterTime =
time.Now().Format(constants.TimeFormatStr)
+ instanceRes.Spec.Protocol = ""
+ instanceRes.Spec.Serialization = ""
+ instanceRes.Spec.PreferSerialization = ""
+ instanceRes.Spec.Tags = nil
+}
+
+func ClearRuntimeInstanceFromInstance(instanceRes *InstanceResource) {
+ if instanceRes == nil || instanceRes.Spec == nil {
+ return
+ }
+ instanceRes.Labels = nil
+ instanceRes.Spec.Image = ""
+ instanceRes.Spec.CreateTime = ""
+ instanceRes.Spec.StartTime = ""
+ instanceRes.Spec.ReadyTime = ""
+ instanceRes.Spec.DeployState = ""
+ instanceRes.Spec.WorkloadType = ""
+ instanceRes.Spec.WorkloadName = ""
+ instanceRes.Spec.Node = ""
+ instanceRes.Spec.Probes = nil
+ instanceRes.Spec.Conditions = nil
+ instanceRes.Spec.SourceEngine = ""
+}
+
+func HasRuntimeInstanceSource(instanceRes *InstanceResource) bool {
+ if instanceRes == nil || instanceRes.Spec == nil {
+ return false
+ }
+ return instanceRes.Spec.SourceEngine != "" ||
+ instanceRes.Spec.DeployState != "" ||
+ instanceRes.Spec.WorkloadName != "" ||
+ instanceRes.Spec.Node != "" ||
+ instanceRes.Spec.Image != "" ||
+ instanceRes.Spec.StartTime != "" ||
+ instanceRes.Spec.ReadyTime != "" ||
+ len(instanceRes.Spec.Conditions) > 0
+}
+
+func HasRPCInstanceSource(instanceRes *InstanceResource) bool {
+ if instanceRes == nil || instanceRes.Spec == nil {
+ return false
+ }
+ return instanceRes.Spec.RegisterTime != ""
+}
diff --git a/pkg/engine/kubernetes/listerwatcher/runtime_instance.go
b/pkg/engine/kubernetes/listerwatcher/runtime_instance.go
index 725b9218..bc35f3fb 100644
--- a/pkg/engine/kubernetes/listerwatcher/runtime_instance.go
+++ b/pkg/engine/kubernetes/listerwatcher/runtime_instance.go
@@ -48,6 +48,7 @@ type PodListerWatcher struct {
}
var _ controller.ResourceListerWatcher = &PodListerWatcher{}
+var _ controller.ResourceKeyProvider = &PodListerWatcher{}
func NewPodListWatcher(clientset *kubernetes.Clientset, cfg *enginecfg.Config)
(*PodListerWatcher, error) {
var selector fields.Selector
@@ -105,10 +106,7 @@ func (p *PodListerWatcher) TransformFunc()
cache.TransformFunc {
readyTime =
c.LastTransitionTime.Format(constants.TimeFormatStr)
}
})
- phase := string(pod.Status.Phase)
- if pod.DeletionTimestamp != nil {
- phase = meshproto.InstanceTerminating
- }
+ phase := derivePodPhase(pod)
var workloadName string
var workloadType string
if len(pod.GetOwnerReferences()) > 0 {
@@ -173,6 +171,105 @@ func (p *PodListerWatcher) TransformFunc()
cache.TransformFunc {
}
}
+func (p *PodListerWatcher) KeyFunc() cache.KeyFunc {
+ return p.resourceKeyFromObject
+}
+
+func (p *PodListerWatcher) resourceKeyFromObject(obj interface{}) (string,
error) {
+ for {
+ tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+ if !ok {
+ break
+ }
+ obj = tombstone.Obj
+ }
+ switch o := obj.(type) {
+ case *v1.Pod:
+ return p.resourceKeyFromPod(o), nil
+ case *meshresource.RuntimeInstanceResource:
+ return o.ResourceKey(), nil
+ default:
+ if obj == nil {
+ return "", bizerror.NewAssertionError("Pod", "nil")
+ }
+ return "", bizerror.NewAssertionError("Pod",
reflect.TypeOf(obj).Name())
+ }
+}
+
+func (p *PodListerWatcher) resourceKeyFromPod(pod *v1.Pod) string {
+ return coremodel.BuildResourceKey(p.getDubboMesh(pod), pod.Name)
+}
+
+func derivePodPhase(pod *v1.Pod) string {
+ if pod == nil {
+ return "Unknown"
+ }
+ if pod.DeletionTimestamp != nil {
+ return meshproto.InstanceTerminating
+ }
+ if hasCrashingContainerStatus(pod.Status.InitContainerStatuses) ||
hasCrashingContainerStatus(pod.Status.ContainerStatuses) {
+ return meshproto.InstanceCrashing
+ }
+ switch pod.Status.Phase {
+ case v1.PodPending:
+ if hasStartingContainerStatus(pod.Status.InitContainerStatuses)
|| hasStartingContainerStatus(pod.Status.ContainerStatuses) {
+ return meshproto.InstanceStarting
+ }
+ return string(v1.PodPending)
+ case v1.PodRunning:
+ if !isPodReady(pod.Status.Conditions) {
+ return meshproto.InstanceStarting
+ }
+ return string(v1.PodRunning)
+ case v1.PodFailed:
+ return string(v1.PodFailed)
+ case v1.PodSucceeded:
+ return string(v1.PodSucceeded)
+ case v1.PodUnknown:
+ return string(v1.PodUnknown)
+ default:
+ return string(pod.Status.Phase)
+ }
+}
+
+func isPodReady(conditions []v1.PodCondition) bool {
+ for _, condition := range conditions {
+ if condition.Type == v1.PodReady {
+ return condition.Status == v1.ConditionTrue
+ }
+ }
+ return false
+}
+
+func hasStartingContainerStatus(statuses []v1.ContainerStatus) bool {
+ for _, status := range statuses {
+ if status.State.Waiting == nil {
+ continue
+ }
+ switch status.State.Waiting.Reason {
+ case "ContainerCreating", "PodInitializing":
+ return true
+ }
+ }
+ return false
+}
+
+func hasCrashingContainerStatus(statuses []v1.ContainerStatus) bool {
+ for _, status := range statuses {
+ if status.State.Waiting != nil {
+ switch status.State.Waiting.Reason {
+ case "CrashLoopBackOff", "ImagePullBackOff",
"ErrImagePull", "RunContainerError",
+ "CreateContainerConfigError",
"CreateContainerError", "StartError":
+ return true
+ }
+ }
+ if status.State.Terminated != nil &&
status.State.Terminated.ExitCode != 0 {
+ return true
+ }
+ }
+ return false
+}
+
func (p *PodListerWatcher) getMainContainer(pod *v1.Pod) *v1.Container {
containers := pod.Spec.Containers
strategy := p.cfg.Properties.MainContainerChooseStrategy
diff --git a/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml
b/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml
index 91a5961e..1bcdfdc3 100644
--- a/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml
+++ b/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml
@@ -114,6 +114,8 @@ spec:
app: shop-order
orderVersion: v1
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20882"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-order
@@ -161,6 +163,8 @@ spec:
app: shop-order
orderVersion: v2
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20883"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-order
@@ -205,6 +209,8 @@ spec:
labels:
app: shop-user
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20884"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-user
@@ -250,6 +256,8 @@ spec:
app: shop-detail
detailVersion: v1
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20885"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-detail
@@ -297,6 +305,8 @@ spec:
app: shop-detail
detailVersion: v2
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20886"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-detail
@@ -344,6 +354,8 @@ spec:
app: shop-comment
commentVersion: v1
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20887"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-comment
@@ -391,6 +403,8 @@ spec:
app: shop-comment
commentVersion: v2
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20888"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-comment
@@ -434,6 +448,8 @@ spec:
labels:
app: shop-user
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20892"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-user-gray
@@ -480,6 +496,8 @@ spec:
app: shop-order
orderVersion: v1
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20891"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-order
@@ -525,6 +543,8 @@ spec:
app: shop-detail
detailVersion: v1
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20890"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-detail
@@ -570,6 +590,8 @@ spec:
app: shop-comment
commentVersion: v1
app-type: dubbo
+ org.apache.dubbo/dubbo-rpc-port: "20889"
+ org.apache.dubbo/registry: nacos2.5
spec:
containers:
- name: shop-comment
diff --git a/release/kubernetes/dubbo-system/dubbo-admin.yaml
b/release/kubernetes/dubbo-system/dubbo-admin.yaml
index 18faf418..54216c4f 100644
--- a/release/kubernetes/dubbo-system/dubbo-admin.yaml
+++ b/release/kubernetes/dubbo-system/dubbo-admin.yaml
@@ -113,6 +113,20 @@ data:
id: default
name: default
type: kubernetes
+ properties:
+ podWatchSelector: metadata.namespace=dubbo-samples-shop
+ dubboAppIdentifier:
+ type: ByLabel
+ labelKey: app
+ dubboRPCPortIdentifier:
+ type: ByLabel
+ labelKey: org.apache.dubbo/dubbo-rpc-port
+ dubboDiscoveryIdentifier:
+ type: ByLabel
+ labelKey: org.apache.dubbo/registry
+ mainContainerChooseStrategy:
+ type: ByIndex
+ index: 0
---
apiVersion: v1
kind: Service
diff --git a/ui-vue3/src/base/constants.ts b/ui-vue3/src/base/constants.ts
index 166772ef..e1c865fe 100644
--- a/ui-vue3/src/base/constants.ts
+++ b/ui-vue3/src/base/constants.ts
@@ -57,8 +57,8 @@ export const PRIMARY_COLOR_T = (percent: string) =>
computed(() => PRIMARY_COLOR
export const PRIMARY_COLOR_R = computed(() =>
getTextColorByBackground(PRIMARY_COLOR.value))
export const INSTANCE_REGISTER_COLOR: { [key: string]: string } = {
- HEALTHY: 'green',
- REGISTED: 'green'
+ REGISTERED: 'green',
+ UNREGISTERED: 'default'
}
export const TAB_HEADER_TITLE: Component = {
@@ -83,7 +83,19 @@ export const TAB_HEADER_TITLE: Component = {
*/
export const INSTANCE_DEPLOY_COLOR: { [key: string]: string } = {
RUNNING: 'green',
+ STARTING: 'gold',
PENDING: 'yellow',
TERMINATING: 'red',
- CRASHING: 'darkRed'
+ FAILED: 'red',
+ UNKNOWN: 'default',
+ CRASHING: 'red'
+}
+
+export const INSTANCE_LIFECYCLE_COLOR: { [key: string]: string } = {
+ STARTING: 'gold',
+ SERVING: 'green',
+ DRAINING: 'orange',
+ TERMINATING: 'red',
+ ERROR: 'red',
+ UNKNOWN: 'default'
}
diff --git a/ui-vue3/src/base/i18n/en.ts b/ui-vue3/src/base/i18n/en.ts
index f81082e9..777c7dc9 100644
--- a/ui-vue3/src/base/i18n/en.ts
+++ b/ui-vue3/src/base/i18n/en.ts
@@ -172,6 +172,7 @@ const words: I18nType = {
instanceName: 'InstanceName',
ip: 'Ip',
name: 'Name',
+ lifecycleState: 'Lifecycle State',
deployState: 'Deploy State',
deployCluster: 'Deploy Cluster',
deployClusters: 'Deploy Clusters',
@@ -564,6 +565,7 @@ const words: I18nType = {
dependentService: 'Dependent Service',
submit: 'Submit',
reset: 'Reset',
+ refresh: 'Refresh',
router: {
resource: {
app: {
diff --git a/ui-vue3/src/base/i18n/zh.ts b/ui-vue3/src/base/i18n/zh.ts
index 3d1c4765..c382c0e9 100644
--- a/ui-vue3/src/base/i18n/zh.ts
+++ b/ui-vue3/src/base/i18n/zh.ts
@@ -194,6 +194,7 @@ const words: I18nType = {
instanceIP: '实例IP',
ip: 'IP',
name: '实例名称',
+ lifecycleState: '生命周期状态',
deployState: '部署状态',
deployCluster: '部署集群',
deployClusters: '部署集群',
@@ -554,6 +555,7 @@ const words: I18nType = {
idx: '序号',
submit: '提交',
reset: '重置',
+ refresh: '刷新',
router: {
resource: {
app: {
diff --git a/ui-vue3/src/components/SearchTable.vue
b/ui-vue3/src/components/SearchTable.vue
index 5324e6a4..e67424df 100644
--- a/ui-vue3/src/components/SearchTable.vue
+++ b/ui-vue3/src/components/SearchTable.vue
@@ -68,7 +68,7 @@
</a-form>
</a-col>
<a-col :span="6">
- <a-flex style="justify-content: flex-end">
+ <a-flex style="justify-content: flex-end; align-items: center; gap:
12px">
<slot name="customOperation"></slot>
<div class="common-tool" @click="commonTool.customColumns =
!commonTool.customColumns">
<div class="custom-column button">
@@ -221,22 +221,23 @@ function hideColumn(item: any) {
.common-tool {
margin-top: 5px;
- width: 100px;
cursor: pointer;
position: relative;
+ flex: none;
.button {
- vertical-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
line-height: 24px;
font-size: 24px;
- float: right;
&:hover {
color: v-bind('PRIMARY_COLOR');
}
svg {
- margin-left: 10px;
+ margin-left: 0;
}
}
diff --git a/ui-vue3/src/views/resources/applications/tabs/instance.vue
b/ui-vue3/src/views/resources/applications/tabs/instance.vue
index ed59e4e0..b75bad3a 100644
--- a/ui-vue3/src/views/resources/applications/tabs/instance.vue
+++ b/ui-vue3/src/views/resources/applications/tabs/instance.vue
@@ -39,13 +39,24 @@
</a-tooltip>
</template>
<template v-if="column.dataIndex === 'deployState'">
- <a-tag :color="INSTANCE_DEPLOY_COLOR[text.toUpperCase()]">{{ text
}}</a-tag>
+ <a-tag :color="INSTANCE_DEPLOY_COLOR[(text ||
'UNKNOWN').toUpperCase()] || 'default'">
+ {{ text }}
+ </a-tag>
+ </template>
+ <template v-if="column.dataIndex === 'lifecycleState'">
+ <a-tag :color="INSTANCE_LIFECYCLE_COLOR[(text ||
'UNKNOWN').toUpperCase()] || 'default'">
+ {{ text }}
+ </a-tag>
</template>
<template v-if="column.dataIndex === 'deployClusters'">
<a-tag>{{ text }}</a-tag>
</template>
<template v-if="column.dataIndex === 'registerState'">
- <a-tag :color="INSTANCE_REGISTER_COLOR[text.toUpperCase()]">{{ text
}}</a-tag>
+ <a-tag
+ :color="INSTANCE_REGISTER_COLOR[(text ||
'UNREGISTERED').toUpperCase()] || 'default'"
+ >
+ {{ text }}
+ </a-tag>
</template>
<template v-if="column.dataIndex === 'registerCluster'">
<a-tag>{{ text }}</a-tag>
@@ -64,7 +75,12 @@
<script setup lang="ts">
import { onMounted, provide, reactive } from 'vue'
-import { INSTANCE_DEPLOY_COLOR, INSTANCE_REGISTER_COLOR, PRIMARY_COLOR } from
'@/base/constants'
+import {
+ INSTANCE_DEPLOY_COLOR,
+ INSTANCE_LIFECYCLE_COLOR,
+ INSTANCE_REGISTER_COLOR,
+ PRIMARY_COLOR
+} from '@/base/constants'
import { Icon } from '@iconify/vue'
import SearchTable from '@/components/SearchTable.vue'
import { SearchDomain } from '@/utils/SearchUtil'
@@ -126,6 +142,12 @@ const columns = [
// sorter: true,
width: 150
},
+ {
+ title: 'instanceDomain.lifecycleState',
+ dataIndex: 'lifecycleState',
+ key: 'lifecycleState',
+ width: 150
+ },
{
title: 'instanceDomain.deployState',
dataIndex: 'deployState',
@@ -203,7 +225,7 @@ function instanceInfo(params: any) {
* on (pod) group_left(pod_ip)
kube_pod_info{pod_ip="${ip}"}`)
instance.cpu = isNumber(cpu) ? cpu.toFixed(3) + 'u' : cpu
- instance.memory = bytesToHuman(mem)
+ instance.memory = isNumber(mem) ? bytesToHuman(mem) : mem
})
})
}
@@ -255,7 +277,7 @@ onMounted(() => {
})
const viewDetail = (record: any) => {
- router.push(`/resources/instances/detail/${record.name}/${record.ip}`)
+
router.push(`/resources/instances/detail/${record.name}/${record.ip}/${record.appName}`)
}
provide(PROVIDE_INJECT_KEY.SEARCH_DOMAIN, searchDomain)
diff --git a/ui-vue3/src/views/resources/instances/index.vue
b/ui-vue3/src/views/resources/instances/index.vue
index ae88eb39..6055b087 100644
--- a/ui-vue3/src/views/resources/instances/index.vue
+++ b/ui-vue3/src/views/resources/instances/index.vue
@@ -17,6 +17,18 @@
<template>
<div class="instances-container">
<search-table :search-domain="searchDomain">
+ <template #customOperation>
+ <a-button
+ class="refresh-button"
+ :loading="searchDomain.table.loading"
+ @click="searchDomain.onSearch()"
+ >
+ <template #icon>
+ <Icon icon="material-symbols:refresh-rounded"></Icon>
+ </template>
+ {{ $t('refresh') }}
+ </a-button>
+ </template>
<template #bodyCell="{ text, record, index, column }">
<template v-if="column.dataIndex === 'name'">
<a-tooltip :title="text">
@@ -43,18 +55,14 @@
<span>{{ text }}</span>
</template>
- <template v-if="column.dataIndex === 'deployState'">
- <a-tag :color="INSTANCE_DEPLOY_COLOR[text.toUpperCase()]">{{ text
}}</a-tag>
- </template>
-
- <template v-if="column.dataIndex === 'deployCluster'">
- <a-tag color="grey">
+ <template v-if="column.dataIndex === 'lifecycleState'">
+ <a-tag :color="INSTANCE_LIFECYCLE_COLOR[(text ||
'UNKNOWN').toUpperCase()] || 'default'">
{{ text }}
</a-tag>
</template>
- <template v-if="column.dataIndex === 'registerState'">
- <a-tag :color="INSTANCE_REGISTER_COLOR[text.toUpperCase()]">
+ <template v-if="column.dataIndex === 'deployCluster'">
+ <a-tag color="grey">
{{ text }}
</a-tag>
</template>
@@ -83,7 +91,7 @@ import { searchInstances } from '@/api/service/instance'
import SearchTable from '@/components/SearchTable.vue'
import { SearchDomain } from '@/utils/SearchUtil'
import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
-import { INSTANCE_DEPLOY_COLOR, INSTANCE_REGISTER_COLOR, PRIMARY_COLOR } from
'@/base/constants'
+import { INSTANCE_LIFECYCLE_COLOR, PRIMARY_COLOR } from '@/base/constants'
import router from '@/router'
import { Icon } from '@iconify/vue'
import { queryMetrics } from '@/base/http/promQuery'
@@ -110,13 +118,11 @@ let columns = [
width: 200
},
{
- title: 'instanceDomain.deployState',
- key: 'deployState',
- dataIndex: 'deployState',
- width: 120
- // sorter: (a: any, b: any) => sortString(a.deployState, b.deployState)
+ title: 'instanceDomain.lifecycleState',
+ key: 'lifecycleState',
+ dataIndex: 'lifecycleState',
+ width: 130
},
-
{
title: 'instanceDomain.deployCluster',
key: 'deployCluster',
@@ -124,13 +130,6 @@ let columns = [
// sorter: (a: any, b: any) => sortString(a.deployCluster,
b.deployCluster),
width: 120
},
- {
- title: 'instanceDomain.registerState',
- key: 'registerState',
- dataIndex: 'registerState',
- // sorter: (a: any, b: any) => sortString(a.registerState,
b.registerState),
- width: 120
- },
{
title: 'instanceDomain.registerCluster',
key: 'registerClusters',
diff --git
a/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue
b/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue
index bcfaade9..cf21a233 100644
--- a/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue
+++ b/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue
@@ -17,18 +17,53 @@
<template>
<!-- example like blow-->
<div class="__container_AppTabHeaderSlot">
- <a-row>
- <a-col :span="12">
+ <a-row :gutter="12" align="middle">
+ <a-col flex="none">
<span class="header-desc">{{ $t('instanceDomain.name') }}: {{
route.params?.name }}</span>
</a-col>
+ <a-col flex="none">
+ <a-tag :color="lifecycleColor(instanceLifecycleState)">
+ {{ instanceLifecycleState }}
+ </a-tag>
+ </a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
+import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
+import { getInstanceDetail } from '@/api/service/instance'
+import { INSTANCE_LIFECYCLE_COLOR } from '@/base/constants'
const route = useRoute()
+const instanceLifecycleState = ref('Unknown')
+
+const lifecycleColor = (state?: string) => {
+ return INSTANCE_LIFECYCLE_COLOR[(state || 'UNKNOWN').toUpperCase()] ||
'default'
+}
+
+const fetchInstanceLifecycleState = async () => {
+ const instanceName = route.params?.name
+ if (!instanceName) {
+ instanceLifecycleState.value = 'Unknown'
+ return
+ }
+
+ try {
+ const { data } = await getInstanceDetail({
+ instanceName,
+ instanceIP: route.params?.pathId
+ })
+ instanceLifecycleState.value = data?.lifecycleState || 'Unknown'
+ } catch {
+ instanceLifecycleState.value = 'Unknown'
+ }
+}
+
+watch(() => [route.params?.name, route.params?.pathId],
fetchInstanceLifecycleState, {
+ immediate: true
+})
</script>
<style lang="less" scoped>
.__container_AppTabHeaderSlot {
diff --git a/ui-vue3/src/views/resources/instances/tabs/detail.vue
b/ui-vue3/src/views/resources/instances/tabs/detail.vue
index 134144ea..495a359b 100644
--- a/ui-vue3/src/views/resources/instances/tabs/detail.vue
+++ b/ui-vue3/src/views/resources/instances/tabs/detail.vue
@@ -51,21 +51,13 @@
<a-col :span="12">
<a-card class="_detail" style="height: 100%">
<a-descriptions class="description-column" :column="1">
- <!-- deployState -->
<a-descriptions-item
:label="$t('instanceDomain.deployState')"
:labelStyle="{ fontWeight: 'bold' }"
>
- <a-typography-paragraph
- type="success"
- style=""
- v-if="instanceDetail?.deployState === 'Running'"
- >
- Running
- </a-typography-paragraph>
- <a-typography-paragraph type="danger" v-else>
+ <a-tag :color="deployColor(instanceDetail?.deployState)">
{{ instanceDetail?.deployState }}
- </a-typography-paragraph>
+ </a-tag>
</a-descriptions-item>
<!-- Start time -->
@@ -249,7 +241,7 @@ import { type ComponentInternalInstance,
getCurrentInstance, onMounted, reactive
import { CopyOutlined } from '@ant-design/icons-vue'
import useClipboard from 'vue-clipboard3'
import { message } from 'ant-design-vue'
-import { PRIMARY_COLOR, PRIMARY_COLOR_T } from '@/base/constants'
+import { INSTANCE_DEPLOY_COLOR, PRIMARY_COLOR, PRIMARY_COLOR_T } from
'@/base/constants'
import { getInstanceDetail } from '@/api/service/instance'
import { useRoute, useRouter } from 'vue-router'
import { formattedDate } from '@/utils/DateUtil'
@@ -296,6 +288,10 @@ function copyIt(v: string) {
const isProbeOpen = (status: boolean) => {
return status ? '开启' : '关闭'
}
+
+const deployColor = (state?: string) => {
+ return INSTANCE_DEPLOY_COLOR[(state || 'UNKNOWN').toUpperCase()] || 'default'
+}
</script>
<style lang="less" scoped>