diff --git a/cmd/module-controller/main.go b/cmd/module-controller/main.go
index 56697c9..5535736 100644
--- a/cmd/module-controller/main.go
+++ b/cmd/module-controller/main.go
@@ -16,16 +16,21 @@ package main
 
 import (
 	"context"
+	"errors"
+	"fmt"
+	"github.com/go-logr/logr"
+	"github.com/koupleless/virtual-kubelet/vnode_controller"
 	"os"
 	"os/signal"
+	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
+	log2 "sigs.k8s.io/controller-runtime/pkg/log"
 	"strconv"
 	"syscall"
 
 	"github.com/google/uuid"
 	"github.com/koupleless/module_controller/common/model"
 	"github.com/koupleless/module_controller/controller/module_deployment_controller"
-	"github.com/koupleless/module_controller/module_tunnels"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_http_tunnel"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_mqtt_tunnel"
 	"github.com/koupleless/module_controller/report_server"
@@ -35,7 +40,6 @@ import (
 	"github.com/koupleless/virtual-kubelet/common/trace/opencensus"
 	"github.com/koupleless/virtual-kubelet/common/tracker"
 	"github.com/koupleless/virtual-kubelet/common/utils"
-	"github.com/koupleless/virtual-kubelet/controller/vnode_controller"
 	vkModel "github.com/koupleless/virtual-kubelet/model"
 	"github.com/koupleless/virtual-kubelet/tunnel"
 	"github.com/sirupsen/logrus"
@@ -98,6 +102,11 @@ func main() {
 
 	// Initialize controller manager
 	kubeConfig := config.GetConfigOrDie()
+	// TODO: should support to set from parameter
+	kubeConfig.QPS = 100
+	kubeConfig.Burst = 200
+	ctrl.SetLogger(logr.New(log2.NullLogSink{}))
+
 	mgr, err := manager.New(kubeConfig, manager.Options{
 		Cache:                  cache.Options{},
 		HealthProbeBindAddress: ":8081",
@@ -113,83 +122,42 @@ func main() {
 
 	tracker.SetTracker(&tracker.DefaultTracker{})
 
-	// Initialize tunnels based on configuration
-	tunnels := make([]tunnel.Tunnel, 0)
-	moduleTunnels := make([]module_tunnels.ModuleTunnel, 0)
-
-	mqttTunnelEnable := utils.GetEnv("ENABLE_MQTT_TUNNEL", "false")
-	if mqttTunnelEnable == "true" {
-		mqttTl := &koupleless_mqtt_tunnel.MqttTunnel{
-			Cache:  mgr.GetCache(),
-			Client: mgr.GetClient(),
-		}
-
-		tunnels = append(tunnels, mqttTl)
-		moduleTunnels = append(moduleTunnels, mqttTl)
-	}
-
-	httpTunnelEnable := utils.GetEnv("ENABLE_HTTP_TUNNEL", "false")
-	if httpTunnelEnable == "true" {
-		httpTunnelListenPort, err := strconv.Atoi(utils.GetEnv("HTTP_TUNNEL_LISTEN_PORT", "7777"))
-
-		if err != nil {
-			log.G(ctx).WithError(err).Error("failed to parse HTTP_TUNNEL_LISTEN_PORT, set default port 7777")
-			httpTunnelListenPort = 7777
-		}
-
-		httpTl := &koupleless_http_tunnel.HttpTunnel{
-			Cache:  mgr.GetCache(),
-			Client: mgr.GetClient(),
-			Port:   httpTunnelListenPort,
-		}
-		tunnels = append(tunnels, httpTl)
-		moduleTunnels = append(moduleTunnels, httpTl)
-	}
-
 	// Configure and create VNode controller
 	rcc := vkModel.BuildVNodeControllerConfig{
 		ClientID:         clientID,
 		Env:              env,
-		VPodIdentity:     model.ComponentModule,
+		VPodType:         model.ComponentModule,
 		IsCluster:        isCluster,
 		WorkloadMaxLevel: workloadMaxLevel,
 		VNodeWorkerNum:   vnodeWorkerNum,
 	}
 
-	vc, err := vnode_controller.NewVNodeController(&rcc, tunnels)
+	mdc, err := module_deployment_controller.NewModuleDeploymentController(env)
 	if err != nil {
-		log.G(ctx).Error(err, "unable to set up VNodeController")
+		log.G(ctx).Error(err, "unable to set up module_deployment_controller")
 		return
 	}
 
-	err = vc.SetupWithManager(ctx, mgr)
+	err = mdc.SetupWithManager(ctx, mgr)
 	if err != nil {
 		log.G(ctx).WithError(err).Error("unable to setup vnode controller")
 		return
 	}
 
-	mdc, err := module_deployment_controller.NewModuleDeploymentController(env, moduleTunnels)
+	tunnel := startTunnels(ctx, clientID, env, mgr, mdc)
+
+	vc, err := vnode_controller.NewVNodeController(&rcc, tunnel)
 	if err != nil {
-		log.G(ctx).Error(err, "unable to set up module_deployment_controller")
+		log.G(ctx).Error(err, "unable to set up VNodeController")
 		return
 	}
 
-	err = mdc.SetupWithManager(ctx, mgr)
+	err = vc.SetupWithManager(ctx, mgr)
 	if err != nil {
 		log.G(ctx).WithError(err).Error("unable to setup vnode controller")
 		return
 	}
 
-	// Start all tunnels
-	for _, t := range tunnels {
-		err = t.Start(ctx, clientID, env)
-		if err != nil {
-			log.G(ctx).WithError(err).Error("failed to start tunnel", t.Key())
-		} else {
-			log.G(ctx).Info("Tunnel started: ", t.Key())
-		}
-	}
-
 	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
 		log.G(ctx).Error(err, "unable to set up health check")
 		os.Exit(1)
@@ -205,3 +173,50 @@ func main() {
 		log.G(ctx).WithError(err).Error("failed to start manager")
 	}
 }
+
+func startTunnels(ctx context.Context, clientId string, env string, mgr manager.Manager,
+	moduleDeploymentController *module_deployment_controller.ModuleDeploymentController) tunnel.Tunnel {
+	// Initialize tunnels based on configuration
+	tunnels := make([]tunnel.Tunnel, 0)
+
+	mqttTunnelEnable := utils.GetEnv("ENABLE_MQTT_TUNNEL", "false")
+	if mqttTunnelEnable == "true" {
+		mqttTl := koupleless_mqtt_tunnel.NewMqttTunnel(env, mgr.GetClient(), moduleDeploymentController)
+		tunnels = append(tunnels, &mqttTl)
+	}
+
+	httpTunnelEnable := utils.GetEnv("ENABLE_HTTP_TUNNEL", "false")
+	if httpTunnelEnable == "true" {
+		httpTunnelListenPort, err := strconv.Atoi(utils.GetEnv("HTTP_TUNNEL_LISTEN_PORT", "7777"))
+
+		if err != nil {
+			log.G(ctx).WithError(err).Error("failed to parse HTTP_TUNNEL_LISTEN_PORT, set default port 7777")
+			httpTunnelListenPort = 7777
+		}
+
+		httpTl := koupleless_http_tunnel.NewHttpTunnel(env, mgr.GetClient(), moduleDeploymentController, httpTunnelListenPort)
+		tunnels = append(tunnels, &httpTl)
+	}
+
+	// Start all tunnels
+	successTunnelCount := 0
+	startFailedCount := 0
+	for _, t := range tunnels {
+		err := t.Start(ctx, clientId, env)
+		if err != nil {
+			log.G(ctx).WithError(err).Error("failed to start tunnel", t.Key())
+			startFailedCount++
+		} else {
+			log.G(ctx).Info("Tunnel started: ", t.Key())
+			successTunnelCount++
+		}
+	}
+
+	if startFailedCount > 0 {
+		panic(errors.New(fmt.Sprintf("failed to start %d tunnels", startFailedCount)))
+	} else if successTunnelCount == 0 {
+		panic(errors.New(fmt.Sprintf("successfully started 0 tunnels")))
+	}
+	// we only using one tunnel for now
+	return tunnels[0]
+}
diff --git a/common/model/consts.go b/common/model/consts.go
index fb71fc7..81bd0e6 100644
--- a/common/model/consts.go
+++ b/common/model/consts.go
@@ -53,19 +53,19 @@ const (
 
 // MQTT topic patterns for base communication
 const (
-	// BaseHeartBeatTopic for heartbeat messages
+	// BaseHeartBeatTopic for heartbeat messages, broadcast mode
 	BaseHeartBeatTopic = "koupleless_%s/+/base/heart"
-	// BaseQueryBaselineTopic for baseline queries
+	// BaseQueryBaselineTopic for baseline queries, broadcast mode
 	BaseQueryBaselineTopic = "koupleless_%s/+/base/queryBaseline"
-	// BaseHealthTopic for health status
+	// BaseHealthTopic for health status, p2p mode
 	BaseHealthTopic = "koupleless_%s/%s/base/health"
-	// BaseSimpleBizTopic for simple business operations
+	// BaseSimpleBizTopic for simple business operations, p2p mode
 	BaseSimpleBizTopic = "koupleless_%s/%s/base/simpleBiz"
-	// BaseAllBizTopic for all business operations
+	// BaseAllBizTopic for all business operations, p2p mode
 	BaseAllBizTopic = "koupleless_%s/%s/base/biz"
-	// BaseBizOperationResponseTopic for business operation responses
+	// BaseBizOperationResponseTopic for business operation responses, p2p mode
 	BaseBizOperationResponseTopic = "koupleless_%s/%s/base/bizOperation"
-	// BaseBaselineResponseTopic for baseline responses
+	// BaseBaselineResponseTopic for baseline responses, p2p mode
 	BaseBaselineResponseTopic = "koupleless_%s/%s/base/baseline"
 )
 
@@ -73,6 +73,6 @@ const (
 const (
 	// LabelKeyOfTechStack specifies the technology stack
 	LabelKeyOfTechStack = "base.koupleless.io/stack"
-	// LabelKeyOfArkletPort specifies the arklet port
-	LabelKeyOfArkletPort = "base.koupleless.io/arklet-port"
+	// LabelKeyOfTunnelPort specifies the tunnel port
+	LabelKeyOfTunnelPort = "base.koupleless.io/tunnel-port"
 )
diff --git a/common/model/model.go b/common/model/model.go
index 5efec98..1d9d66d 100644
--- a/common/model/model.go
+++ b/common/model/model.go
@@ -10,32 +10,27 @@ type ArkMqttMsg[T any] struct {
 	Data             T     `json:"data"`
 }
 
-// Metadata contains basic identifying information
-type Metadata struct {
-	Name    string `json:"name"`    // Name of the resource
-	Version string `json:"version"` // Version identifier
+// BaseMetadata contains basic identifying information
+type BaseMetadata struct {
+	Identity    string `json:"identity"`
+	Version     string `json:"version"`     // Version identifier
+	ClusterName string `json:"clusterName"` // ClusterName of the resource communicate with base
 }
 
-// HeartBeatData is the data of base heart beat.
+// BaseStatus is the data of base heart beat.
 // Contains information about the base node's status and network details
-type HeartBeatData struct {
-	BaseID        string      `json:"baseID"`        // Unique identifier for the base
-	State         string      `json:"state"`         // Current state of the base
-	MasterBizInfo Metadata    `json:"masterBizInfo"` // Master business info metadata
-	NetworkInfo   NetworkInfo `json:"networkInfo"`   // Network configuration details
-}
-
-// NetworkInfo contains network-related configuration
-type NetworkInfo struct {
-	LocalIP       string `json:"localIP"`       // Local IP address
-	LocalHostName string `json:"localHostName"` // Local hostname
-	ArkletPort    int    `json:"arkletPort"`    // Port number for arklet service
+type BaseStatus struct {
+	BaseMetadata  BaseMetadata `json:"baseMetadata"`  // Master business info metadata
+	LocalIP       string       `json:"localIP"`       // Local IP address
+	LocalHostName string       `json:"localHostName"` // Local hostname
+	Port          int          `json:"port"`          // Port number for arklet service
+	State         string       `json:"state"`         // Current state of the base
 }
 
 // BizOperationResponse represents the response from a business operation
 type BizOperationResponse struct {
 	Command    string                  `json:"command"`    // Operation command executed
-	BizName    string                  `json:"bizName"`    // Name of the business
+	BizName    string                  `json:"bizName"`    // ClusterName of the business
 	BizVersion string                  `json:"bizVersion"` // Version of the business
 	Response   ark_service.ArkResponse `json:"response"`   // Response from ark service
 }
@@ -43,9 +38,9 @@ type BizOperationResponse struct {
 // QueryBaselineRequest is the request parameters of query baseline func
 // Used to query baseline configuration with filters
 type QueryBaselineRequest struct {
-	Name         string            `json:"name"`         // Name to filter by
-	Version      string            `json:"version"`      // Version to filter by
-	CustomLabels map[string]string `json:"customLabels"` // Additional label filters
+	Identity    string `json:"identity"`    // Identity base to filter by
+	ClusterName string `json:"clusterName"` // ClusterName to filter by
+	Version     string `json:"version"`     // Version to filter by
 }
 
 // BuildModuleDeploymentControllerConfig contains controller configuration
diff --git a/common/utils/utils.go b/common/utils/utils.go
index 6fef56c..7d3eeb0 100644
--- a/common/utils/utils.go
+++ b/common/utils/utils.go
@@ -19,8 +19,8 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-// GetBaseIDFromTopic extracts the base ID from a topic string by splitting on '/'
-func GetBaseIDFromTopic(topic string) string {
+// GetBaseIdentityFromTopic extracts the base ID from a topic string by splitting on '/'
+func GetBaseIdentityFromTopic(topic string) string {
 	fileds := strings.Split(topic, "/")
 	if len(fileds) < 2 {
 		return ""
@@ -69,32 +69,34 @@ func GetBizIdentity(bizName, bizVersion string) string {
 	return bizName + ":" + bizVersion
 }
 
-// TranslateHeartBeatDataToNodeInfo converts heartbeat data to node info
-func TranslateHeartBeatDataToNodeInfo(data model.HeartBeatData) vkModel.NodeInfo {
-	state := vkModel.NodeStatusDeactivated
-	if data.State == "ACTIVATED" {
-		state = vkModel.NodeStatusActivated
+// ConvertBaseStatusToNodeInfo converts heartbeat data to node info
+func ConvertBaseStatusToNodeInfo(data model.BaseStatus, env string) vkModel.NodeInfo {
+	state := vkModel.NodeStateDeactivated
+	if strings.EqualFold(data.State, "ACTIVATED") {
+		state = vkModel.NodeStateActivated
 	}
 	labels := map[string]string{}
-	if data.NetworkInfo.ArkletPort != 0 {
-		labels[model.LabelKeyOfArkletPort] = strconv.Itoa(data.NetworkInfo.ArkletPort)
+	if data.Port != 0 {
+		labels[model.LabelKeyOfTunnelPort] = strconv.Itoa(data.Port)
 	}
+
 	return vkModel.NodeInfo{
 		Metadata: vkModel.NodeMetadata{
-			Name:    data.MasterBizInfo.Name,
-			Version: data.MasterBizInfo.Version,
-			Status:  state,
+			Name:        utils.FormatNodeName(data.BaseMetadata.Identity, env),
+			ClusterName: data.BaseMetadata.ClusterName,
+			Version:     data.BaseMetadata.Version,
 		},
 		NetworkInfo: vkModel.NetworkInfo{
-			NodeIP:   data.NetworkInfo.LocalIP,
-			HostName: data.NetworkInfo.LocalHostName,
+			NodeIP:   data.LocalIP,
+			HostName: data.LocalHostName,
 		},
 		CustomLabels: labels,
+		State:        state,
 	}
 }
 
-// TranslateHealthDataToNodeStatus converts health data to node status
-func TranslateHealthDataToNodeStatus(data ark.HealthData) vkModel.NodeStatusData {
+// ConvertHealthDataToNodeStatus converts health data to node status
+func ConvertHealthDataToNodeStatus(data ark.HealthData) vkModel.NodeStatusData {
 	resourceMap := make(map[corev1.ResourceName]vkModel.NodeResource)
 	memory := vkModel.NodeResource{}
 	// Set memory capacity if JavaMaxMetaspace is valid (not -1)
@@ -116,14 +118,12 @@ func TranslateHealthDataToNodeStatus(data ark.HealthData) vkModel.NodeStatusData
 	}
 }
 
-// TranslateHeartBeatDataToBaselineQuery converts heartbeat metadata to a baseline query
-func TranslateHeartBeatDataToBaselineQuery(data model.Metadata) model.QueryBaselineRequest {
+// ConvertBaseMetadataToBaselineQuery converts heartbeat metadata to a baseline query
+func ConvertBaseMetadataToBaselineQuery(data model.BaseMetadata) model.QueryBaselineRequest {
 	return model.QueryBaselineRequest{
-		Name:    data.Name,
-		Version: data.Version,
-		CustomLabels: map[string]string{
-			model.LabelKeyOfTechStack: "java",
-		},
+		Identity:    data.Identity,
+		ClusterName: data.ClusterName,
+		Version:     data.Version,
 	}
 }
 
@@ -200,12 +200,11 @@ func GetLatestState(state string, records []ark.ArkBizStateRecord) (time.Time, s
 }
 
 // OnBaseUnreachable handles cleanup when a base becomes unreachable
-func OnBaseUnreachable(ctx context.Context, info vkModel.UnreachableNodeInfo, env string, k8sClient client.Client) {
+func OnBaseUnreachable(ctx context.Context, nodeName string, k8sClient client.Client) {
 	// base not ready, delete from api server
 	node := corev1.Node{}
-	nodeName := utils.FormatNodeName(info.NodeID, env)
 	err := k8sClient.Get(ctx, client.ObjectKey{Name: nodeName}, &node)
-	logger := log.G(ctx).WithField("nodeID", info.NodeID).WithField("func", "OnNodeNotReady")
+	logger := log.G(ctx).WithField("nodeName", nodeName).WithField("func", "OnNodeNotReady")
 	if err == nil {
 		// delete node from api server
 		logger.Info("DeleteBaseNode")
@@ -220,9 +219,9 @@ func OnBaseUnreachable(ctx context.Context, info vkModel.UnreachableNodeInfo, en
 	}
 }
 
-// ExtractNetworkInfoFromNodeInfoData extracts network info from node info
-func ExtractNetworkInfoFromNodeInfoData(initData vkModel.NodeInfo) model.NetworkInfo {
-	portStr := initData.CustomLabels[model.LabelKeyOfArkletPort]
+// ConvertBaseStatusFromNodeInfo extracts network info from node info
+func ConvertBaseStatusFromNodeInfo(initData vkModel.NodeInfo) model.BaseStatus {
+	portStr := initData.CustomLabels[model.LabelKeyOfTunnelPort]
 
 	port, err := strconv.Atoi(portStr)
 	if err != nil {
@@ -230,9 +229,16 @@ func ExtractNetworkInfoFromNodeInfoData(initData vkModel.NodeInfo) model.Network
 		port = 1238
 	}
 
-	return model.NetworkInfo{
+	return model.BaseStatus{
+		BaseMetadata: model.BaseMetadata{
+			Identity:    utils.ExtractNodeIDFromNodeName(initData.Metadata.Name),
+			Version:     initData.Metadata.Version,
+			ClusterName: initData.Metadata.ClusterName,
+		},
+
 		LocalIP:       initData.NetworkInfo.NodeIP,
 		LocalHostName: initData.NetworkInfo.HostName,
-		ArkletPort:    port,
+		Port:          port,
+		State:         string(initData.State),
 	}
 }
diff --git a/common/utils/utils_test.go b/common/utils/utils_test.go
index fe31d38..a1e9492 100644
--- a/common/utils/utils_test.go
+++ b/common/utils/utils_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/koupleless/arkctl/v1/service/ark"
 	"github.com/koupleless/module_controller/common/model"
+	"github.com/koupleless/virtual-kubelet/common/utils"
 	vkModel "github.com/koupleless/virtual-kubelet/model"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
@@ -15,7 +16,7 @@ import (
 	"time"
 )
 
-// Test cases for GetBaseIDFromTopic function
+// Test cases for GetBaseIdentityFromTopic function
 func TestGetBaseIDFromTopic(t *testing.T) {
 	tests := []struct {
 		topic    string
@@ -31,9 +32,9 @@ func TestGetBaseIDFromTopic(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.topic, func(t *testing.T) {
-			actual := GetBaseIDFromTopic(tt.topic)
+			actual := GetBaseIdentityFromTopic(tt.topic)
 			if actual != tt.expected {
-				t.Errorf("GetBaseIDFromTopic(%q) = %q; expected %q", tt.topic, actual, tt.expected)
+				t.Errorf("GetBaseIdentityFromTopic(%q) = %q; expected %q", tt.topic, actual, tt.expected)
 			}
 		})
 	}
@@ -175,7 +176,7 @@ func TestTranslateCoreV1ContainerToBizModel(t *testing.T) {
 		{
 			container: &corev1.Container{
 				Name:  "test-container",
-				Image: "test-image",
+				Image: "test-image.jar",
 				Env: []corev1.EnvVar{
 					{Name: "BIZ_VERSION", Value: "1.0.0"},
 				},
@@ -183,13 +184,13 @@ func TestTranslateCoreV1ContainerToBizModel(t *testing.T) {
 			expected: ark.BizModel{
 				BizName:    "test-container",
 				BizVersion: "1.0.0",
-				BizUrl:     "test-image",
+				BizUrl:     "test-image.jar",
 			},
 		},
 		{
 			container: &corev1.Container{
 				Name:  "another-container",
-				Image: "another-image",
+				Image: "another-image.jar",
 				Env: []corev1.EnvVar{
 					{Name: "OTHER_ENV", Value: "foo"},
 					{Name: "BIZ_VERSION", Value: "2.0.0"},
@@ -198,13 +199,13 @@ func TestTranslateCoreV1ContainerToBizModel(t *testing.T) {
 			expected: ark.BizModel{
 				BizName:    "another-container",
 				BizVersion: "2.0.0",
-				BizUrl:     "another-image",
+				BizUrl:     "another-image.jar",
 			},
 		},
 		{
 			container: &corev1.Container{
 				Name:  "empty-version-container",
-				Image: "empty-version-image",
+				Image: "empty-version-image.jar",
 				Env: []corev1.EnvVar{
 					{Name: "OTHER_ENV", Value: "bar"},
 				},
@@ -212,19 +213,19 @@ func TestTranslateCoreV1ContainerToBizModel(t *testing.T) {
 			expected: ark.BizModel{
 				BizName:    "empty-version-container",
 				BizVersion: "",
-				BizUrl:     "empty-version-image",
+				BizUrl:     "empty-version-image.jar",
 			},
 		},
 		{
 			container: &corev1.Container{
 				Name:  "no-env-container",
-				Image: "no-env-image",
+				Image: "no-env-image.jar",
 				Env:   []corev1.EnvVar{},
 			},
 			expected: ark.BizModel{
 				BizName:    "no-env-container",
 				BizVersion: "",
-				BizUrl:     "no-env-image",
+				BizUrl:     "no-env-image.jar",
 			},
 		},
 	}
@@ -263,72 +264,73 @@ func TestGetBizIdentity(t *testing.T) {
 	}
 }
 
-// Test cases for TranslateHeartBeatDataToNodeInfo function
+// Test cases for ConvertBaseStatusToNodeInfo function
 func TestTranslateHeartBeatDataToNodeInfo(t *testing.T) {
+	env := "test"
 	tests := []struct {
-		data     model.HeartBeatData
+		data     model.BaseStatus
 		expected vkModel.NodeInfo
 	}{
 		{
-			data: model.HeartBeatData{
-				State: "ACTIVATED",
-				MasterBizInfo: model.Metadata{
-					Name:    "biz1",
-					Version: "1.0.0",
-				},
-				NetworkInfo: model.NetworkInfo{
-					LocalIP:       "192.168.1.1",
-					LocalHostName: "host1",
-					ArkletPort:    1238,
+			data: model.BaseStatus{
+				BaseMetadata: model.BaseMetadata{
+					Identity:    "base1",
+					ClusterName: "base",
+					Version:     "1.0.0",
 				},
+				LocalIP:       "192.168.1.1",
+				LocalHostName: "host1",
+				Port:          1238,
+				State:         "ACTIVATED",
 			},
 			expected: vkModel.NodeInfo{
 				Metadata: vkModel.NodeMetadata{
-					Name:    "biz1",
-					Version: "1.0.0",
-					Status:  vkModel.NodeStatusActivated,
+					Name:        utils.FormatNodeName("base1", env),
+					Version:     "1.0.0",
+					ClusterName: "base",
 				},
 				NetworkInfo: vkModel.NetworkInfo{
 					NodeIP:   "192.168.1.1",
 					HostName: "host1",
 				},
 				CustomLabels: map[string]string{
-					model.LabelKeyOfArkletPort: "1238",
+					model.LabelKeyOfTunnelPort: "1238",
 				},
+				State: vkModel.NodeStateActivated,
 			},
 		},
 		{
-			data: model.HeartBeatData{
-				State: "DEACTIVATED",
-				MasterBizInfo: model.Metadata{
-					Name:    "biz2",
-					Version: "2.0.0",
-				},
-				NetworkInfo: model.NetworkInfo{
-					LocalIP:       "192.168.1.2",
-					LocalHostName: "host2",
+			data: model.BaseStatus{
+				BaseMetadata: model.BaseMetadata{
+					Identity:    "base2",
+					ClusterName: "base",
+					Version:     "2.0.0",
 				},
+				LocalIP:       "192.168.1.2",
+				LocalHostName: "host2",
+				State:         "DEACTIVATED",
 			},
 			expected: vkModel.NodeInfo{
 				Metadata: vkModel.NodeMetadata{
-					Name:    "biz2",
-					Version: "2.0.0",
-					Status:  vkModel.NodeStatusDeactivated,
+					Name:        utils.FormatNodeName("base2", env),
+					Version:     "2.0.0",
+					ClusterName: "base",
 				},
 				NetworkInfo: vkModel.NetworkInfo{
 					NodeIP:   "192.168.1.2",
 					HostName: "host2",
 				},
 				CustomLabels: map[string]string{},
+				State:        vkModel.NodeStateDeactivated,
 			},
 		},
 	}
 
 	for _, tt := range tests {
-		t.Run(tt.data.MasterBizInfo.Name, func(t *testing.T) {
-			actual := TranslateHeartBeatDataToNodeInfo(tt.data)
+		t.Run(tt.data.BaseMetadata.Identity, func(t *testing.T) {
+			actual := ConvertBaseStatusToNodeInfo(tt.data, env)
 			if !reflect.DeepEqual(actual, tt.expected) {
-				t.Errorf("TranslateHeartBeatDataToNodeInfo() = %+v; expected %+v", actual, tt.expected)
+				t.Errorf("ConvertBaseStatusToNodeInfo() = %+v; expected %+v", actual, tt.expected)
 			}
 		})
 	}
@@ -534,40 +536,39 @@ func TestTranslateHealthDataToNodeStatus(t *testing.T) {
 	}
 
 	for _, tc := range testCases {
-		result := TranslateHealthDataToNodeStatus(tc.input)
+		result := ConvertHealthDataToNodeStatus(tc.input)
 		if result.Resources[corev1.ResourceMemory].Capacity != tc.expected.Resources[corev1.ResourceMemory].Capacity {
-			t.Errorf("TranslateHealthDataToNodeStatus(%v) = %v; want %v", tc.input, result, tc.expected)
+			t.Errorf("ConvertHealthDataToNodeStatus(%v) = %v; want %v", tc.input, result, tc.expected)
 		}
 		if result.Resources[corev1.ResourceMemory].Allocatable != tc.expected.Resources[corev1.ResourceMemory].Allocatable {
-			t.Errorf("TranslateHealthDataToNodeStatus(%v) = %v; want %v", tc.input, result, tc.expected)
+			t.Errorf("ConvertHealthDataToNodeStatus(%v) = %v; want %v", tc.input, result, tc.expected)
 		}
 	}
 }
 
 func TestTranslateHeartBeatDataToBaselineQuery(t *testing.T) {
 	testCases := []struct {
-		input    model.Metadata
+		input    model.BaseMetadata
 		expected model.QueryBaselineRequest
 	}{
 		{
-			input: model.Metadata{
-				Name:    "test",
-				Version: "1.0.0",
+			input: model.BaseMetadata{
+				Identity:    "base1",
+				ClusterName: "test",
+				Version:     "1.0.0",
 			},
 			expected: model.QueryBaselineRequest{
-				Name:    "test",
-				Version: "1.0.0",
-				CustomLabels: map[string]string{
-					model.LabelKeyOfTechStack: "java",
-				},
+				Identity:    "base1",
+				ClusterName: "test",
+				Version:     "1.0.0",
 			},
 		},
 	}
 
 	for _, tc := range testCases {
-		result := TranslateHeartBeatDataToBaselineQuery(tc.input)
-		if result.Name != tc.expected.Name || result.Version != tc.expected.Version || len(result.CustomLabels) != len(tc.expected.CustomLabels) {
-			t.Errorf("TranslateHeartBeatDataToBaselineQuery(%s) = %v; want %v", tc.input, result, tc.expected)
+		result := ConvertBaseMetadataToBaselineQuery(tc.input)
+		if result.ClusterName != tc.expected.ClusterName || result.Version != tc.expected.Version {
+			t.Errorf("ConvertBaseMetadataToBaselineQuery(%s) = %v; want %v", tc.input, result, tc.expected)
 		}
 	}
 }
@@ -596,7 +597,7 @@ func TestTranslateSimpleBizDataToArkBizInfos(t *testing.T) {
 
 	for _, tc := range testCases {
 		result := TranslateSimpleBizDataToBizInfos(tc.input)
-		assert.Equal(t, len(result), len(tc.expected), fmt.Errorf("TranslateHeartBeatDataToBaselineQuery(%s) = %v; want %v", tc.input, result, tc.expected))
+		assert.Equal(t, len(result), len(tc.expected), fmt.Errorf("ConvertBaseMetadataToBaselineQuery(%s) = %v; want %v", tc.input, result, tc.expected))
 	}
 }
 
@@ -622,14 +623,14 @@ func TestGetLatestState_ChangeTimeLenLt3(t *testing.T) {
 }
 
 func TestExtractNetworkInfoFromNodeInfoData(t *testing.T) {
-	data := ExtractNetworkInfoFromNodeInfoData(vkModel.NodeInfo{
+	data := ConvertBaseStatusFromNodeInfo(vkModel.NodeInfo{
 		CustomLabels: map[string]string{
-			model.LabelKeyOfArkletPort: ";",
+			model.LabelKeyOfTunnelPort: ";",
 		},
 	})
-	assert.Equal(t, data.ArkletPort, 1238)
+	assert.Equal(t, data.Port, 1238)
 }
 
 func TestOnBaseUnreachable(t *testing.T) {
-	OnBaseUnreachable(context.Background(), vkModel.UnreachableNodeInfo{}, "test", fake.NewFakeClient())
+	OnBaseUnreachable(context.Background(), "test-node-name", fake.NewFakeClient())
 }
diff --git a/controller/module_deployment_controller/module_deployment_controller.go b/controller/module_deployment_controller/module_deployment_controller.go
index b52f9f7..e7f0054 100644
--- a/controller/module_deployment_controller/module_deployment_controller.go
+++ b/controller/module_deployment_controller/module_deployment_controller.go
@@ -6,15 +6,14 @@ import (
 	"github.com/koupleless/virtual-kubelet/common/tracker"
 	"github.com/koupleless/virtual-kubelet/common/utils"
 	"github.com/sirupsen/logrus"
+	errors2 "k8s.io/apimachinery/pkg/api/errors"
 	"sort"
 
 	"github.com/koupleless/module_controller/common/model"
-	"github.com/koupleless/module_controller/module_tunnels"
 	"github.com/koupleless/virtual-kubelet/common/log"
 	vkModel "github.com/koupleless/virtual-kubelet/model"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/selection"
 	"k8s.io/client-go/util/workqueue"
@@ -33,8 +32,6 @@ import (
 type ModuleDeploymentController struct {
 	env string // The environment in which the controller operates.
 
-	tunnels []module_tunnels.ModuleTunnel // A list of tunnels for communication with modules.
-
 	client client.Client // The client for interacting with the Kubernetes API.
 
 	cache cache.Cache // The cache for storing and retrieving Kubernetes objects.
@@ -51,10 +48,9 @@ func (mdc *ModuleDeploymentController) Reconcile(ctx context.Context, request re
 }
 
 // NewModuleDeploymentController creates a new instance of the controller.
-func NewModuleDeploymentController(env string, tunnels []module_tunnels.ModuleTunnel) (*ModuleDeploymentController, error) {
+func NewModuleDeploymentController(env string) (*ModuleDeploymentController, error) {
 	return &ModuleDeploymentController{
 		env:            env,
-		tunnels:        tunnels,
 		runtimeStorage: NewRuntimeInfoStore(),
 		updateToken:    make(chan interface{}, 1),
 	}, nil
@@ -63,11 +59,6 @@ func NewModuleDeploymentController(env string, tunnels []module_tunnels.ModuleTu
 // SetupWithManager sets up the controller with a manager.
 func (mdc *ModuleDeploymentController) SetupWithManager(ctx context.Context, mgr manager.Manager) (err error) {
 	mdc.updateToken <- nil
-
-	for _, tunnel := range mdc.tunnels {
-		tunnel.RegisterQuery(mdc.queryContainerBaseline)
-	}
-
 	mdc.client = mgr.GetClient()
 	mdc.cache = mgr.GetCache()
 
@@ -126,18 +117,18 @@ func (mdc *ModuleDeploymentController) SetupWithManager(ctx context.Context, mgr
 			mdc.runtimeStorage.PutDeployment(deployment)
 		}
 
-		mdc.updateDeploymentReplicas(depList.Items)
+		mdc.updateDeploymentReplicas(ctx, depList.Items)
 	}()
 
 	var vnodeEventHandler = handler.TypedFuncs[*corev1.Node, reconcile.Request]{
 		CreateFunc: func(ctx context.Context, e event.TypedCreateEvent[*corev1.Node], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-			mdc.vnodeCreateHandler(e.Object)
+			mdc.vnodeCreateHandler(ctx, e.Object)
 		},
 		UpdateFunc: func(ctx context.Context, e event.TypedUpdateEvent[*corev1.Node], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-			mdc.vnodeUpdateHandler(e.ObjectOld, e.ObjectNew)
+			mdc.vnodeUpdateHandler(ctx, e.ObjectOld, e.ObjectNew)
 		},
 		DeleteFunc: func(ctx context.Context, e event.TypedDeleteEvent[*corev1.Node], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-			mdc.vnodeDeleteHandler(e.Object)
+			mdc.vnodeDeleteHandler(ctx, e.Object)
 		},
 		GenericFunc: func(ctx context.Context, e event.TypedGenericEvent[*corev1.Node], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
 			log.G(ctx).WithField("node_name", e.Object.Name).Warn("Generic func call")
@@ -151,13 +142,12 @@ func (mdc *ModuleDeploymentController) SetupWithManager(ctx context.Context, mgr
 
 	var deploymentEventHandler = handler.TypedFuncs[*appsv1.Deployment, reconcile.Request]{
 		CreateFunc: func(ctx context.Context, e event.TypedCreateEvent[*appsv1.Deployment], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-			mdc.deploymentAddHandler(e.Object)
+			mdc.deploymentAddHandler(ctx, e.Object)
 		},
 		UpdateFunc: func(ctx context.Context, e event.TypedUpdateEvent[*appsv1.Deployment], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-			mdc.deploymentUpdateHandler(e.ObjectOld, e.ObjectNew)
+			mdc.deploymentUpdateHandler(ctx, e.ObjectOld, e.ObjectNew)
 		},
 		DeleteFunc: func(ctx context.Context, e event.TypedDeleteEvent[*appsv1.Deployment], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
-			mdc.deploymentDeleteHandler(e.Object)
 		},
 		GenericFunc: func(ctx context.Context, e event.TypedGenericEvent[*appsv1.Deployment], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
 			log.G(ctx).WithField("deployment_name", e.Object.Name).Warn("Generic func call")
@@ -174,30 +164,33 @@ func (mdc *ModuleDeploymentController) SetupWithManager(ctx context.Context, mgr
 	return nil
 }
 
-// queryContainerBaseline queries the baseline for a given container.
-func (mdc *ModuleDeploymentController) queryContainerBaseline(req model.QueryBaselineRequest) []corev1.Container {
+// QueryContainerBaseline queries the baseline for a given container.
+func (mdc *ModuleDeploymentController) QueryContainerBaseline(req model.QueryBaselineRequest) []corev1.Container {
 	labelMap := map[string]string{
-		vkModel.LabelKeyOfEnv:          mdc.env,
-		vkModel.LabelKeyOfVNodeName:    req.Name,
-		vkModel.LabelKeyOfVNodeVersion: req.Version,
-	}
-	for key, value := range req.CustomLabels {
-		labelMap[key] = value
+		// TODO: should add those label to deployments by module controller
+		vkModel.LabelKeyOfEnv: mdc.env,
 	}
-	relatedDeploymentsByNode := mdc.runtimeStorage.GetRelatedDeploymentsByNode(&corev1.Node{
-		ObjectMeta: metav1.ObjectMeta{
-			Labels: labelMap,
-		},
+	allDeploymentList := appsv1.DeploymentList{}
+	err := mdc.cache.List(context.Background(), &allDeploymentList, &client.ListOptions{
+		LabelSelector: labels.SelectorFromSet(labelMap),
 	})
+	if err != nil {
+		log.G(context.Background()).WithError(err).Error("failed to list deployments")
+		return []corev1.Container{}
+	}
+
 	// get relate containers of related deployments
-	sort.Slice(relatedDeploymentsByNode, func(i, j int) bool {
-		return relatedDeploymentsByNode[i].CreationTimestamp.UnixMilli() < relatedDeploymentsByNode[j].CreationTimestamp.UnixMilli()
+	sort.Slice(allDeploymentList.Items, func(i, j int) bool {
+		return allDeploymentList.Items[i].CreationTimestamp.UnixMilli() < allDeploymentList.Items[j].CreationTimestamp.UnixMilli()
 	})
 	// record last version of biz model with same name
 	containers := make([]corev1.Container, 0)
-	for _, deployment := range relatedDeploymentsByNode {
-		for _, container := range deployment.Spec.Template.Spec.Containers {
-			containers = append(containers, container)
+	for _, deployment := range allDeploymentList.Items {
+		clusterName := getClusterNameFromDeployment(&deployment)
+		if clusterName != "" && clusterName != req.ClusterName {
+			for _, container := range deployment.Spec.Template.Spec.Containers {
+				containers = append(containers, container)
+			}
 		}
 	}
 	log.G(context.Background()).Infof("query base line got: %", containers)
@@ -205,64 +198,75 @@ func (mdc *ModuleDeploymentController) queryContainerBaseline(req model.QueryBas
 }
 
 // vnodeCreateHandler handles the creation of a new vnode.
-func (mdc *ModuleDeploymentController) vnodeCreateHandler(vnode *corev1.Node) {
-	changed := mdc.runtimeStorage.PutNode(vnode.DeepCopy())
-	if changed {
-		relatedDeploymentsByNode := mdc.runtimeStorage.GetRelatedDeploymentsByNode(vnode)
-		go mdc.updateDeploymentReplicas(relatedDeploymentsByNode)
-	}
+func (mdc *ModuleDeploymentController) vnodeCreateHandler(ctx context.Context, vnode *corev1.Node) {
+	relatedDeploymentsByNode := mdc.GetRelatedDeploymentsByNode(ctx, vnode)
+	mdc.updateDeploymentReplicas(ctx, relatedDeploymentsByNode)
 }
 
 // vnodeUpdateHandler handles the update of an existing vnode.
-func (mdc *ModuleDeploymentController) vnodeUpdateHandler(_, vnode *corev1.Node) {
-	changed := mdc.runtimeStorage.PutNode(vnode.DeepCopy())
-	if changed {
-		relatedDeploymentsByNode := mdc.runtimeStorage.GetRelatedDeploymentsByNode(vnode)
-		go mdc.updateDeploymentReplicas(relatedDeploymentsByNode)
-	}
+func (mdc *ModuleDeploymentController) vnodeUpdateHandler(ctx context.Context, _, vnode *corev1.Node) {
+	relatedDeploymentsByNode := mdc.GetRelatedDeploymentsByNode(ctx, vnode)
+	mdc.updateDeploymentReplicas(ctx, relatedDeploymentsByNode)
 }
 
 // vnodeDeleteHandler handles the deletion of a vnode.
-func (mdc *ModuleDeploymentController) vnodeDeleteHandler(vnode *corev1.Node) {
+func (mdc *ModuleDeploymentController) vnodeDeleteHandler(ctx context.Context, vnode *corev1.Node) {
 	vnodeCopy := vnode.DeepCopy()
-	mdc.runtimeStorage.DeleteNode(vnodeCopy)
-	relatedDeploymentsByNode := mdc.runtimeStorage.GetRelatedDeploymentsByNode(vnodeCopy)
-	go mdc.updateDeploymentReplicas(relatedDeploymentsByNode)
+	relatedDeploymentsByNode := mdc.GetRelatedDeploymentsByNode(ctx, vnodeCopy)
+	mdc.updateDeploymentReplicas(ctx, relatedDeploymentsByNode)
 }
 
-// deploymentAddHandler handles the addition of a new deployment.
-func (mdc *ModuleDeploymentController) deploymentAddHandler(dep *appsv1.Deployment) {
-	if dep == nil {
-		return
+func (mdc *ModuleDeploymentController) GetRelatedDeploymentsByNode(ctx context.Context, node *corev1.Node) []appsv1.Deployment {
+	matchedDeployments := make([]appsv1.Deployment, 0)
+
+	clusterName := getClusterNameFromNode(node)
+	if clusterName == "" {
+		logrus.Warnf("failed to get cluster name of node %s", node.Name)
+		return matchedDeployments
+	}
+
+	deploymentList := appsv1.DeploymentList{}
+	err := mdc.cache.List(ctx, &deploymentList, &client.ListOptions{
+		LabelSelector: labels.SelectorFromSet(map[string]string{
+			model.LabelKeyOfVPodDeploymentStrategy: string(model.VPodDeploymentStrategyPeer),
+		}),
+	})
+
+	if err != nil {
+		logrus.WithError(err).Error("failed to list deployments")
+		return matchedDeployments
 	}
 
-	deploymentCopy := dep.DeepCopy()
-	mdc.runtimeStorage.PutDeployment(*deploymentCopy)
+	for _, deployment := range deploymentList.Items {
+		clusterNameFromDeployment := getClusterNameFromDeployment(&deployment)
+		if clusterName == clusterNameFromDeployment && clusterNameFromDeployment != "" {
+			matchedDeployments = append(matchedDeployments, deployment)
+		}
+	}
 
-	go mdc.updateDeploymentReplicas([]appsv1.Deployment{*deploymentCopy})
+	return matchedDeployments
 }
 
-// deploymentUpdateHandler handles the update of an existing deployment.
-func (mdc *ModuleDeploymentController) deploymentUpdateHandler(_, newDep *appsv1.Deployment) {
-	if newDep == nil {
+// deploymentAddHandler handles the addition of a new deployment.
+func (mdc *ModuleDeploymentController) deploymentAddHandler(ctx context.Context, dep *appsv1.Deployment) {
+	if dep == nil {
 		return
 	}
-	deploymentCopy := newDep.DeepCopy()
-	mdc.runtimeStorage.PutDeployment(*deploymentCopy)
 
-	go mdc.updateDeploymentReplicas([]appsv1.Deployment{*deploymentCopy})
+	mdc.updateDeploymentReplicas(ctx, []appsv1.Deployment{*dep})
 }
 
-// deploymentDeleteHandler handles the deletion of a deployment.
-func (mdc *ModuleDeploymentController) deploymentDeleteHandler(dep *appsv1.Deployment) {
-	if dep == nil {
+// deploymentUpdateHandler handles the update of an existing deployment.
+func (mdc *ModuleDeploymentController) deploymentUpdateHandler(ctx context.Context, _, newDep *appsv1.Deployment) {
+	if newDep == nil {
 		return
 	}
-	mdc.runtimeStorage.DeleteDeployment(*dep.DeepCopy())
+
+	mdc.updateDeploymentReplicas(ctx, []appsv1.Deployment{*newDep})
 }
 
 // updateDeploymentReplicas updates the replicas of deployments based on node count.
-func (mdc *ModuleDeploymentController) updateDeploymentReplicas(deployments []appsv1.Deployment) {
+func (mdc *ModuleDeploymentController) updateDeploymentReplicas(ctx context.Context, deployments []appsv1.Deployment) {
 
 	// TODO Implement this function.
 	<-mdc.updateToken
@@ -279,10 +283,22 @@ func (mdc *ModuleDeploymentController) updateDeploymentReplicas(deployments []ap
 		if deployment.Labels[model.LabelKeyOfVPodDeploymentStrategy] != string(model.VPodDeploymentStrategyPeer) || deployment.Labels[model.LabelKeyOfSkipReplicasControl] == "true" {
 			continue
 		}
-		newReplicas := mdc.runtimeStorage.GetMatchedNodeNum(deployment)
-		if int32(newReplicas) != *deployment.Spec.Replicas {
+
+		clusterName := getClusterNameFromDeployment(&deployment)
+		if clusterName == "" {
+			logrus.Warnf("failed to get cluster name of deployment %s, skip to update replicas", deployment.Name)
+			continue
+		}
+
+		sameClusterNodeCount, err := mdc.getReadyNodeCount(ctx, clusterName)
+		if err != nil {
+			logrus.WithError(err).Errorf("failed to get nodes of cluster %s, skip to update relicas", clusterName)
+			continue
+		}
+
+		if int32(sameClusterNodeCount) != *deployment.Spec.Replicas {
 			err := tracker.G().FuncTrack(deployment.Labels[vkModel.LabelKeyOfTraceID], vkModel.TrackSceneVPodDeploy, model.TrackEventVPodPeerDeploymentReplicaModify, deployment.Labels, func() (error, vkModel.ErrorCode) {
-				return mdc.updateDeploymentReplicasOfKubernetes(newReplicas, deployment)
+				return mdc.updateDeploymentReplicasOfKubernetes(ctx, sameClusterNodeCount, deployment)
 			})
 			if err != nil {
 				logrus.WithError(err).Errorf("failed to update deployment replicas of %s", deployment.Name)
@@ -291,11 +307,77 @@ func (mdc *ModuleDeploymentController) updateDeploymentReplicas(deployments []ap
 	}
 }
 
+func getClusterNameFromDeployment(deployment *appsv1.Deployment) string {
+	if clusterName, has := deployment.Labels[vkModel.LabelKeyOfVNodeClusterName]; has {
+		return clusterName
+	}
+
+	if deployment.Spec.Template.Spec.NodeSelector != nil {
+		if clusterName, has := deployment.Spec.Template.Spec.NodeSelector[vkModel.LabelKeyOfVNodeClusterName]; has {
+			return clusterName
+		}
+	}
+
+	if deployment.Spec.Template.Spec.Affinity != nil &&
+		deployment.Spec.Template.Spec.Affinity.NodeAffinity != nil &&
+		deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil &&
+		deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms != nil {
+		for _, term := range deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
+			for _, expr := range term.MatchExpressions {
+				if expr.Key == vkModel.LabelKeyOfVNodeClusterName {
+					return expr.Values[0]
+				}
+			}
+		}
+	}
+
+	return ""
+}
+
+func getClusterNameFromNode(node *corev1.Node) string {
+	if clusterName, has := node.Labels[vkModel.LabelKeyOfVNodeClusterName]; has {
+		return clusterName
+	}
+
+	return ""
+}
+
+func (mdc *ModuleDeploymentController) getReadyNodeCount(ctx context.Context, clusterName string) (int, error) {
+	nodeList := &corev1.NodeList{}
+	err := mdc.cache.List(ctx, nodeList, &client.ListOptions{
+		LabelSelector: labels.SelectorFromSet(map[string]string{vkModel.LabelKeyOfVNodeClusterName: clusterName}),
+	})
+
+	if err != nil {
+		logrus.WithError(err).Errorf("failed to list nodes of cluster %s", clusterName)
+		return 0, err
+	}
+
+	readyNodeCnt := 0
+	for _, node := range nodeList.Items {
+		if node.Status.Phase == corev1.NodeRunning {
+			for _, cond := range node.Status.Conditions {
+				if cond.Type == corev1.NodeReady {
+					if cond.Status == corev1.ConditionTrue {
+						readyNodeCnt++
+						break
+					}
+				}
+			}
+		}
+	}
+
+	return readyNodeCnt, nil
+}
+
 // updateDeploymentReplicasOfKubernetes updates the replicas of a deployment in Kubernetes.
-func (mdc *ModuleDeploymentController) updateDeploymentReplicasOfKubernetes(replicas int, deployment appsv1.Deployment) (error, vkModel.ErrorCode) {
+func (mdc *ModuleDeploymentController) updateDeploymentReplicasOfKubernetes(ctx context.Context, replicas int, deployment appsv1.Deployment) (error, vkModel.ErrorCode) {
+	old := deployment.DeepCopy()
+	patch := client.MergeFrom(old)
+
 	deployment.Spec.Replicas = ptr.To[int32](int32(replicas))
-	err := mdc.client.Update(context.TODO(), &deployment)
-	if err != nil {
+	err := mdc.client.Patch(ctx, &deployment, patch)
+	if err != nil && !errors2.IsNotFound(err) {
 		return err, model.CodeKubernetesOperationFailed
 	}
 	return nil, vkModel.CodeSuccess
diff --git a/controller/module_deployment_controller/module_deployment_controller_test.go b/controller/module_deployment_controller/module_deployment_controller_test.go
index 23658cc..6c7fb2f 100644
--- a/controller/module_deployment_controller/module_deployment_controller_test.go
+++ b/controller/module_deployment_controller/module_deployment_controller_test.go
@@ -1,6 +1,7 @@
 package module_deployment_controller
 
 import (
+	"context"
 	"github.com/stretchr/testify/assert"
 	"testing"
 )
@@ -11,15 +12,12 @@ func TestDeploymentHandler(t *testing.T) {
 		updateToken:    make(chan interface{}),
 	}
 
-	c.deploymentAddHandler(nil)
+	ctx := context.Background()
+	c.deploymentAddHandler(ctx, nil)
 
 	assert.Equal(t, 0, len(c.runtimeStorage.peerDeploymentMap))
 
-	c.deploymentUpdateHandler(nil, nil)
-
-	assert.Equal(t, 0, len(c.runtimeStorage.peerDeploymentMap))
-
-	c.deploymentDeleteHandler(nil)
+	c.deploymentUpdateHandler(ctx, nil, nil)
 
 	assert.Equal(t, 0, len(c.runtimeStorage.peerDeploymentMap))
 }
diff --git a/go.mod b/go.mod
index 3c5060d..e7117a1 100644
--- a/go.mod
+++ b/go.mod
@@ -6,10 +6,11 @@ toolchain go1.22.4
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.5.0
+	github.com/go-logr/logr v1.4.2
 	github.com/go-resty/resty/v2 v2.11.0
 	github.com/google/uuid v1.6.0
 	github.com/koupleless/arkctl v0.2.2
-	github.com/koupleless/virtual-kubelet v0.3.4
+	github.com/koupleless/virtual-kubelet v0.3.5-0.20241125132309-00fba45cbde1
 	github.com/onsi/ginkgo/v2 v2.19.0
 	github.com/onsi/gomega v1.33.1
 	github.com/sirupsen/logrus v1.9.3
@@ -31,7 +32,6 @@ require (
 	github.com/evanphx/json-patch/v5 v5.9.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
-	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-logr/zapr v1.3.0 // indirect
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
diff --git a/go.sum b/go.sum
index bbf9e28..de8ba2c 100644
--- a/go.sum
+++ b/go.sum
@@ -94,8 +94,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/koupleless/arkctl v0.2.2 h1:mSx6YMpWAz3+1HEX9ttSkblqgQU1cMSCV+Brsa5K3u8=
 github.com/koupleless/arkctl v0.2.2/go.mod h1:nbnAiPEv7x/ZDQ+QsjFWkqwxMDofGmqnFPHa3XpXHyE=
-github.com/koupleless/virtual-kubelet v0.3.4 h1:GkhiWC+wtKGUey5ku/wDBKY0ZzXrlOEI3gfEROhbQ4A=
-github.com/koupleless/virtual-kubelet v0.3.4/go.mod h1:W64xRKk/hDpGW6WaJdxtY9KQHg0YKHeilBRm0ARxni4=
+github.com/koupleless/virtual-kubelet v0.3.5-0.20241125132309-00fba45cbde1 h1:9mg6otRLrFkGDI4LTlpBaRqQhKRy91tXA1puoEM6nT8=
+github.com/koupleless/virtual-kubelet v0.3.5-0.20241125132309-00fba45cbde1/go.mod h1:q9EkakXgR7FU0BNGbHbOLpwxM7ufvpROCDHwWRJolQU=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
diff --git a/module_tunnels/koupleless_http_tunnel/ark_service/ark_service.go b/module_tunnels/koupleless_http_tunnel/ark_service/ark_service.go
index 89a6fcd..a4706d6 100644
--- a/module_tunnels/koupleless_http_tunnel/ark_service/ark_service.go
+++ b/module_tunnels/koupleless_http_tunnel/ark_service/ark_service.go
@@ -71,12 +71,12 @@ func (h *Service) UninstallBiz(ctx context.Context, req UninstallBizRequest, bas
 	return response, nil
 }
 
-func (h *Service) QueryAllBiz(ctx context.Context, baseIP string, arkletPort int) (response QueryAllBizResponse, err error) {
+func (h *Service) QueryAllBiz(ctx context.Context, baseIP string, port int) (response QueryAllBizResponse, err error) {
 
 	resp, err := h.client.R().
 		SetContext(ctx).
 		SetBody(`{}`).
-		Post(fmt.Sprintf("http://%s:%d/queryAllBiz", baseIP, arkletPort))
+		Post(fmt.Sprintf("http://%s:%d/queryAllBiz", baseIP, port))
 
 	if err != nil {
 		log.G(ctx).WithError(err).Errorf("queryAllBiz request failed")
diff --git a/module_tunnels/koupleless_http_tunnel/ark_service/model.go b/module_tunnels/koupleless_http_tunnel/ark_service/model.go
index 70fea04..bceb985 100644
--- a/module_tunnels/koupleless_http_tunnel/ark_service/model.go
+++ b/module_tunnels/koupleless_http_tunnel/ark_service/model.go
@@ -26,18 +26,16 @@ type ArkResponse struct {
 	// ErrorStackTrace is the error stack trace in case of an error
 	ErrorStackTrace string `json:"errorStackTrace"`
 
-	// BaseID is a unique identifier for the base service
-	BaseID string `json:"baseID"`
+	// BaseIdentity is a unique identifier for the base service
+	BaseIdentity string `json:"baseIdentity"`
 }
 
 // QueryAllBizResponse represents the response for querying all business services
 type QueryAllBizResponse struct {
 	ark.GenericArkResponseBase[[]ark.ArkBizInfo]
-	BaseID string `json:"baseID"`
 }
 
 // HealthResponse represents the response for health checks
 type HealthResponse struct {
 	ark.GenericArkResponseBase[ark.HealthInfo]
-	BaseID string `json:"baseID"`
 }
diff --git a/module_tunnels/koupleless_http_tunnel/http_tunnel.go b/module_tunnels/koupleless_http_tunnel/http_tunnel.go
index 7fa54a4..a5ba1ad 100644
--- a/module_tunnels/koupleless_http_tunnel/http_tunnel.go
+++ b/module_tunnels/koupleless_http_tunnel/http_tunnel.go
@@ -4,6 +4,8 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"github.com/koupleless/module_controller/controller/module_deployment_controller"
+	utils2 "github.com/koupleless/virtual-kubelet/common/utils"
 	"net/http"
 	"sync"
 	"time"
@@ -13,43 +15,50 @@ import (
 	"github.com/koupleless/arkctl/v1/service/ark"
 	"github.com/koupleless/module_controller/common/model"
 	"github.com/koupleless/module_controller/common/utils"
-	"github.com/koupleless/module_controller/module_tunnels"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_http_tunnel/ark_service"
 	"github.com/koupleless/virtual-kubelet/common/log"
 	vkModel "github.com/koupleless/virtual-kubelet/model"
 	"github.com/koupleless/virtual-kubelet/tunnel"
 	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
-	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-var _ module_tunnels.ModuleTunnel = &HttpTunnel{}
+var _ tunnel.Tunnel = &HttpTunnel{}
 
 type HttpTunnel struct {
 	sync.Mutex
 
 	arkService *ark_service.Service
 
-	cache.Cache
-	client.Client
-	env  string
-	Port int
+	kubeClient client.Client
+	env        string
+	port       int
 
 	ready bool
 
-	onBaseDiscovered         tunnel.OnNodeDiscovered
-	onHealthDataArrived      tunnel.OnNodeStatusDataArrived
+	onBaseDiscovered         tunnel.OnBaseDiscovered
+	onHealthDataArrived      tunnel.OnBaseStatusArrived
 	onQueryAllBizDataArrived tunnel.OnAllBizStatusArrived
 	onOneBizDataArrived      tunnel.OnSingleBizStatusArrived
-	queryBaseline            module_tunnels.QueryBaseline
 
 	onlineNode map[string]bool
 
-	nodeNetworkInfoOfNodeID map[string]model.NetworkInfo
+	nodeIdToBaseStatusMap map[string]model.BaseStatus
 
 	queryAllBizLock         sync.Mutex
 	queryAllBizDataOutdated bool
+
+	moduleDeploymentController *module_deployment_controller.ModuleDeploymentController
+}
+
+func NewHttpTunnel(env string, kubeClient client.Client, moduleDeploymentController *module_deployment_controller.ModuleDeploymentController, port int) HttpTunnel {
+	return HttpTunnel{
+		env:                        env,
+		kubeClient:                 kubeClient,
+		moduleDeploymentController: moduleDeploymentController,
+		port:                       port,
+	}
 }
 
 // Ready returns the current status of the tunnel
@@ -62,28 +71,30 @@ func (h *HttpTunnel) GetBizUniqueKey(container *corev1.Container) string {
 	return utils.GetBizIdentity(container.Name, utils.GetBizVersionFromContainer(container))
 }
 
-// OnNodeStart is called when a new node starts
-func (h *HttpTunnel) OnNodeStart(ctx context.Context, nodeID string, initData vkModel.NodeInfo) {
+// RegisterNode is called when a new node starts
+func (h *HttpTunnel) RegisterNode(ctx context.Context, initData vkModel.NodeInfo) {
 	h.Lock()
 	defer h.Unlock()
 
 	// check base network info, if not exist, extract from initData
-	_, has := h.nodeNetworkInfoOfNodeID[nodeID]
+	nodeID := utils2.ExtractNodeIDFromNodeName(initData.Metadata.Name)
+	_, has := h.nodeIdToBaseStatusMap[nodeID]
 	if !has {
-		h.nodeNetworkInfoOfNodeID[nodeID] = utils.ExtractNetworkInfoFromNodeInfoData(initData)
+		h.nodeIdToBaseStatusMap[nodeID] = utils.ConvertBaseStatusFromNodeInfo(initData)
 	}
 }
 
-// OnNodeStop is called when a node stops
-func (h *HttpTunnel) OnNodeStop(ctx context.Context, nodeID string) {
+// UnRegisterNode is called when a node stops
+func (h *HttpTunnel) UnRegisterNode(ctx context.Context, nodeName string) {
 	h.Lock()
 	defer h.Unlock()
-	delete(h.nodeNetworkInfoOfNodeID, nodeID)
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	delete(h.nodeIdToBaseStatusMap, nodeID)
 }
 
 // OnNodeNotReady is called when a node is not ready
-func (h *HttpTunnel) OnNodeNotReady(ctx context.Context, info vkModel.UnreachableNodeInfo) {
-	utils.OnBaseUnreachable(ctx, info, h.env, h.Client)
+func (h *HttpTunnel) OnNodeNotReady(ctx context.Context, nodeName string) {
+	utils.OnBaseUnreachable(ctx, nodeName, h.kubeClient)
 }
 
 // Key returns the key of the tunnel
@@ -92,7 +103,7 @@ func (h *HttpTunnel) Key() string {
 }
 
 // RegisterCallback registers the callback functions for the tunnel
-func (h *HttpTunnel) RegisterCallback(onBaseDiscovered tunnel.OnNodeDiscovered, onHealthDataArrived tunnel.OnNodeStatusDataArrived, onQueryAllBizDataArrived tunnel.OnAllBizStatusArrived, onOneBizDataArrived tunnel.OnSingleBizStatusArrived) {
+func (h *HttpTunnel) RegisterCallback(onBaseDiscovered tunnel.OnBaseDiscovered, onHealthDataArrived tunnel.OnBaseStatusArrived, onQueryAllBizDataArrived tunnel.OnAllBizStatusArrived, onOneBizDataArrived tunnel.OnSingleBizStatusArrived) {
 	h.onBaseDiscovered = onBaseDiscovered
 
 	h.onHealthDataArrived = onHealthDataArrived
@@ -102,15 +113,10 @@ func (h *HttpTunnel) RegisterCallback(onBaseDiscovered tunnel.OnNodeDiscovered,
 	h.onOneBizDataArrived = onOneBizDataArrived
 }
 
-// RegisterQuery registers the query function for the tunnel
-func (h *HttpTunnel) RegisterQuery(queryBaseline module_tunnels.QueryBaseline) {
-	h.queryBaseline = queryBaseline
-}
-
 // Start starts the tunnel
 func (h *HttpTunnel) Start(ctx context.Context, clientID, env string) (err error) {
 	h.onlineNode = make(map[string]bool)
-	h.nodeNetworkInfoOfNodeID = make(map[string]model.NetworkInfo)
+	h.nodeIdToBaseStatusMap = make(map[string]model.BaseStatus)
 	h.env = env
 
 	h.arkService = ark_service.NewService()
@@ -129,7 +135,7 @@ func (h *HttpTunnel) startBaseDiscovery(ctx context.Context) {
 	// start a simple http server to handle base discovery, exit when ctx done
 	mux := http.NewServeMux()
 	server := &http.Server{
-		Addr:    fmt.Sprintf(":%d", h.Port),
+		Addr:    fmt.Sprintf(":%d", h.port),
 		Handler: mux,
 	}
 
@@ -140,7 +146,7 @@ func (h *HttpTunnel) startBaseDiscovery(ctx context.Context) {
 			return
 		}
 
-		heartbeatData := model.HeartBeatData{}
+		heartbeatData := model.BaseStatus{}
 		err := json.NewDecoder(r.Body).Decode(&heartbeatData)
 		if err != nil {
 			logger.WithError(err).Error("failed to unmarshal heartbeat data")
@@ -149,9 +155,9 @@ func (h *HttpTunnel) startBaseDiscovery(ctx context.Context) {
 			return
 		}
 		h.Lock()
-		h.nodeNetworkInfoOfNodeID[heartbeatData.BaseID] = heartbeatData.NetworkInfo
+		h.nodeIdToBaseStatusMap[heartbeatData.BaseMetadata.Identity] = heartbeatData
 		h.Unlock()
-		h.onBaseDiscovered(heartbeatData.BaseID, utils.TranslateHeartBeatDataToNodeInfo(heartbeatData), h)
+		h.onBaseDiscovered(utils.ConvertBaseStatusToNodeInfo(heartbeatData, h.env))
 
 		w.WriteHeader(http.StatusOK)
 		w.Write([]byte("SUCCESS"))
@@ -163,21 +169,19 @@ func (h *HttpTunnel) startBaseDiscovery(ctx context.Context) {
 			return
 		}
 
-		queryBaselineRequest := model.Metadata{}
-		err := json.NewDecoder(r.Body).Decode(&queryBaselineRequest)
+		baseMetadata := model.BaseMetadata{}
+		err := json.NewDecoder(r.Body).Decode(&baseMetadata)
 		if err != nil {
-			logger.WithError(err).Error("failed to unmarshal queryBaselineRequest data")
+			logger.WithError(err).Error("failed to unmarshal baseMetadata data")
 			w.WriteHeader(http.StatusBadRequest)
 			w.Write([]byte(err.Error()))
 			return
 		}
 
 		baselineBizs := make([]ark.BizModel, 0)
-		if h.queryBaseline != nil {
-			baseline := h.queryBaseline(utils.TranslateHeartBeatDataToBaselineQuery(queryBaselineRequest))
-			for _, container := range baseline {
-				baselineBizs = append(baselineBizs, utils.TranslateCoreV1ContainerToBizModel(&container))
-			}
+		baseline := h.moduleDeploymentController.QueryContainerBaseline(utils.ConvertBaseMetadataToBaselineQuery(baseMetadata))
+		for _, container := range baseline {
+			baselineBizs = append(baselineBizs, utils.TranslateCoreV1ContainerToBizModel(&container))
 		}
 
 		jsonData, _ := json.Marshal(baselineBizs)
@@ -193,7 +197,7 @@ func (h *HttpTunnel) startBaseDiscovery(ctx context.Context) {
 		}
 	}()
 
-	logger.Infof("http base discovery server started, listening on port %d", h.Port)
+	logger.Infof("http base discovery server started, listening on port %d", h.port)
 
 	defer func() {
 		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -203,40 +207,30 @@ func (h *HttpTunnel) startBaseDiscovery(ctx context.Context) {
 		}
 	}()
 	<-ctx.Done()
-
 }
 
 // healthMsgCallback is the callback function for health messages
 func (h *HttpTunnel) healthMsgCallback(nodeID string, data ark_service.HealthResponse) {
-	if data.BaseID != nodeID {
-		return
-	}
 	if data.Code != "SUCCESS" {
 		return
 	}
 	if h.onHealthDataArrived != nil {
-		h.onHealthDataArrived(nodeID, utils.TranslateHealthDataToNodeStatus(data.Data.HealthData))
+		h.onHealthDataArrived(utils2.FormatNodeName(nodeID, h.env), utils.ConvertHealthDataToNodeStatus(data.Data.HealthData))
 	}
 }
 
 // allBizMsgCallback is the callback function for all business messages
 func (h *HttpTunnel) allBizMsgCallback(nodeID string, data ark_service.QueryAllBizResponse) {
-	if data.BaseID != nodeID {
-		return
-	}
 	if data.Code != "SUCCESS" {
 		return
 	}
 	if h.onQueryAllBizDataArrived != nil {
-		h.onQueryAllBizDataArrived(nodeID, utils.TranslateBizInfosToContainerStatuses(data.GenericArkResponseBase.Data, time.Now().UnixMilli()))
+		h.onQueryAllBizDataArrived(utils2.FormatNodeName(nodeID, h.env), utils.TranslateBizInfosToContainerStatuses(data.GenericArkResponseBase.Data, time.Now().UnixMilli()))
 	}
 }
 
 // bizOperationResponseCallback is the callback function for business operation responses
 func (h *HttpTunnel) bizOperationResponseCallback(nodeID string, data model.BizOperationResponse) {
-	if data.Response.BaseID != nodeID {
-		return
-	}
 	if data.Response.Code == "SUCCESS" {
 		if data.Command == model.CommandInstallBiz {
 			// not update here, update in all biz response callback
@@ -247,7 +241,7 @@ func (h *HttpTunnel) bizOperationResponseCallback(nodeID string, data model.BizO
 		logrus.Errorf("biz operation failed: %s\n%s\n%s", data.Response.Message, data.Response.ErrorStackTrace, data.Response.Data.Message)
 	}
 
-	h.onOneBizDataArrived(nodeID, vkModel.BizStatusData{
+	h.onOneBizDataArrived(utils2.FormatNodeName(nodeID, h.env), vkModel.BizStatusData{
 		Key:  utils.GetBizIdentity(data.BizName, data.BizVersion),
 		Name: data.BizName,
 		// fille PodKey when using
@@ -260,15 +254,16 @@ func (h *HttpTunnel) bizOperationResponseCallback(nodeID string, data model.BizO
 }
 
 // FetchHealthData fetches health data from the node
-func (h *HttpTunnel) FetchHealthData(ctx context.Context, nodeID string) error {
+func (h *HttpTunnel) FetchHealthData(ctx context.Context, nodeName string) error {
 	h.Lock()
-	networkInfo, ok := h.nodeNetworkInfoOfNodeID[nodeID]
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	baseStatus, ok := h.nodeIdToBaseStatusMap[nodeID]
 	h.Unlock()
 	if !ok {
 		return errors.New("network info not found")
 	}
 
-	healthData, err := h.arkService.Health(ctx, networkInfo.LocalIP, networkInfo.ArkletPort)
+	healthData, err := h.arkService.Health(ctx, baseStatus.LocalIP, baseStatus.Port)
 
 	if err != nil {
 		return err
@@ -280,7 +275,7 @@ func (h *HttpTunnel) FetchHealthData(ctx context.Context, nodeID string) error {
 }
 
 // QueryAllBizStatusData queries all container status data from the node
-func (h *HttpTunnel) QueryAllBizStatusData(ctx context.Context, nodeID string) error {
+func (h *HttpTunnel) QueryAllBizStatusData(ctx context.Context, nodeName string) error {
 	// add a signal to check
 	success := h.queryAllBizLock.TryLock()
 	if !success {
@@ -292,18 +287,19 @@ func (h *HttpTunnel) QueryAllBizStatusData(ctx context.Context, nodeID string) e
 	defer func() {
 		h.queryAllBizLock.Unlock()
 		if h.queryAllBizDataOutdated {
-			go h.QueryAllBizStatusData(ctx, nodeID)
+			go h.QueryAllBizStatusData(ctx, nodeName)
 		}
 	}()
 
 	h.Lock()
-	networkInfo, ok := h.nodeNetworkInfoOfNodeID[nodeID]
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	baseStatus, ok := h.nodeIdToBaseStatusMap[nodeID]
 	h.Unlock()
 	if !ok {
 		return errors.New("network info not found")
 	}
 
-	allBizData, err := h.arkService.QueryAllBiz(ctx, networkInfo.LocalIP, networkInfo.ArkletPort)
+	allBizData, err := h.arkService.QueryAllBiz(ctx, baseStatus.LocalIP, baseStatus.Port)
 
 	if err != nil {
 		return err
@@ -315,10 +311,10 @@ func (h *HttpTunnel) QueryAllBizStatusData(ctx context.Context, nodeID string) e
 }
 
 // StartBiz starts a container on the node
-func (h *HttpTunnel) StartBiz(ctx context.Context, nodeID, _ string, container *corev1.Container) error {
-
+func (h *HttpTunnel) StartBiz(ctx context.Context, nodeName, _ string, container *corev1.Container) error {
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
 	h.Lock()
-	networkInfo, ok := h.nodeNetworkInfoOfNodeID[nodeID]
+	baseStatus, ok := h.nodeIdToBaseStatusMap[nodeID]
 	h.Unlock()
 	if !ok {
 		return errors.New("network info not found")
@@ -337,22 +333,20 @@ func (h *HttpTunnel) StartBiz(ctx context.Context, nodeID, _ string, container *
 
 	response, err := h.arkService.InstallBiz(ctx, ark_service.InstallBizRequest{
 		BizModel: bizModel,
-	}, networkInfo.LocalIP, networkInfo.ArkletPort)
+	}, baseStatus.LocalIP, baseStatus.Port)
 
 	bizOperationResponse.Response = response
 
 	h.bizOperationResponseCallback(nodeID, bizOperationResponse)
 
-	go h.QueryAllBizStatusData(ctx, nodeID)
-
 	return err
 }
 
 // StopBiz shuts down a container on the node
-func (h *HttpTunnel) StopBiz(ctx context.Context, nodeID, _ string, container *corev1.Container) error {
-
+func (h *HttpTunnel) StopBiz(ctx context.Context, nodeName, _ string, container *corev1.Container) error {
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
 	h.Lock()
-	networkInfo, ok := h.nodeNetworkInfoOfNodeID[nodeID]
+	baseStatus, ok := h.nodeIdToBaseStatusMap[nodeID]
 	h.Unlock()
 	if !ok {
 		return errors.New("network info not found")
@@ -370,13 +364,11 @@ func (h *HttpTunnel) StopBiz(ctx context.Context, nodeID, _ string, container *c
 
 	response, err := h.arkService.UninstallBiz(ctx, ark_service.UninstallBizRequest{
 		BizModel: bizModel,
-	}, networkInfo.LocalIP, networkInfo.ArkletPort)
+	}, baseStatus.LocalIP, baseStatus.Port)
 
 	bizOperationResponse.Response = response
 
 	h.bizOperationResponseCallback(nodeID, bizOperationResponse)
 
-	go h.QueryAllBizStatusData(ctx, nodeID)
-
 	return err
 }
diff --git a/module_tunnels/koupleless_mqtt_tunnel/mqtt_tunnel.go b/module_tunnels/koupleless_mqtt_tunnel/mqtt_tunnel.go
index 0cafba3..d929be0 100644
--- a/module_tunnels/koupleless_mqtt_tunnel/mqtt_tunnel.go
+++ b/module_tunnels/koupleless_mqtt_tunnel/mqtt_tunnel.go
@@ -8,104 +8,105 @@ import (
 	"github.com/koupleless/arkctl/v1/service/ark"
 	"github.com/koupleless/module_controller/common/model"
 	"github.com/koupleless/module_controller/common/utils"
-	"github.com/koupleless/module_controller/module_tunnels"
+	"github.com/koupleless/module_controller/controller/module_deployment_controller"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_mqtt_tunnel/mqtt"
 	"github.com/koupleless/virtual-kubelet/common/log"
+	utils2 "github.com/koupleless/virtual-kubelet/common/utils"
 	vkModel "github.com/koupleless/virtual-kubelet/model"
 	"github.com/koupleless/virtual-kubelet/tunnel"
 	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
-	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sync"
 	"time"
 )
 
-var _ module_tunnels.ModuleTunnel = &MqttTunnel{}
+var _ tunnel.Tunnel = &MqttTunnel{}
 
 type MqttTunnel struct {
 	sync.Mutex
 
 	mqttClient *mqtt.Client
-	cache.Cache
-	client.Client
-	env string
+	kubeClient client.Client
+	env        string
 
 	ready bool
 
-	onBaseDiscovered         tunnel.OnNodeDiscovered
-	onHealthDataArrived      tunnel.OnNodeStatusDataArrived
-	onQueryAllBizDataArrived tunnel.OnAllBizStatusArrived
-	onOneBizDataArrived      tunnel.OnSingleBizStatusArrived
-	queryBaseline            module_tunnels.QueryBaseline
+	onBaseDiscovered      tunnel.OnBaseDiscovered
+	onHealthDataArrived   tunnel.OnBaseStatusArrived
+	onAllBizStatusArrived tunnel.OnAllBizStatusArrived
+	onOneBizDataArrived   tunnel.OnSingleBizStatusArrived
 
-	onlineNode map[string]bool
+	moduleDeploymentController *module_deployment_controller.ModuleDeploymentController
 }
 
-func (m *MqttTunnel) Ready() bool {
-	return m.ready
+func NewMqttTunnel(env string, kubeClient client.Client, moduleDeploymentController *module_deployment_controller.ModuleDeploymentController) MqttTunnel {
+	return MqttTunnel{
+		env:                        env,
+		kubeClient:                 kubeClient,
+		moduleDeploymentController: moduleDeploymentController,
+	}
+}
+
+func (mqttTunnel *MqttTunnel) Ready() bool {
+	return mqttTunnel.ready
 }
 
-func (m *MqttTunnel) GetBizUniqueKey(container *corev1.Container) string {
+func (mqttTunnel *MqttTunnel) GetBizUniqueKey(container *corev1.Container) string {
 	return utils.GetBizIdentity(container.Name, utils.GetBizVersionFromContainer(container))
 }
 
-func (m *MqttTunnel) OnNodeStart(ctx context.Context, nodeID string, _ vkModel.NodeInfo) {
-	m.mqttClient.Sub(fmt.Sprintf(model.BaseHealthTopic, m.env, nodeID), mqtt.Qos1, m.healthMsgCallback)
+func (mqttTunnel *MqttTunnel) RegisterNode(ctx context.Context, nodeInfo vkModel.NodeInfo) {
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeInfo.Metadata.Name)
+	mqttTunnel.mqttClient.Sub(fmt.Sprintf(model.BaseHealthTopic, mqttTunnel.env, nodeID), mqtt.Qos1, mqttTunnel.healthMsgCallback)
 
-	m.mqttClient.Sub(fmt.Sprintf(model.BaseSimpleBizTopic, m.env, nodeID), mqtt.Qos1, m.bizMsgCallback)
+	mqttTunnel.mqttClient.Sub(fmt.Sprintf(model.BaseSimpleBizTopic, mqttTunnel.env, nodeID), mqtt.Qos1, mqttTunnel.bizMsgCallback)
 
-	m.mqttClient.Sub(fmt.Sprintf(model.BaseAllBizTopic, m.env, nodeID), mqtt.Qos1, m.allBizMsgCallback)
+	mqttTunnel.mqttClient.Sub(fmt.Sprintf(model.BaseAllBizTopic, mqttTunnel.env, nodeID), mqtt.Qos1, mqttTunnel.allBizMsgCallback)
 
-	m.mqttClient.Sub(fmt.Sprintf(model.BaseBizOperationResponseTopic, m.env, nodeID), mqtt.Qos1, m.bizOperationResponseCallback)
-	m.Lock()
-	defer m.Unlock()
-	m.onlineNode[nodeID] = true
+	mqttTunnel.mqttClient.Sub(fmt.Sprintf(model.BaseBizOperationResponseTopic, mqttTunnel.env, nodeID), mqtt.Qos1, mqttTunnel.bizOperationResponseCallback)
+	mqttTunnel.Lock()
+	defer mqttTunnel.Unlock()
 }
 
-func (m *MqttTunnel) OnNodeStop(ctx context.Context, nodeID string) {
-	m.mqttClient.UnSub(fmt.Sprintf(model.BaseHealthTopic, m.env, nodeID))
+func (mqttTunnel *MqttTunnel) UnRegisterNode(ctx context.Context, nodeName string) {
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	mqttTunnel.mqttClient.UnSub(fmt.Sprintf(model.BaseHealthTopic, mqttTunnel.env, nodeID))
 
-	m.mqttClient.UnSub(fmt.Sprintf(model.BaseSimpleBizTopic, m.env, nodeID))
+	mqttTunnel.mqttClient.UnSub(fmt.Sprintf(model.BaseSimpleBizTopic, mqttTunnel.env, nodeID))
 
-	m.mqttClient.UnSub(fmt.Sprintf(model.BaseAllBizTopic, m.env, nodeID))
+	mqttTunnel.mqttClient.UnSub(fmt.Sprintf(model.BaseAllBizTopic, mqttTunnel.env, nodeID))
 
-	m.mqttClient.UnSub(fmt.Sprintf(model.BaseBizOperationResponseTopic, m.env, nodeID))
+	mqttTunnel.mqttClient.UnSub(fmt.Sprintf(model.BaseBizOperationResponseTopic, mqttTunnel.env, nodeID))
 
-	m.Lock()
-	defer m.Unlock()
-	delete(m.onlineNode, nodeID)
+	mqttTunnel.Lock()
+	defer mqttTunnel.Unlock()
 }
 
-func (m *MqttTunnel) OnNodeNotReady(ctx context.Context, info vkModel.UnreachableNodeInfo) {
-	utils.OnBaseUnreachable(ctx, info, m.env, m.Client)
+func (mqttTunnel *MqttTunnel) OnNodeNotReady(ctx context.Context, nodeName string) {
+	utils.OnBaseUnreachable(ctx, nodeName, mqttTunnel.kubeClient)
 }
 
-func (m *MqttTunnel) Key() string {
+func (mqttTunnel *MqttTunnel) Key() string {
 	return "mqtt_tunnel_provider"
 }
 
-func (m *MqttTunnel) RegisterCallback(onBaseDiscovered tunnel.OnNodeDiscovered, onHealthDataArrived tunnel.OnNodeStatusDataArrived, onQueryAllBizDataArrived tunnel.OnAllBizStatusArrived, onOneBizDataArrived tunnel.OnSingleBizStatusArrived) {
-	m.onBaseDiscovered = onBaseDiscovered
+func (mqttTunnel *MqttTunnel) RegisterCallback(onBaseDiscovered tunnel.OnBaseDiscovered, onHealthDataArrived tunnel.OnBaseStatusArrived, onAllBizStatusArrived tunnel.OnAllBizStatusArrived, onOneBizDataArrived tunnel.OnSingleBizStatusArrived) {
+	mqttTunnel.onBaseDiscovered = onBaseDiscovered
 
-	m.onHealthDataArrived = onHealthDataArrived
+	mqttTunnel.onHealthDataArrived = onHealthDataArrived
 
-	m.onQueryAllBizDataArrived = onQueryAllBizDataArrived
-
-	m.onOneBizDataArrived = onOneBizDataArrived
-}
+	mqttTunnel.onAllBizStatusArrived = onAllBizStatusArrived
 
-func (m *MqttTunnel) RegisterQuery(queryBaseline module_tunnels.QueryBaseline) {
-	m.queryBaseline = queryBaseline
+	mqttTunnel.onOneBizDataArrived = onOneBizDataArrived
 }
 
-func (m *MqttTunnel) Start(ctx context.Context, clientID, env string) (err error) {
+func (mqttTunnel *MqttTunnel) Start(ctx context.Context, clientID, env string) (err error) {
 	c := &MqttConfig{}
 	c.init()
 	clientID = fmt.Sprintf("%s@@@%s", c.MqttClientPrefix, clientID)
-	m.onlineNode = make(map[string]bool)
-	m.env = env
-	m.mqttClient, err = mqtt.NewMqttClient(&mqtt.ClientConfig{
+	mqttTunnel.env = env
+	mqttTunnel.mqttClient, err = mqtt.NewMqttClient(&mqtt.ClientConfig{
 		Broker:        c.MqttBroker,
 		Port:          c.MqttPort,
 		ClientID:      clientID,
@@ -117,35 +118,31 @@ func (m *MqttTunnel) Start(ctx context.Context, clientID, env string) (err error
 		CleanSession:  true,
 		OnConnectHandler: func(client paho.Client) {
 			log.G(ctx).Info("MQTT client connected :", clientID)
-			client.Subscribe(fmt.Sprintf(model.BaseHeartBeatTopic, m.env), mqtt.Qos1, m.heartBeatMsgCallback)
-			client.Subscribe(fmt.Sprintf(model.BaseQueryBaselineTopic, m.env), mqtt.Qos1, m.queryBaselineMsgCallback)
-			for nodeId, _ := range m.onlineNode {
-				m.OnNodeStart(ctx, nodeId, vkModel.NodeInfo{})
-			}
+			client.Subscribe(fmt.Sprintf(model.BaseHeartBeatTopic, mqttTunnel.env), mqtt.Qos1, mqttTunnel.heartBeatMsgCallback)
+			client.Subscribe(fmt.Sprintf(model.BaseQueryBaselineTopic, mqttTunnel.env), mqtt.Qos1, mqttTunnel.queryBaselineMsgCallback)
 		},
 	})
 
-	err = m.mqttClient.Connect()
+	err = mqttTunnel.mqttClient.Connect()
 	if err != nil {
 		log.G(ctx).WithError(err).Error("mqtt connect error")
-		return
+		return err
 	}
 
 	go func() {
 		<-ctx.Done()
-		m.mqttClient.Disconnect()
+		mqttTunnel.mqttClient.Disconnect()
 	}()
-	m.ready = true
+	mqttTunnel.ready = true
 	return
 }
 
-func (m *MqttTunnel) heartBeatMsgCallback(_ paho.Client, msg paho.Message) {
+func (mqttTunnel *MqttTunnel) heartBeatMsgCallback(_ paho.Client, msg paho.Message) {
 	defer msg.Ack()
 
 	logrus.Infof("query health beat callback for %s: %s", msg.Topic(), msg.Payload())
 
-	nodeID := utils.GetBaseIDFromTopic(msg.Topic())
-	var data model.ArkMqttMsg[model.HeartBeatData]
+	var data model.ArkMqttMsg[model.BaseStatus]
 	err := json.Unmarshal(msg.Payload(), &data)
 	if err != nil {
 		logrus.Errorf("Error unmarshalling heart beat data: %v", err)
@@ -154,18 +151,18 @@ func (m *MqttTunnel) heartBeatMsgCallback(_ paho.Client, msg paho.Message) {
 	if utils.Expired(data.PublishTimestamp, 1000*10) {
 		return
 	}
-	if m.onBaseDiscovered != nil {
-		m.onBaseDiscovered(nodeID, utils.TranslateHeartBeatDataToNodeInfo(data.Data), m)
+	if mqttTunnel.onBaseDiscovered != nil {
+		mqttTunnel.onBaseDiscovered(utils.ConvertBaseStatusToNodeInfo(data.Data, mqttTunnel.env))
 	}
 }
 
-func (m *MqttTunnel) queryBaselineMsgCallback(_ paho.Client, msg paho.Message) {
+func (mqttTunnel *MqttTunnel) queryBaselineMsgCallback(_ paho.Client, msg paho.Message) {
 	defer msg.Ack()
 
 	logrus.Infof("query baseline callback for %s: %s", msg.Topic(), msg.Payload())
 
-	nodeID := utils.GetBaseIDFromTopic(msg.Topic())
-	var data model.ArkMqttMsg[model.Metadata]
+	nodeID := utils.GetBaseIdentityFromTopic(msg.Topic())
+	var data model.ArkMqttMsg[model.BaseMetadata]
 	err := json.Unmarshal(msg.Payload(), &data)
 	if err != nil {
 		logrus.Errorf("Error unmarshalling queryBaseline data: %v", err)
@@ -174,28 +171,27 @@ func (m *MqttTunnel) queryBaselineMsgCallback(_ paho.Client, msg paho.Message) {
 	if utils.Expired(data.PublishTimestamp, 1000*10) {
 		return
 	}
-	if m.queryBaseline != nil {
-		baseline := m.queryBaseline(utils.TranslateHeartBeatDataToBaselineQuery(data.Data))
-		go func() {
-			baselineBizs := make([]ark.BizModel, 0)
-			for _, container := range baseline {
-				baselineBizs = append(baselineBizs, utils.TranslateCoreV1ContainerToBizModel(&container))
-			}
-			baselineBytes, _ := json.Marshal(baselineBizs)
-			err = m.mqttClient.Pub(utils.FormatBaselineResponseTopic(m.env, nodeID), mqtt.Qos1, baselineBytes)
-			if err != nil {
-				logrus.WithError(err).Errorf("Error publishing baseline response data")
-			}
-		}()
-	}
+
+	baselineContainers := mqttTunnel.moduleDeploymentController.QueryContainerBaseline(utils.ConvertBaseMetadataToBaselineQuery(data.Data))
+	go func() {
+		baselineBizs := make([]ark.BizModel, 0)
+		for _, container := range baselineContainers {
+			baselineBizs = append(baselineBizs, utils.TranslateCoreV1ContainerToBizModel(&container))
+		}
+		baselineBytes, _ := json.Marshal(baselineBizs)
+		err = mqttTunnel.mqttClient.Pub(utils.FormatBaselineResponseTopic(mqttTunnel.env, nodeID), mqtt.Qos1, baselineBytes)
+		if err != nil {
+			logrus.WithError(err).Errorf("Error publishing baselineContainers response data")
+		}
+	}()
 }
 
-func (m *MqttTunnel) healthMsgCallback(_ paho.Client, msg paho.Message) {
+func (mqttTunnel *MqttTunnel) healthMsgCallback(_ paho.Client, msg paho.Message) {
 	defer msg.Ack()
 
 	logrus.Infof("query base health status callback for %s: %s", msg.Topic(), msg.Payload())
 
-	nodeID := utils.GetBaseIDFromTopic(msg.Topic())
+	nodeID := utils.GetBaseIdentityFromTopic(msg.Topic())
 	var data model.ArkMqttMsg[ark.HealthResponse]
 	err := json.Unmarshal(msg.Payload(), &data)
 	if err != nil {
@@ -208,17 +204,18 @@ func (m *MqttTunnel) healthMsgCallback(_ paho.Client, msg paho.Message) {
 	if data.Data.Code != "SUCCESS" {
 		return
 	}
-	if m.onHealthDataArrived != nil {
-		m.onHealthDataArrived(nodeID, utils.TranslateHealthDataToNodeStatus(data.Data.Data.HealthData))
+	if mqttTunnel.onHealthDataArrived != nil {
+		nodeName := utils2.FormatNodeName(nodeID, mqttTunnel.env)
+		mqttTunnel.onHealthDataArrived(nodeName, utils.ConvertHealthDataToNodeStatus(data.Data.Data.HealthData))
 	}
 }
 
-func (m *MqttTunnel) bizMsgCallback(_ paho.Client, msg paho.Message) {
+func (mqttTunnel *MqttTunnel) bizMsgCallback(_ paho.Client, msg paho.Message) {
 	defer msg.Ack()
 
 	logrus.Infof("query all simple biz status callback for %s: %s", msg.Topic(), msg.Payload())
 
-	nodeID := utils.GetBaseIDFromTopic(msg.Topic())
+	nodeID := utils.GetBaseIdentityFromTopic(msg.Topic())
 	var data model.ArkMqttMsg[model.ArkSimpleAllBizInfoData]
 	err := json.Unmarshal(msg.Payload(), &data)
 	if err != nil {
@@ -229,18 +226,19 @@ func (m *MqttTunnel) bizMsgCallback(_ paho.Client, msg paho.Message) {
 		return
 	}
 
-	if m.onQueryAllBizDataArrived != nil {
+	if mqttTunnel.onAllBizStatusArrived != nil {
 		bizInfos := utils.TranslateSimpleBizDataToBizInfos(data.Data)
 		// 更新 vNode 上 stats
-		m.onQueryAllBizDataArrived(nodeID, utils.TranslateBizInfosToContainerStatuses(bizInfos, data.PublishTimestamp))
+		nodeName := utils2.FormatNodeName(nodeID, mqttTunnel.env)
+		mqttTunnel.onAllBizStatusArrived(nodeName, utils.TranslateBizInfosToContainerStatuses(bizInfos, data.PublishTimestamp))
 	}
 }
 
-func (m *MqttTunnel) allBizMsgCallback(_ paho.Client, msg paho.Message) {
+func (mqttTunnel *MqttTunnel) allBizMsgCallback(_ paho.Client, msg paho.Message) {
 	defer msg.Ack()
 	logrus.Infof("query all biz status callback for %s: %s", msg.Topic(), msg.Payload())
 
-	nodeID := utils.GetBaseIDFromTopic(msg.Topic())
+	nodeID := utils.GetBaseIdentityFromTopic(msg.Topic())
 	var data model.ArkMqttMsg[ark.QueryAllArkBizResponse]
 	err := json.Unmarshal(msg.Payload(), &data)
 	if err != nil {
@@ -251,17 +249,18 @@ func (m *MqttTunnel) allBizMsgCallback(_ paho.Client, msg paho.Message) {
 		return
 	}
 
-	if m.onQueryAllBizDataArrived != nil {
-		m.onQueryAllBizDataArrived(nodeID, utils.TranslateBizInfosToContainerStatuses(data.Data.GenericArkResponseBase.Data, data.PublishTimestamp))
+	if mqttTunnel.onAllBizStatusArrived != nil {
+		nodeName := utils2.FormatNodeName(nodeID, mqttTunnel.env)
+		mqttTunnel.onAllBizStatusArrived(nodeName, utils.TranslateBizInfosToContainerStatuses(data.Data.GenericArkResponseBase.Data, data.PublishTimestamp))
 	}
 }
 
-func (m *MqttTunnel) bizOperationResponseCallback(_ paho.Client, msg paho.Message) {
+func (mqttTunnel *MqttTunnel) bizOperationResponseCallback(_ paho.Client, msg paho.Message) {
 	defer msg.Ack()
 
 	logrus.Infof("query biz operation status callback for %s: %s", msg.Topic(), msg.Payload())
 
-	nodeID := utils.GetBaseIDFromTopic(msg.Topic())
+	nodeID := utils.GetBaseIdentityFromTopic(msg.Topic())
 	var data model.ArkMqttMsg[model.BizOperationResponse]
 	err := json.Unmarshal(msg.Payload(), &data)
 	if err != nil {
@@ -269,8 +268,9 @@ func (m *MqttTunnel) bizOperationResponseCallback(_ paho.Client, msg paho.Messag
 		return
 	}
 
+	nodeName := utils2.FormatNodeName(nodeID, mqttTunnel.env)
 	if data.Data.Command == model.CommandInstallBiz && data.Data.Response.Code == "SUCCESS" {
-		m.onOneBizDataArrived(nodeID, vkModel.BizStatusData{
+		mqttTunnel.onOneBizDataArrived(nodeName, vkModel.BizStatusData{
 			Key:  utils.GetBizIdentity(data.Data.BizName, data.Data.BizVersion),
 			Name: data.Data.BizName,
 			// fille PodKey when using
@@ -279,7 +279,7 @@ func (m *MqttTunnel) bizOperationResponseCallback(_ paho.Client, msg paho.Messag
 			ChangeTime: time.UnixMilli(data.PublishTimestamp),
 		})
 	} else if data.Data.Command == model.CommandUnInstallBiz && data.Data.Response.Code == "SUCCESS" {
-		m.onOneBizDataArrived(nodeID, vkModel.BizStatusData{
+		mqttTunnel.onOneBizDataArrived(nodeName, vkModel.BizStatusData{
 			Key:  utils.GetBizIdentity(data.Data.BizName, data.Data.BizVersion),
 			Name: data.Data.BizName,
 			// fille PodKey when using
@@ -290,26 +290,30 @@ func (m *MqttTunnel) bizOperationResponseCallback(_ paho.Client, msg paho.Messag
 	}
 }
 
-func (m *MqttTunnel) FetchHealthData(_ context.Context, nodeID string) error {
-	return m.mqttClient.Pub(utils.FormatArkletCommandTopic(m.env, nodeID, model.CommandHealth), mqtt.Qos0, []byte("{}"))
+func (mqttTunnel *MqttTunnel) FetchHealthData(_ context.Context, nodeName string) error {
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	return mqttTunnel.mqttClient.Pub(utils.FormatArkletCommandTopic(mqttTunnel.env, nodeID, model.CommandHealth), mqtt.Qos0, []byte("{}"))
 }
 
-func (m *MqttTunnel) QueryAllBizStatusData(_ context.Context, nodeID string) error {
-	return m.mqttClient.Pub(utils.FormatArkletCommandTopic(m.env, nodeID, model.CommandQueryAllBiz), mqtt.Qos0, []byte("{}"))
+func (mqttTunnel *MqttTunnel) QueryAllBizStatusData(_ context.Context, nodeName string) error {
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	return mqttTunnel.mqttClient.Pub(utils.FormatArkletCommandTopic(mqttTunnel.env, nodeID, model.CommandQueryAllBiz), mqtt.Qos0, []byte("{}"))
 }
 
-func (m *MqttTunnel) StartBiz(ctx context.Context, nodeID, _ string, container *corev1.Container) error {
+func (mqttTunnel *MqttTunnel) StartBiz(ctx context.Context, nodeName, _ string, container *corev1.Container) error {
 	bizModel := utils.TranslateCoreV1ContainerToBizModel(container)
 	logger := log.G(ctx).WithField("bizName", bizModel.BizName).WithField("bizVersion", bizModel.BizVersion)
 	logger.Info("InstallModule")
 	installBizRequestBytes, _ := json.Marshal(bizModel)
-	return m.mqttClient.Pub(utils.FormatArkletCommandTopic(m.env, nodeID, model.CommandInstallBiz), mqtt.Qos0, installBizRequestBytes)
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	return mqttTunnel.mqttClient.Pub(utils.FormatArkletCommandTopic(mqttTunnel.env, nodeID, model.CommandInstallBiz), mqtt.Qos0, installBizRequestBytes)
 }
 
-func (m *MqttTunnel) StopBiz(ctx context.Context, nodeID, _ string, container *corev1.Container) error {
+func (mqttTunnel *MqttTunnel) StopBiz(ctx context.Context, nodeName, _ string, container *corev1.Container) error {
 	bizModel := utils.TranslateCoreV1ContainerToBizModel(container)
 	unInstallBizRequestBytes, _ := json.Marshal(bizModel)
 	logger := log.G(ctx).WithField("bizName", bizModel.BizName).WithField("bizVersion", bizModel.BizVersion)
 	logger.Info("UninstallModule")
-	return m.mqttClient.Pub(utils.FormatArkletCommandTopic(m.env, nodeID, model.CommandUnInstallBiz), mqtt.Qos0, unInstallBizRequestBytes)
+	nodeID := utils2.ExtractNodeIDFromNodeName(nodeName)
+	return mqttTunnel.mqttClient.Pub(utils.FormatArkletCommandTopic(mqttTunnel.env, nodeID, model.CommandUnInstallBiz), mqtt.Qos0, unInstallBizRequestBytes)
 }
diff --git a/module_tunnels/module_tunnel.go b/module_tunnels/module_tunnel.go
deleted file mode 100644
index 621714c..0000000
--- a/module_tunnels/module_tunnel.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package module_tunnels
-
-import (
-	"github.com/koupleless/module_controller/common/model"
-	"github.com/koupleless/virtual-kubelet/tunnel"
-	v1 "k8s.io/api/core/v1"
-)
-
-type QueryBaseline func(model.QueryBaselineRequest) []v1.Container
-
-type ModuleTunnel interface {
-	tunnel.Tunnel
-
-	RegisterQuery(queryBaseline QueryBaseline)
-}
diff --git a/samples/module_controller_pod.yaml b/samples/module_controller_pod.yaml
index 0152aea..5dbd331 100644
--- a/samples/module_controller_pod.yaml
+++ b/samples/module_controller_pod.yaml
@@ -1,47 +1,55 @@
-apiVersion: v1
-kind: Pod
+apiVersion: apps/v1
+kind: Deployment
 metadata:
-  name: module-controller
-  labels:
-    app: module-controller
+  name: module-controller-pre
+  namespace: default
 spec:
-  serviceAccountName: virtual-kubelet
-  containers:
-    - name: module-controller
-      image: serverless-registry.cn-shanghai.cr.aliyuncs.com/opensource/release/module_controller:2.1.1
-      imagePullPolicy: Always
-      resources:
-        limits:
-          cpu: "1000m"
-          memory: "400Mi"
-      env:
-        - name: ENABLE_MODULE_DEPLOYMENT_CONTROLLER
-          value: "true"
-        - name: ENABLE_MQTT_TUNNEL # mqtt tunnel flag
-          value: "true" # true means enable, if enable mqtt tunnel, please configure env below
-        - name: MQTT_BROKER # mqtt broker url
-          value: YOUR_MQTT_BROKER_ENDPOINT
-        - name: MQTT_PORT # mqtt port
-          value: YOUR_MQTT_PORT
-        - name: MQTT_USERNAME # mqtt username
-          value: YOUR_MQTT_USERNAME
-        - name: MQTT_PASSWORD # mqtt password
-          value: YOUR_MQTT_PASSWORD
-        - name: MQTT_CLIENT_PREFIX # mqtt client prefix
-          value: YOUR_MQTT_CLIENT_PREFIX
-        - name: ENABLE_HTTP_TUNNEL # http tunnel flag
-          value: "true" # true means enable, if enable mqtt tunnel, you can choose to configure env below
-        - name: HTTP_TUNNEL_LISTEN_PORT # Module Controller HTTP Tunnel listen port, use 7777 for default
-          value: "7777"
-        - name: REPORT_HOOKS # error report hooks, only support dingTalk robot webhook
-          value: YOUR_REPORT_HOOKS
-        - name: ENV # module controller env, will set to vnode labels
-          value: YOUR_ENV
-        - name: IS_CLUSTER # cluster flag, if true, will use cluster configure to start virtual kubelet
-          value: "false"
-        - name: WORKLOAD_MAX_LEVEL # cluster configure, means max workload level for workload calculation in virtual kubelet, default is 3
-          value: "3"
-        - name: ENABLE_MODULE_DEPLOYMENT_CONTROLLER # module deployment controller flag, if true, will start deployment controller to modify module deployment's replicas and baseline
-          value: "true"
-        - name: VNODE_WORKER_NUM # VNode concurrent module processing thread number, set to 1 to indicate single thread
-          value: "8"
\ No newline at end of file
+  replicas: 3
+  selector:
+    matchLabels:
+      app: module-controller
+  template:
+    metadata:
+      labels:
+        app: module-controller
+    spec:
+      serviceAccountName: virtual-kubelet
+      containers:
+        - name: module-controller
+          image: serverless-registry.cn-shanghai.cr.aliyuncs.com/opensource/release/module-controller-v2:v2.1.2
+          imagePullPolicy: Always
+          resources:
+            limits:
+              cpu: "1000m"
+              memory: "400Mi"
+          env:
+            - name: ENABLE_MODULE_DEPLOYMENT_CONTROLLER
+              value: "true"
+            - name: ENABLE_MQTT_TUNNEL # mqtt tunnel flag
+              value: "true" # true means enable, if enable mqtt tunnel, please configure env below
+            - name: MQTT_BROKER # mqtt broker url
+              value: YOUR_MQTT_BROKER_ENDPOINT
+            - name: MQTT_PORT # mqtt port
+              value: YOUR_MQTT_PORT
+            - name: MQTT_USERNAME # mqtt username
+              value: YOUR_MQTT_USERNAME
+            - name: MQTT_PASSWORD # mqtt password
+              value: YOUR_MQTT_PASSWORD
+            - name: MQTT_CLIENT_PREFIX # mqtt client prefix
+              value: YOUR_MQTT_CLIENT_PREFIX
+            - name: ENABLE_HTTP_TUNNEL # http tunnel flag
+              value: "true" # true means enable, if enable mqtt tunnel, you can choose to configure env below
+            - name: HTTP_TUNNEL_LISTEN_PORT # Module Controller HTTP Tunnel listen port, use 7777 for default
+              value: "7777"
+            - name: REPORT_HOOKS # error report hooks, only support dingTalk robot webhook
+              value: YOUR_REPORT_HOOKS
+            - name: ENV # module controller env, will set to vnode labels
+              value: YOUR_ENV
+            - name: IS_CLUSTER # cluster flag, if true, will use cluster configure to start virtual kubelet
+              value: "false"
+            - name: WORKLOAD_MAX_LEVEL # cluster configure, means max workload level for workload calculation in virtual kubelet, default is 3
+              value: "3"
+            - name: ENABLE_MODULE_DEPLOYMENT_CONTROLLER # module deployment controller flag, if true, will start deployment controller to modify module deployment's replicas and baseline
+              value: "true"
+            - name: VNODE_WORKER_NUM # VNode concurrent module processing thread number, set to 1 to indicate single thread
+              value: "8"
\ No newline at end of file
diff --git a/suite/http/base_http_lifecycle_test.go b/suite/http/base_http_lifecycle_test.go
new file mode 100644
index 0000000..1d56398
--- /dev/null
+++ b/suite/http/base_http_lifecycle_test.go
@@ -0,0 +1,135 @@
+package http
+
+import (
+	"context"
+	"github.com/koupleless/virtual-kubelet/common/utils"
+	"github.com/koupleless/virtual-kubelet/model"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	v12 "k8s.io/api/coordination/v1"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/types"
+	"time"
+)
+
+var _ = Describe("Base Lifecycle Test", func() {
+
+	ctx := context.Background()
+
+	clusterName := "test-cluster-name"
+	httpNodeID := "test-http-base"
+	mockHttpBase := NewMockHttpBase(httpNodeID, clusterName, "1.0.0", env, 1238)
+
+	Context("http base online and deactive finally", func() {
+		It("base should become a ready vnode eventually", func() {
+			time.Sleep(time.Second)
+
+			go mockHttpBase.Start(ctx, clientID)
+
+			Eventually(func() bool {
+				lease := &v12.Lease{}
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name:      utils.FormatNodeName(httpNodeID, env),
+					Namespace: v1.NamespaceNodeLease,
+				}, lease)
+
+				isLeader := err == nil &&
+					*lease.Spec.HolderIdentity == clientID &&
+					!time.Now().After(lease.Spec.RenewTime.Time.Add(time.Second*model.NodeLeaseDurationSeconds))
+
+				return isLeader
+			}, time.Second*50, time.Second).Should(BeTrue())
+
+			Eventually(func() bool {
+				vnode := &v1.Node{}
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name: utils.FormatNodeName(httpNodeID, env),
+				}, vnode)
+				vnodeReady := false
+				for _, cond := range vnode.Status.Conditions {
+					if cond.Type == v1.NodeReady {
+						vnodeReady = cond.Status == v1.ConditionTrue
+						break
+					}
+				}
+				return err == nil && vnodeReady
+			}, time.Second*50, time.Second).Should(BeTrue())
+		})
+
+		It("base offline with deactive message and finally exit", func() {
+			mockHttpBase.Exit()
+			Eventually(func() bool {
+				vnode := &v1.Node{}
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name: utils.FormatNodeName(httpNodeID, env),
+				}, vnode)
+				return errors.IsNotFound(err)
+			}, time.Second*50, time.Second).Should(BeTrue())
+		})
+
+		It("base should become a ready vnode eventually", func() {
+			time.Sleep(time.Second)
+
+			go mockHttpBase.Start(ctx, clientID)
+			vnode := &v1.Node{}
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name: utils.FormatNodeName(httpNodeID, env),
+				}, vnode)
+				vnodeReady := false
+				for _, cond := range vnode.Status.Conditions {
+					if cond.Type == v1.NodeReady {
+						vnodeReady = cond.Status == v1.ConditionTrue
+						break
+					}
+				}
+				return err == nil && vnodeReady
+			}, time.Second*50, time.Second).Should(BeTrue())
+		})
+
+		It("base unreachable finally exit", func() {
+			mockHttpBase.reachable = false
+			Eventually(func() bool {
+				vnode := &v1.Node{}
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name: utils.FormatNodeName(httpNodeID, env),
+				}, vnode)
+				return errors.IsNotFound(err)
+			}, time.Minute*2, time.Second).Should(BeTrue())
+		})
+
+		It("reachable base should become a ready vnode eventually", func() {
+			time.Sleep(time.Second)
+			mockHttpBase.reachable = true
+			go mockHttpBase.Start(ctx, clientID)
+			vnode := &v1.Node{}
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name: utils.FormatNodeName(httpNodeID, env),
+				}, vnode)
+				vnodeReady := false
+				for _, cond := range vnode.Status.Conditions {
+					if cond.Type == v1.NodeReady {
+						vnodeReady = cond.Status == v1.ConditionTrue
+						break
+					}
+				}
+				return err == nil && vnodeReady
+			}, time.Second*50, time.Second).Should(BeTrue())
+		})
+
+		It("base finally exit", func() {
+			mockHttpBase.Exit()
+
+			Eventually(func() bool {
+				vnode := &v1.Node{}
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name: utils.FormatNodeName(httpNodeID, env),
+				}, vnode)
+				return errors.IsNotFound(err)
+			}, time.Second*50, time.Second).Should(BeTrue())
+
+		})
+	})
+})
diff --git a/suite/mock_http_base.go b/suite/http/mock_http_base.go
similarity index 78%
rename from suite/mock_http_base.go
rename to suite/http/mock_http_base.go
index c6d2fee..0028260 100644
--- a/suite/mock_http_base.go
+++ b/suite/http/mock_http_base.go
@@ -1,4 +1,4 @@
-package suite
+package http
 
 import (
 	"bytes"
@@ -8,6 +8,8 @@ import (
 	"github.com/koupleless/arkctl/v1/service/ark"
 	"github.com/koupleless/module_controller/common/model"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_http_tunnel/ark_service"
+	"github.com/koupleless/virtual-kubelet/common/log"
+	"github.com/koupleless/virtual-kubelet/common/utils"
 	"github.com/sirupsen/logrus"
 	"io"
 	"net/http"
@@ -17,30 +19,29 @@ import (
 
 type MockHttpBase struct {
 	sync.Mutex
-	ID        string
 	Env       string
 	CurrState string
-	Metadata  model.Metadata
+	Metadata  model.BaseMetadata
 	port      int
 	BizInfos  map[string]ark.ArkBizInfo
 	Baseline  []ark.BizModel
 
 	exit      chan struct{}
-	reachable chan struct{}
+	reachable bool
 }
 
-func NewMockHttpBase(name, version, id, env string, port int) *MockHttpBase {
+func NewMockHttpBase(name, clusterName, version, env string, port int) *MockHttpBase {
 	return &MockHttpBase{
-		ID:        id,
 		Env:       env,
 		CurrState: "ACTIVATED",
-		Metadata: model.Metadata{
-			Name:    name,
-			Version: version,
+		Metadata: model.BaseMetadata{
+			Identity:    name,
+			ClusterName: clusterName,
+			Version:     version,
 		},
 		BizInfos:  make(map[string]ark.ArkBizInfo),
 		exit:      make(chan struct{}),
-		reachable: make(chan struct{}),
+		reachable: true,
 		port:      port,
 	}
 }
@@ -53,19 +54,10 @@ func (b *MockHttpBase) Exit() {
 	}
 }
 
-func (b *MockHttpBase) Unreachable() {
-	b.reachable = make(chan struct{})
-}
-
-func (b *MockHttpBase) Start(ctx context.Context) error {
-	select {
-	case <-b.reachable:
-	default:
-		close(b.reachable)
-	}
+func (b *MockHttpBase) Start(ctx context.Context, clientID string) error {
 	b.exit = make(chan struct{})
 	b.CurrState = "ACTIVATED"
-	// start a http server to mock arklet
+	// start a http server to mock base
 	mux := http.NewServeMux()
 
 	server := http.Server{
@@ -74,30 +66,27 @@ func (b *MockHttpBase) Start(ctx context.Context) error {
 	}
 
 	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
-		select {
-		case <-b.reachable:
+		if b.reachable {
 			w.WriteHeader(http.StatusOK)
 			w.Header().Set("Content-Type", "application/json")
 			w.Write(b.getHealthMsg())
-		default:
+		} else {
 			w.WriteHeader(http.StatusBadGateway)
 		}
 	})
 
 	mux.HandleFunc("/queryAllBiz", func(w http.ResponseWriter, r *http.Request) {
-		select {
-		case <-b.reachable:
+		if b.reachable {
 			w.WriteHeader(http.StatusOK)
 			w.Header().Set("Content-Type", "application/json")
 			w.Write(b.getQueryAllBizMsg())
-		default:
+		} else {
 			w.WriteHeader(http.StatusBadGateway)
 		}
 	})
 
 	mux.HandleFunc("/installBiz", func(w http.ResponseWriter, r *http.Request) {
-		select {
-		case <-b.reachable:
+		if b.reachable {
 			w.WriteHeader(http.StatusOK)
 			w.Header().Set("Content-Type", "application/json")
 
@@ -109,15 +98,14 @@ func (b *MockHttpBase) Start(ctx context.Context) error {
 			}
 
 			w.Write(b.processInstallBiz(body))
-		default:
+		} else {
 			w.WriteHeader(http.StatusBadGateway)
 		}
 
 	})
 
 	mux.HandleFunc("/uninstallBiz", func(w http.ResponseWriter, r *http.Request) {
-		select {
-		case <-b.reachable:
+		if b.reachable {
 			w.WriteHeader(http.StatusOK)
 			w.Header().Set("Content-Type", "application/json")
 
@@ -129,13 +117,24 @@ func (b *MockHttpBase) Start(ctx context.Context) error {
 			}
 
 			w.Write(b.processUnInstallBiz(body))
-		default:
+		} else {
 			w.WriteHeader(http.StatusBadGateway)
 		}
 	})
 
 	go server.ListenAndServe()
 
+	// Start a new goroutine to upload node heart beat data every 10 seconds
+	go utils.TimedTaskWithInterval(ctx, time.Second*10, func(ctx context.Context) {
+		if b.reachable {
+			log.G(ctx).Info("upload node heart beat data from node ", b.Metadata.Identity)
+			_, err := http.Post("http://127.0.0.1:7777/heartbeat", "application/json", bytes.NewBuffer(b.getHeartBeatMsg()))
+			if err != nil {
+				logrus.Errorf("error calling heartbeat: %s", err)
+			}
+		}
+	})
+
 	_, err := http.Post("http://127.0.0.1:7777/heartbeat", "application/json", bytes.NewBuffer(b.getHeartBeatMsg()))
 	if err != nil {
 		logrus.Errorf("error calling heartbeat: %s", err)
@@ -147,9 +146,10 @@ func (b *MockHttpBase) Start(ctx context.Context) error {
 		case <-ctx.Done():
 		case <-b.exit:
 		}
-		server.Shutdown(ctx)
 		b.CurrState = "DEACTIVATED"
 		_, err = http.Post("http://127.0.0.1:7777/heartbeat", "application/json", bytes.NewBuffer(b.getHeartBeatMsg()))
+		time.Sleep(2 * time.Second)
+		server.Shutdown(ctx)
 		if err != nil {
 			logrus.Errorf("error calling heartbeat: %s", err)
 		}
@@ -159,15 +159,12 @@ func (b *MockHttpBase) Start(ctx context.Context) error {
 }
 
 func (b *MockHttpBase) getHeartBeatMsg() []byte {
-	msg := model.HeartBeatData{
-		BaseID:        b.ID,
+	msg := model.BaseStatus{
+		BaseMetadata:  b.Metadata,
+		LocalIP:       "127.0.0.1",
+		LocalHostName: "localhost",
+		Port:          b.port,
 		State:         b.CurrState,
-		MasterBizInfo: b.Metadata,
-		NetworkInfo: model.NetworkInfo{
-			LocalIP:       "127.0.0.1",
-			LocalHostName: "localhost",
-			ArkletPort:    b.port,
-		},
 	}
 	msgBytes, _ := json.Marshal(msg)
 	return msgBytes
@@ -185,7 +182,7 @@ func (b *MockHttpBase) getHealthMsg() []byte {
 						JavaCommittedMetaspace: 1024,
 					},
 					MasterBizInfo: ark.MasterBizInfo{
-						BizName:    b.Metadata.Name,
+						BizName:    b.Metadata.Identity,
 						BizState:   b.CurrState,
 						BizVersion: b.Metadata.Version,
 					},
@@ -193,7 +190,6 @@ func (b *MockHttpBase) getHealthMsg() []byte {
 			},
 			Message: "",
 		},
-		BaseID: b.ID,
 	}
 	msgBytes, _ := json.Marshal(msg)
 	return msgBytes
@@ -213,7 +209,6 @@ func (b *MockHttpBase) getQueryAllBizMsg() []byte {
 			Data:    arkBizInfos,
 			Message: "",
 		},
-		BaseID: b.ID,
 	}
 	msgBytes, _ := json.Marshal(msg)
 	return msgBytes
@@ -250,7 +245,7 @@ func (b *MockHttpBase) processInstallBiz(msg []byte) []byte {
 		},
 		Message:         "",
 		ErrorStackTrace: "",
-		BaseID:          b.ID,
+		BaseIdentity:    b.Metadata.Identity,
 	}
 	respBytes, _ := json.Marshal(response)
 	return respBytes
@@ -275,8 +270,12 @@ func (b *MockHttpBase) processUnInstallBiz(msg []byte) []byte {
 		},
 		Message:         "",
 		ErrorStackTrace: "",
-		BaseID:          b.ID,
+		BaseIdentity:    b.Metadata.Identity,
 	}
 	respBytes, _ := json.Marshal(response)
 	return respBytes
 }
+
+func getBizIdentity(bizModel ark.BizModel) string {
+	return fmt.Sprintf("%s:%s", bizModel.BizName, bizModel.BizVersion)
+}
diff --git a/suite/module_http_lifecycle_test.go b/suite/http/module_http_lifecycle_test.go
similarity index 89%
rename from suite/module_http_lifecycle_test.go
rename to suite/http/module_http_lifecycle_test.go
index f67e6db..c2ae966 100644
--- a/suite/module_http_lifecycle_test.go
+++ b/suite/http/module_http_lifecycle_test.go
@@ -1,4 +1,4 @@
-package suite
+package http
 
 import (
 	"context"
@@ -16,15 +16,17 @@ var _ = Describe("Module Lifecycle Test", func() {
 	ctx := context.Background()
 
 	nodeID := "test-http-base"
-	mockBase := NewMockHttpBase("test-base", "1.0.0", nodeID, env, 1238)
+	clusterName := "test-cluster-name"
+
+	mockBase := NewMockHttpBase(nodeID, clusterName, "1.0.0", env, 1238)
 
 	mockModulePod := prepareModulePod("test-module", "default", utils.FormatNodeName(nodeID, env))
 
 	Context("pod install", func() {
 		It("base should become a ready vnode eventually", func() {
-			go mockBase.Start(ctx)
-			vnode := &v1.Node{}
+			go mockBase.Start(ctx, clientID)
 			Eventually(func() bool {
+				vnode := &v1.Node{}
 				err := k8sClient.Get(ctx, types.NamespacedName{
 					Name: utils.FormatNodeName(nodeID, env),
 				}, vnode)
@@ -36,7 +38,7 @@ var _ = Describe("Module Lifecycle Test", func() {
 					}
 				}
 				return err == nil && vnodeReady
-			}, time.Second*20, time.Second).Should(BeTrue())
+			}, time.Second*50, time.Second).Should(BeTrue())
 		})
 
 		It("publish a module pod and it should be running", func() {
@@ -49,7 +51,7 @@ var _ = Describe("Module Lifecycle Test", func() {
 					Name:      mockModulePod.Name,
 				}, podFromKubernetes)
 				return err == nil && podFromKubernetes.Status.Phase == v1.PodRunning
-			}, time.Second*20, time.Second).Should(BeTrue())
+			}, time.Second*50, time.Second).Should(BeTrue())
 			Eventually(func() bool {
 				return len(mockBase.BizInfos) == 1
 			}, time.Second*20, time.Second).Should(BeTrue())
diff --git a/suite/http/suite_test.go b/suite/http/suite_test.go
new file mode 100644
index 0000000..bfac8bf
--- /dev/null
+++ b/suite/http/suite_test.go
@@ -0,0 +1,165 @@
+package http
+
+import (
+	"context"
+	"fmt"
+	model2 "github.com/koupleless/module_controller/common/model"
+	"github.com/koupleless/module_controller/controller/module_deployment_controller"
+	"github.com/koupleless/module_controller/module_tunnels/koupleless_http_tunnel"
+	"github.com/koupleless/virtual-kubelet/common/log"
+	logruslogger "github.com/koupleless/virtual-kubelet/common/log/logrus"
+	"github.com/koupleless/virtual-kubelet/model"
+	"github.com/koupleless/virtual-kubelet/vnode_controller"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"github.com/sirupsen/logrus"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/rest"
+	"os"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
+	"testing"
+	"time"
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var cfg *rest.Config
+var testEnv *envtest.Environment
+var k8sClient client.Client
+var httpTunnel koupleless_http_tunnel.HttpTunnel
+var ctx, cancel = context.WithCancel(context.Background())
+
+const (
+	clientID = "suite-test"
+	env      = "suite"
+)
+
+func TestControllers(t *testing.T) {
+	RegisterFailHandler(Fail)
+
+	RunSpecs(t, "Module Controller Suite")
+}
+
+var _ = BeforeSuite(func() {
+
+	os.Setenv("ENABLE_MODULE_REPLICAS_SYNC_WITH_BASE", "true")
+
+	logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
+	log.L = logruslogger.FromLogrus(logrus.NewEntry(logrus.StandardLogger()))
+
+	By("bootstrapping suite environment")
+	testEnv = &envtest.Environment{}
+
+	var err error
+
+	cfg, err = testEnv.Start()
+	Expect(err).NotTo(HaveOccurred())
+	Expect(cfg).NotTo(BeNil())
+
+	// Allow all connections.
+	err = scheme.AddToScheme(scheme.Scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
+		Scheme: scheme.Scheme,
+		Metrics: metricsserver.Options{
+			BindAddress: ":8081",
+		},
+	})
+	Expect(err).ToNot(HaveOccurred())
+
+	moduleDeploymentController, err := module_deployment_controller.NewModuleDeploymentController(env)
+	Expect(err).ToNot(HaveOccurred())
+
+	err = moduleDeploymentController.SetupWithManager(ctx, k8sManager)
+
+	Expect(err).ToNot(HaveOccurred())
+
+	httpTunnel = koupleless_http_tunnel.NewHttpTunnel(env, k8sManager.GetClient(), moduleDeploymentController, 7777)
+
+	err = httpTunnel.Start(ctx, clientID, env)
+	if err != nil {
+		log.G(ctx).WithError(err).Error("failed to start tunnel", httpTunnel.Key())
+		panic(fmt.Sprintf("failed to start tunnel %s", httpTunnel.Key()))
+	} else {
+		log.G(ctx).Info("Tunnel started: ", httpTunnel.Key())
+	}
+
+	vnodeController, err := vnode_controller.NewVNodeController(&model.BuildVNodeControllerConfig{
+		ClientID:       clientID,
+		Env:            env,
+		VPodType:       model2.ComponentModule,
+		VNodeWorkerNum: 4,
+	}, &httpTunnel)
+	Expect(err).ToNot(HaveOccurred())
+
+	err = vnodeController.SetupWithManager(ctx, k8sManager)
+
+	k8sClient = k8sManager.GetClient()
+	Expect(k8sClient).ToNot(BeNil())
+
+	go func() {
+		err = k8sManager.Start(ctx)
+		log.G(ctx).WithError(err).Error("k8sManager Start")
+		Expect(err).ToNot(HaveOccurred())
+	}()
+
+	k8sManager.GetCache().WaitForCacheSync(ctx)
+})
+
+var _ = AfterSuite(func() {
+	By("tearing down the suite environment")
+	cancel()
+	testEnv.Stop()
+	time.Sleep(15 * time.Second)
+	log.G(ctx).Info("suite for http stopped!")
+})
+
+func prepareModulePod(name, namespace, nodeName string) v1.Pod {
+	return v1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+			Labels: map[string]string{
+				model.LabelKeyOfComponent: model2.ComponentModule,
+			},
+		},
+		Spec: v1.PodSpec{
+			NodeName: nodeName,
+			Containers: []v1.Container{
+				{
+					Name:  "biz",
+					Image: "suite-biz.jar",
+					Env: []v1.EnvVar{
+						{
+							Name:  "BIZ_VERSION",
+							Value: "0.0.1",
+						},
+					},
+				},
+			},
+			Tolerations: []v1.Toleration{
+				{
+					Key:      model.TaintKeyOfVnode,
+					Operator: v1.TolerationOpEqual,
+					Value:    "True",
+					Effect:   v1.TaintEffectNoExecute,
+				},
+				{
+					Key:      model.TaintKeyOfEnv,
+					Operator: v1.TolerationOpEqual,
+					Value:    env,
+					Effect:   v1.TaintEffectNoExecute,
+				},
+			},
+		},
+	}
+}
diff --git a/suite/base_lifecycle_test.go b/suite/mqtt/base_mqtt_lifecycle_test.go
similarity index 54%
rename from suite/base_lifecycle_test.go
rename to suite/mqtt/base_mqtt_lifecycle_test.go
index 44ed2cd..31fa43b 100644
--- a/suite/base_lifecycle_test.go
+++ b/suite/mqtt/base_mqtt_lifecycle_test.go
@@ -1,10 +1,12 @@
-package suite
+package mqtt
 
 import (
 	"context"
 	"github.com/koupleless/virtual-kubelet/common/utils"
+	"github.com/koupleless/virtual-kubelet/model"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
+	v12 "k8s.io/api/coordination/v1"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
@@ -16,89 +18,34 @@ var _ = Describe("Base Lifecycle Test", func() {
 	ctx := context.Background()
 
 	mqttNodeID := "test-mqtt-base"
-	mockMqttBase := NewMockMqttBase("test-mqtt-base", "1.0.0", mqttNodeID, env)
-
-	httpNodeID := "test-http-base"
-	mockHttpBase := NewMockHttpBase("test-http-base", "1.0.0", httpNodeID, env, 1238)
+	clusterName := "test-cluster-name"
+	mockMqttBase := NewMockMqttBase(mqttNodeID, clusterName, "1.0.0", env)
 
 	Context("mqtt base online and deactive finally", func() {
 		It("base should become a ready vnode eventually", func() {
 			time.Sleep(time.Second)
 
-			go mockMqttBase.Start(ctx)
-			vnode := &v1.Node{}
-			Eventually(func() bool {
-				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(mqttNodeID, env),
-				}, vnode)
-				vnodeReady := false
-				for _, cond := range vnode.Status.Conditions {
-					if cond.Type == v1.NodeReady {
-						vnodeReady = cond.Status == v1.ConditionTrue
-						break
-					}
-				}
-				return err == nil && vnodeReady
-			}, time.Second*50, time.Second).Should(BeTrue())
-		})
+			go mockMqttBase.Start(ctx, clientID)
 
-		It("base offline with deactive message and finally exit", func() {
-			mockMqttBase.Exit()
 			Eventually(func() bool {
-				vnode := &v1.Node{}
+				lease := &v12.Lease{}
 				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(mqttNodeID, env),
-				}, vnode)
-				return errors.IsNotFound(err)
-			}, time.Second*50, time.Second).Should(BeTrue())
-		})
-	})
+					Name:      utils.FormatNodeName(mqttNodeID, env),
+					Namespace: v1.NamespaceNodeLease,
+				}, lease)
 
-	Context("mqtt base online and unreachable finally", func() {
-		It("base should become a ready vnode eventually", func() {
-			time.Sleep(time.Second)
+				isLeader := err == nil &&
+					*lease.Spec.HolderIdentity == clientID &&
+					!time.Now().After(lease.Spec.RenewTime.Time.Add(time.Second*model.NodeLeaseDurationSeconds))
 
-			go mockMqttBase.Start(ctx)
-			vnode := &v1.Node{}
-			Eventually(func() bool {
-				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(mqttNodeID, env),
-				}, vnode)
-				vnodeReady := false
-				for _, cond := range vnode.Status.Conditions {
-					if cond.Type == v1.NodeReady {
-						vnodeReady = cond.Status == v1.ConditionTrue
-						break
-					}
-				}
-				return err == nil && vnodeReady
+				return isLeader
 			}, time.Second*50, time.Second).Should(BeTrue())
-		})
 
-		It("base unreachable finally exit", func() {
-			mockMqttBase.Unreachable()
 			Eventually(func() bool {
 				vnode := &v1.Node{}
 				err := k8sClient.Get(ctx, types.NamespacedName{
 					Name: utils.FormatNodeName(mqttNodeID, env),
 				}, vnode)
-				return errors.IsNotFound(err)
-			}, time.Second*50, time.Second).Should(BeTrue())
-
-			mockMqttBase.Exit()
-		})
-	})
-
-	Context("http base online and deactive finally", func() {
-		It("base should become a ready vnode eventually", func() {
-			time.Sleep(time.Second)
-
-			go mockHttpBase.Start(ctx)
-			vnode := &v1.Node{}
-			Eventually(func() bool {
-				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(httpNodeID, env),
-				}, vnode)
 				vnodeReady := false
 				for _, cond := range vnode.Status.Conditions {
 					if cond.Type == v1.NodeReady {
@@ -111,26 +58,26 @@ var _ = Describe("Base Lifecycle Test", func() {
 		})
 
 		It("base offline with deactive message and finally exit", func() {
-			mockHttpBase.Exit()
+			mockMqttBase.Exit()
 			Eventually(func() bool {
 				vnode := &v1.Node{}
 				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(httpNodeID, env),
+					Name: utils.FormatNodeName(mqttNodeID, env),
 				}, vnode)
 				return errors.IsNotFound(err)
 			}, time.Second*50, time.Second).Should(BeTrue())
 		})
 	})
 
-	Context("http base online and unreachable finally", func() {
+	Context("mqtt base online and unreachable finally", func() {
 		It("base should become a ready vnode eventually", func() {
 			time.Sleep(time.Second)
 
-			go mockHttpBase.Start(ctx)
+			go mockMqttBase.Start(ctx, clientID)
 			vnode := &v1.Node{}
 			Eventually(func() bool {
 				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(httpNodeID, env),
+					Name: utils.FormatNodeName(mqttNodeID, env),
 				}, vnode)
 				vnodeReady := false
 				for _, cond := range vnode.Status.Conditions {
@@ -144,27 +91,25 @@ var _ = Describe("Base Lifecycle Test", func() {
 		})
 
 		It("base unreachable finally exit", func() {
-			mockHttpBase.Unreachable()
+			mockMqttBase.reachable = false
 			Eventually(func() bool {
 				vnode := &v1.Node{}
+
 				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(httpNodeID, env),
+					Name: utils.FormatNodeName(mqttNodeID, env),
 				}, vnode)
 				return errors.IsNotFound(err)
-			}, time.Second*50, time.Second).Should(BeTrue())
-
-			mockHttpBase.Exit()
+			}, time.Minute*2, time.Second).Should(BeTrue())
 		})
-	})
 
-	Context("http base online and change base id finally", func() {
-		It("base should become a ready vnode eventually", func() {
+		It("reachable base should become a ready vnode eventually", func() {
 			time.Sleep(time.Second)
-			go mockHttpBase.Start(ctx)
+			mockMqttBase.reachable = true
+			go mockMqttBase.Start(ctx, clientID)
 			vnode := &v1.Node{}
 			Eventually(func() bool {
 				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(httpNodeID, env),
+					Name: utils.FormatNodeName(mqttNodeID, env),
 				}, vnode)
 				vnodeReady := false
 				for _, cond := range vnode.Status.Conditions {
@@ -177,18 +122,15 @@ var _ = Describe("Base Lifecycle Test", func() {
 			}, time.Second*50, time.Second).Should(BeTrue())
 		})
 
-		It("base id changed finally exit", func() {
-			mockHttpBase.ID = "changed-base-id"
+		It("base offline with deactive message and finally exit", func() {
+			mockMqttBase.Exit()
 			Eventually(func() bool {
 				vnode := &v1.Node{}
 				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(httpNodeID, env),
+					Name: utils.FormatNodeName(mqttNodeID, env),
 				}, vnode)
 				return errors.IsNotFound(err)
 			}, time.Second*50, time.Second).Should(BeTrue())
-
-			mockHttpBase.Exit()
 		})
 	})
-
 })
diff --git a/suite/mock_mqtt_base.go b/suite/mqtt/mock_mqtt_base.go
similarity index 73%
rename from suite/mock_mqtt_base.go
rename to suite/mqtt/mock_mqtt_base.go
index 0780176..baa15bf 100644
--- a/suite/mock_mqtt_base.go
+++ b/suite/mqtt/mock_mqtt_base.go
@@ -1,4 +1,4 @@
-package suite
+package mqtt
 
 import (
 	"context"
@@ -9,6 +9,8 @@ import (
 	"github.com/koupleless/module_controller/common/model"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_http_tunnel/ark_service"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_mqtt_tunnel/mqtt"
+	"github.com/koupleless/virtual-kubelet/common/log"
+	"github.com/koupleless/virtual-kubelet/common/utils"
 	"strings"
 	"sync"
 	"time"
@@ -16,30 +18,29 @@ import (
 
 type MockMQTTBase struct {
 	sync.Mutex
-	ID        string
-	Env       string
-	CurrState string
-	Metadata  model.Metadata
-	BizInfos  map[string]ark.ArkBizInfo
-	Baseline  []ark.BizModel
-	client    *mqtt.Client
+	Env          string
+	CurrState    string
+	BaseMetadata model.BaseMetadata
+	BizInfos     map[string]ark.ArkBizInfo
+	Baseline     []ark.BizModel
+	client       *mqtt.Client
 
 	exit      chan struct{}
-	reachable chan struct{}
+	reachable bool
 }
 
-func NewMockMqttBase(name, version, id, env string) *MockMQTTBase {
+func NewMockMqttBase(baseName, clusterName, version, env string) *MockMQTTBase {
 	return &MockMQTTBase{
-		ID:        id,
 		Env:       env,
 		CurrState: "ACTIVATED",
-		Metadata: model.Metadata{
-			Name:    name,
-			Version: version,
+		BaseMetadata: model.BaseMetadata{
+			Identity:    baseName,
+			ClusterName: clusterName,
+			Version:     version,
 		},
 		BizInfos:  make(map[string]ark.ArkBizInfo),
 		exit:      make(chan struct{}),
-		reachable: make(chan struct{}),
+		reachable: true,
 	}
 }
 
@@ -51,28 +52,19 @@ func (b *MockMQTTBase) Exit() {
 	}
 }
 
-func (b *MockMQTTBase) Unreachable() {
-	b.reachable = make(chan struct{})
-}
-
-func (b *MockMQTTBase) Start(ctx context.Context) error {
-	select {
-	case <-b.reachable:
-	default:
-		close(b.reachable)
-	}
+func (b *MockMQTTBase) Start(ctx context.Context, clientID string) error {
 	b.exit = make(chan struct{})
 	b.CurrState = "ACTIVATED"
 	var err error
 	b.client, err = mqtt.NewMqttClient(&mqtt.ClientConfig{
 		Broker:   "localhost",
 		Port:     1883,
-		ClientID: b.ID,
+		ClientID: clientID,
 		Username: "test",
 		Password: "",
 		OnConnectHandler: func(client paho.Client) {
-			client.Subscribe(fmt.Sprintf("koupleless_%s/%s/+", b.Env, b.ID), 1, b.processCommand)
-			client.Subscribe(fmt.Sprintf("koupleless_%s/%s/base/baseline", b.Env, b.ID), 1, b.processBaseline)
+			client.Subscribe(fmt.Sprintf("koupleless_%s/%s/+", b.Env, b.BaseMetadata.Identity), 1, b.processCommand)
+			client.Subscribe(fmt.Sprintf("koupleless_%s/%s/base/baseline", b.Env, b.BaseMetadata.Identity), 1, b.processBaseline)
 		},
 	})
 	if err != nil {
@@ -81,10 +73,16 @@ func (b *MockMQTTBase) Start(ctx context.Context) error {
 
 	b.client.Connect()
 
-	go func() {
-		// send heart beat message
-		b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.ID), 1, b.getHeartBeatMsg())
-	}()
+	// Start a new goroutine to upload node heart beat data every 10 seconds
+	go utils.TimedTaskWithInterval(ctx, time.Second*10, func(ctx context.Context) {
+		if b.reachable {
+			log.G(ctx).Info("upload node heart beat data from node ", b.BaseMetadata.Identity)
+			b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.BaseMetadata.Identity), 1, b.getHeartBeatMsg())
+		}
+	})
+
+	// send heart beat message
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.BaseMetadata.Identity), 1, b.getHeartBeatMsg())
 
 	select {
 	case <-b.exit:
@@ -98,19 +96,17 @@ func (b *MockMQTTBase) Start(ctx context.Context) error {
 func (b *MockMQTTBase) SetCurrState(state string) {
 	b.CurrState = state
 	// send heart beat message
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.ID), 1, b.getHeartBeatMsg())
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.BaseMetadata.Identity), 1, b.getHeartBeatMsg())
 }
 
 func (b *MockMQTTBase) getHeartBeatMsg() []byte {
-	msg := model.ArkMqttMsg[model.HeartBeatData]{
+	msg := model.ArkMqttMsg[model.BaseStatus]{
 		PublishTimestamp: time.Now().UnixMilli(),
-		Data: model.HeartBeatData{
+		Data: model.BaseStatus{
+			BaseMetadata:  b.BaseMetadata,
+			LocalIP:       "127.0.0.1",
+			LocalHostName: "localhost",
 			State:         b.CurrState,
-			MasterBizInfo: b.Metadata,
-			NetworkInfo: model.NetworkInfo{
-				LocalIP:       "127.0.0.1",
-				LocalHostName: "localhost",
-			},
 		},
 	}
 	msgBytes, _ := json.Marshal(msg)
@@ -131,9 +127,9 @@ func (b *MockMQTTBase) getHealthMsg() []byte {
 							JavaCommittedMetaspace: 1024,
 						},
 						MasterBizInfo: ark.MasterBizInfo{
-							BizName:    b.Metadata.Name,
+							BizName:    b.BaseMetadata.Identity,
 							BizState:   b.CurrState,
-							BizVersion: b.Metadata.Version,
+							BizVersion: b.BaseMetadata.Version,
 						},
 					},
 				},
@@ -169,8 +165,7 @@ func (b *MockMQTTBase) getQueryAllBizMsg() []byte {
 
 func (b *MockMQTTBase) processCommand(_ paho.Client, msg paho.Message) {
 	defer msg.Ack()
-	select {
-	case <-b.reachable:
+	if b.reachable {
 		split := strings.Split(msg.Topic(), "/")
 		command := split[len(split)-1]
 		switch command {
@@ -183,8 +178,6 @@ func (b *MockMQTTBase) processCommand(_ paho.Client, msg paho.Message) {
 		case model.CommandQueryAllBiz:
 			go b.processQueryAllBiz()
 		}
-	default:
-		return
 	}
 }
 
@@ -205,7 +198,7 @@ func (b *MockMQTTBase) processBaseline(_ paho.Client, msg paho.Message) {
 }
 
 func (b *MockMQTTBase) processHealth() {
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.ID), 1, b.getHealthMsg())
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.BaseMetadata.Identity), 1, b.getHealthMsg())
 }
 
 func (b *MockMQTTBase) processInstallBiz(msg []byte) {
@@ -250,34 +243,34 @@ func (b *MockMQTTBase) processUnInstallBiz(msg []byte) {
 		},
 	}
 	respBytes, _ := json.Marshal(resp)
-	b.client.Pub(fmt.Sprintf(model.BaseBizOperationResponseTopic, b.Env, b.ID), 1, respBytes)
+	b.client.Pub(fmt.Sprintf(model.BaseBizOperationResponseTopic, b.Env, b.BaseMetadata.Identity), 1, respBytes)
 }
 
 func (b *MockMQTTBase) SendInvalidMessage() {
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.ID), 1, []byte(""))
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.ID), 1, []byte(""))
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.ID), 1, []byte(""))
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/queryBaseline", b.Env, b.ID), 1, []byte(""))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.BaseMetadata.Identity), 1, []byte(""))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.BaseMetadata.Identity), 1, []byte(""))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.BaseMetadata.Identity), 1, []byte(""))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/queryBaseline", b.Env, b.BaseMetadata.Identity), 1, []byte(""))
 }
 
 func (b *MockMQTTBase) SendTimeoutMessage() {
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.ID), 1, []byte("{\"publishTimestamp\":0}"))
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.ID), 1, []byte("{\"publishTimestamp\":0}"))
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.ID), 1, []byte("{\"publishTimestamp\":0}"))
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/queryBaseline", b.Env, b.ID), 1, []byte("{\"publishTimestamp\":0}"))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.BaseMetadata.Identity), 1, []byte("{\"publishTimestamp\":0}"))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.BaseMetadata.Identity), 1, []byte("{\"publishTimestamp\":0}"))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/heart", b.Env, b.BaseMetadata.Identity), 1, []byte("{\"publishTimestamp\":0}"))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/queryBaseline", b.Env, b.BaseMetadata.Identity), 1, []byte("{\"publishTimestamp\":0}"))
 }
 
 func (b *MockMQTTBase) SendFailedMessage() {
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.ID), 1, []byte(fmt.Sprintf("{\"publishTimestamp\":%d, \"data\" : {\"code\":\"\"}}", time.Now().UnixMilli())))
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.ID), 1, []byte(fmt.Sprintf("{\"publishTimestamp\":%d, \"data\" : {\"code\":\"\"}}", time.Now().UnixMilli())))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/health", b.Env, b.BaseMetadata.Identity), 1, []byte(fmt.Sprintf("{\"publishTimestamp\":%d, \"data\" : {\"code\":\"\"}}", time.Now().UnixMilli())))
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.BaseMetadata.Identity), 1, []byte(fmt.Sprintf("{\"publishTimestamp\":%d, \"data\" : {\"code\":\"\"}}", time.Now().UnixMilli())))
 }
 
 func (b *MockMQTTBase) QueryBaseline() {
-	queryBaselineBytes, _ := json.Marshal(model.ArkMqttMsg[model.Metadata]{
+	queryBaselineBytes, _ := json.Marshal(model.ArkMqttMsg[model.BaseMetadata]{
 		PublishTimestamp: time.Now().UnixMilli(),
-		Data:             b.Metadata,
+		Data:             b.BaseMetadata,
 	})
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/queryBaseline", b.Env, b.ID), 1, queryBaselineBytes)
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/queryBaseline", b.Env, b.BaseMetadata.Identity), 1, queryBaselineBytes)
 }
 
 func (b *MockMQTTBase) SetBizState(bizIdentity, state, reason, message string) {
@@ -312,7 +305,7 @@ func (b *MockMQTTBase) SetBizState(bizIdentity, state, reason, message string) {
 			},
 		}
 		respBytes, _ := json.Marshal(resp)
-		b.client.Pub(fmt.Sprintf(model.BaseBizOperationResponseTopic, b.Env, b.ID), 1, respBytes)
+		b.client.Pub(fmt.Sprintf(model.BaseBizOperationResponseTopic, b.Env, b.BaseMetadata.Identity), 1, respBytes)
 	}
 	// send simple all biz data
 	arkBizInfos := make(model.ArkSimpleAllBizInfoData, 0)
@@ -334,11 +327,11 @@ func (b *MockMQTTBase) SetBizState(bizIdentity, state, reason, message string) {
 		Data:             arkBizInfos,
 	}
 	msgBytes, _ := json.Marshal(msg)
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/simpleBiz", b.Env, b.ID), 1, msgBytes)
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/simpleBiz", b.Env, b.BaseMetadata.Identity), 1, msgBytes)
 }
 
 func (b *MockMQTTBase) processQueryAllBiz() {
-	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.ID), 1, b.getQueryAllBizMsg())
+	b.client.Pub(fmt.Sprintf("koupleless_%s/%s/base/biz", b.Env, b.BaseMetadata.Identity), 1, b.getQueryAllBizMsg())
 }
 
 func getBizIdentity(bizModel ark.BizModel) string {
diff --git a/suite/module_deployment_controller_suite_test.go b/suite/mqtt/module_deployment_controller_suite_test.go
similarity index 85%
rename from suite/module_deployment_controller_suite_test.go
rename to suite/mqtt/module_deployment_controller_suite_test.go
index 31ccdc2..9d55c84 100644
--- a/suite/module_deployment_controller_suite_test.go
+++ b/suite/mqtt/module_deployment_controller_suite_test.go
@@ -1,4 +1,4 @@
-package suite
+package mqtt
 
 import (
 	"context"
@@ -18,8 +18,10 @@ var _ = Describe("Module Deployment Controller Test", func() {
 
 	ctx := context.Background()
 
-	mockBase := NewMockMqttBase("test-base", "1.0.0", "test-base", env)
-	mockBase2 := NewMockMqttBase("test-base", "1.0.0", "test-base-2", env)
+	clusterName := "test-cluster-name"
+
+	mockBase := NewMockMqttBase("test-base", clusterName, "1.0.0", env)
+	mockBase2 := NewMockMqttBase("test-base-2", clusterName, "1.0.0", env)
 
 	deployment1 := appsv1.Deployment{
 		ObjectMeta: metav1.ObjectMeta{
@@ -50,7 +52,7 @@ var _ = Describe("Module Deployment Controller Test", func() {
 					Containers: []v1.Container{
 						{
 							Name:  "test-biz",
-							Image: "test-biz",
+							Image: "test-biz.jar",
 							Env: []v1.EnvVar{
 								{
 									Name:  "BIZ_VERSION",
@@ -60,9 +62,9 @@ var _ = Describe("Module Deployment Controller Test", func() {
 						},
 					},
 					NodeSelector: map[string]string{
-						model.LabelKeyOfTechStack:      "java",
-						vkModel.LabelKeyOfVNodeName:    "test-base",
-						vkModel.LabelKeyOfVNodeVersion: "1.0.0",
+						model.LabelKeyOfTechStack:          "java",
+						vkModel.LabelKeyOfVNodeVersion:     "1.0.0",
+						vkModel.LabelKeyOfVNodeClusterName: clusterName,
 					},
 					Tolerations: []v1.Toleration{
 						{
@@ -112,7 +114,7 @@ var _ = Describe("Module Deployment Controller Test", func() {
 					Containers: []v1.Container{
 						{
 							Name:  "test-biz-2",
-							Image: "test-biz-2",
+							Image: "test-biz-2.jar",
 							Env: []v1.EnvVar{
 								{
 									Name:  "BIZ_VERSION",
@@ -122,9 +124,9 @@ var _ = Describe("Module Deployment Controller Test", func() {
 						},
 					},
 					NodeSelector: map[string]string{
-						model.LabelKeyOfTechStack:      "java",
-						vkModel.LabelKeyOfVNodeName:    "test-base-2",
-						vkModel.LabelKeyOfVNodeVersion: "1.0.0",
+						model.LabelKeyOfTechStack:          "java",
+						vkModel.LabelKeyOfVNodeVersion:     "1.0.0",
+						vkModel.LabelKeyOfVNodeClusterName: clusterName + "-2",
 					},
 					Tolerations: []v1.Toleration{
 						{
@@ -160,7 +162,7 @@ var _ = Describe("Module Deployment Controller Test", func() {
 		})
 
 		It("one node online and deployment replicas should be 1", func() {
-			go mockBase.Start(ctx)
+			go mockBase.Start(ctx, clientID)
 			Eventually(func() bool {
 				depFromKubernetes := &appsv1.Deployment{}
 				err := k8sClient.Get(ctx, types.NamespacedName{
@@ -172,7 +174,7 @@ var _ = Describe("Module Deployment Controller Test", func() {
 		})
 
 		It("another node online and deployment replicas should be 2", func() {
-			go mockBase2.Start(ctx)
+			go mockBase2.Start(ctx, clientID)
 			Eventually(func() bool {
 				depFromKubernetes := &appsv1.Deployment{}
 				err := k8sClient.Get(ctx, types.NamespacedName{
@@ -200,7 +202,19 @@ var _ = Describe("Module Deployment Controller Test", func() {
 			mockBase2.QueryBaseline()
 			Eventually(func() bool {
 				return len(mockBase2.Baseline) == 1
-			}, time.Second*10, time.Second).Should(BeTrue())
+			}, time.Second*20, time.Second).Should(BeTrue())
+		})
+
+		It("replicas should be 2", func() {
+			mockBase2.Exit()
+			Eventually(func() bool {
+				depFromKubernetes := &appsv1.Deployment{}
+				err := k8sClient.Get(ctx, types.NamespacedName{
+					Name:      deployment1.Name,
+					Namespace: deployment1.Namespace,
+				}, depFromKubernetes)
+				return err == nil && *depFromKubernetes.Spec.Replicas == 2
+			}, time.Second*20, time.Second).Should(BeTrue())
 		})
 
 		It("mock base 2 exit and replicas should be 1 finally", func() {
@@ -224,7 +238,7 @@ var _ = Describe("Module Deployment Controller Test", func() {
 					Namespace: deployment1.Namespace,
 				}, depFromKubernetes)
 				return err == nil && *depFromKubernetes.Spec.Replicas == 0
-			}, time.Second*20, time.Second).Should(BeTrue())
+			}, time.Minute*2, time.Second).Should(BeTrue())
 		})
 	})
 })
diff --git a/suite/module_mqtt_lifecycle_test.go b/suite/mqtt/module_mqtt_lifecycle_test.go
similarity index 89%
rename from suite/module_mqtt_lifecycle_test.go
rename to suite/mqtt/module_mqtt_lifecycle_test.go
index 3cc32e8..8bf995c 100644
--- a/suite/module_mqtt_lifecycle_test.go
+++ b/suite/mqtt/module_mqtt_lifecycle_test.go
@@ -1,7 +1,8 @@
-package suite
+package mqtt
 
 import (
 	"context"
+	"github.com/koupleless/virtual-kubelet/common/log"
 	"github.com/koupleless/virtual-kubelet/common/utils"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -16,18 +17,24 @@ var _ = Describe("Module Lifecycle Test", func() {
 	ctx := context.Background()
 
 	nodeID := "test-base"
-	mockBase := NewMockMqttBase("test-base", "1.0.0", nodeID, env)
+	clusterName := "test-cluster-name"
+	nodeName := utils.FormatNodeName(nodeID, env)
+	mockBase := NewMockMqttBase(nodeID, clusterName, "1.0.0", env)
 
-	mockModulePod := prepareModulePod("test-module", "default", utils.FormatNodeName(nodeID, env))
+	mockModulePod := prepareModulePod("test-module", "default", nodeName)
 
 	Context("pod install", func() {
 		It("base should become a ready vnode eventually", func() {
-			go mockBase.Start(ctx)
+			go mockBase.Start(ctx, clientID)
 			vnode := &v1.Node{}
 			Eventually(func() bool {
 				err := k8sClient.Get(ctx, types.NamespacedName{
-					Name: utils.FormatNodeName(nodeID, env),
+					Name: nodeName,
 				}, vnode)
+				if err != nil {
+					log.G(ctx).Error(err, "get vnode error")
+					return false
+				}
 				vnodeReady := false
 				for _, cond := range vnode.Status.Conditions {
 					if cond.Type == v1.NodeReady {
diff --git a/suite/suite_test.go b/suite/mqtt/suite_test.go
similarity index 80%
rename from suite/suite_test.go
rename to suite/mqtt/suite_test.go
index 68a0011..f16e280 100644
--- a/suite/suite_test.go
+++ b/suite/mqtt/suite_test.go
@@ -1,17 +1,15 @@
-package suite
+package mqtt
 
 import (
 	"context"
+	"fmt"
 	model2 "github.com/koupleless/module_controller/common/model"
 	"github.com/koupleless/module_controller/controller/module_deployment_controller"
-	"github.com/koupleless/module_controller/module_tunnels"
-	"github.com/koupleless/module_controller/module_tunnels/koupleless_http_tunnel"
 	"github.com/koupleless/module_controller/module_tunnels/koupleless_mqtt_tunnel"
 	"github.com/koupleless/virtual-kubelet/common/log"
 	logruslogger "github.com/koupleless/virtual-kubelet/common/log/logrus"
-	"github.com/koupleless/virtual-kubelet/controller/vnode_controller"
 	"github.com/koupleless/virtual-kubelet/model"
-	"github.com/koupleless/virtual-kubelet/tunnel"
+	"github.com/koupleless/virtual-kubelet/vnode_controller"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"github.com/sirupsen/logrus"
@@ -28,7 +26,9 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/envtest"
 	logf "sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
 	"testing"
+	"time"
 )
 
 // These tests use Ginkgo (BDD-style Go testing framework). Refer to
@@ -38,8 +38,8 @@ var cfg *rest.Config
 var testEnv *envtest.Environment
 var k8sClient client.Client
 var mqttTunnel koupleless_mqtt_tunnel.MqttTunnel
-var httpTunnel koupleless_http_tunnel.HttpTunnel
 var mqttServer *mqtt.Server
+var ctx, cancel = context.WithCancel(context.Background())
 
 const (
 	clientID = "suite-test"
@@ -86,62 +86,58 @@ var _ = BeforeSuite(func() {
 	err = mqttServer.AddListener(tcp)
 	Expect(err).NotTo(HaveOccurred())
 
-	go mqttServer.Serve()
+	go func() {
+		err := mqttServer.Serve()
+		if err != nil {
+			log.G(ctx).Error("failed to start mqtt server")
+			panic(err)
+		}
+	}()
 
 	err = scheme.AddToScheme(scheme.Scheme)
 	Expect(err).NotTo(HaveOccurred())
 
 	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
 		Scheme: scheme.Scheme,
+		Metrics: metricsserver.Options{
+			BindAddress: ":8080",
+		},
 	})
 	Expect(err).ToNot(HaveOccurred())
 
-	mqttTunnel.Client = k8sManager.GetClient()
-	mqttTunnel.Cache = k8sManager.GetCache()
-	httpTunnel.Client = k8sManager.GetClient()
-	httpTunnel.Cache = k8sManager.GetCache()
-	httpTunnel.Port = 7777
+	moduleDeploymentController, err := module_deployment_controller.NewModuleDeploymentController(env)
+	Expect(err).ToNot(HaveOccurred())
 
-	tunnels := []tunnel.Tunnel{
-		&mqttTunnel,
-		&httpTunnel,
-	}
+	err = moduleDeploymentController.SetupWithManager(ctx, k8sManager)
+
+	Expect(err).ToNot(HaveOccurred())
 
-	ctx := context.Background()
+	mqttTunnel = koupleless_mqtt_tunnel.NewMqttTunnel(env, k8sManager.GetClient(), moduleDeploymentController)
+
+	err = mqttTunnel.Start(ctx, clientID, env)
+	if err != nil {
+		log.G(ctx).WithError(err).Error("failed to start tunnel", mqttTunnel.Key())
+		panic(fmt.Sprintf("failed to start tunnel %s", mqttTunnel.Key()))
+	} else {
+		log.G(ctx).Info("Tunnel started: ", mqttTunnel.Key())
+	}
 
 	vnodeController, err := vnode_controller.NewVNodeController(&model.BuildVNodeControllerConfig{
 		ClientID:       clientID,
 		Env:            env,
-		VPodIdentity:   model2.ComponentModule,
+		VPodType:       model2.ComponentModule,
 		VNodeWorkerNum: 4,
-	}, tunnels)
+	}, &mqttTunnel)
 	Expect(err).ToNot(HaveOccurred())
 
 	err = vnodeController.SetupWithManager(ctx, k8sManager)
 
-	moduleDeploymentController, err := module_deployment_controller.NewModuleDeploymentController(env, []module_tunnels.ModuleTunnel{
-		&mqttTunnel,
-	})
-	Expect(err).ToNot(HaveOccurred())
-
-	err = moduleDeploymentController.SetupWithManager(ctx, k8sManager)
-
-	Expect(err).ToNot(HaveOccurred())
-
-	for _, t := range tunnels {
-		err = t.Start(ctx, clientID, env)
-		if err != nil {
-			log.G(ctx).WithError(err).Error("failed to start tunnel", t.Key())
-		} else {
-			log.G(ctx).Info("Tunnel started: ", t.Key())
-		}
-	}
-
 	k8sClient = k8sManager.GetClient()
 	Expect(k8sClient).ToNot(BeNil())
 
 	go func() {
 		err = k8sManager.Start(ctx)
+		log.G(ctx).WithError(err).Error("k8sManager Start")
 		Expect(err).ToNot(HaveOccurred())
 	}()
 
@@ -150,8 +146,11 @@ var _ = BeforeSuite(func() {
 
 var _ = AfterSuite(func() {
 	By("tearing down the suite environment")
-	testEnv.Stop()
 	mqttServer.Close()
+	cancel()
+	testEnv.Stop()
+	time.Sleep(15 * time.Second)
+	log.G(ctx).Info("suite for mqtt stopped!")
 })
 
 func prepareModulePod(name, namespace, nodeName string) v1.Pod {
