Repository: openyurtio/openyurt
Pull request #848 (2022-05-24 14:59:37)

decouple yurtctl with yurtadm
Signed-off-by: Congrool <chpzhangyifei@zju.edu.cn>

<!--  Thanks for sending a pull request!  Here are some tips for you:
https://github.com/openyurtio/openyurt/blob/master/CONTRIBUTING.md 
-->


#### What type of PR is this?
/kind enhancement

#### What this PR does / why we need it:
Decouple yurtctl with yurtadm. Before this pr, code in pkg/yurtctl imported package in pkg/yurtadm, which makes code confused for read and difficult to maintain. This pr totaly separates yurtctl from yurtadm. 

The following work was done in this pr:
1. move `pkg/yurtadm/kubernetes` to `pkg/util/kubernetes` for general use
2. move `pkg/yurtadm/util/templates` and `pkg/yurtadm/util/strings` to `pkg/util/templates` and `pkg/util/strings` respectively for general use
3. move constants which are only used by yurtctl from `pkg/yurtadm` to `pkg/yurtctl`
4. move functions that only used by yurtctl from `pkg/yurtadm/util/kubernetes` to `pkg/yurtctl/util/kubernetes`

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Special notes for your reviewer:
/assign @rambohe-ch @lonelyCZ 


#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

-->
```release-note

```

#### other Note
<!--
If your current PR is still working in process, start the PR title name with [WIP], such as: [WIP] add new crd for yurt-app-manager
If the PR title name begins with [WIP], OpenYurt-bot automatically adds a do-not-merge/work-in-progress label for your pr 
-->



Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index fb69d2f9..e10a6e7c 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -45,6 +45,14 @@ jobs:
         uses: actions/checkout@v2
         with:
           submodules: true
+      - name: Get the version
+        id: get_version
+        run: |
+          VERSION=${GITHUB_REF#refs/tags/}
+          if [[ ${GITHUB_REF} == "refs/heads/master" ]]; then
+            VERSION=latest
+          fi
+          echo ::set-output name=VERSION::${VERSION}
       - name: Install Docker Buildx
         id: buildx
         uses: docker/setup-buildx-action@v1
diff --git a/Makefile b/Makefile
index baa4f2e7..2c848aa8 100644
--- a/Makefile
+++ b/Makefile
@@ -119,16 +119,46 @@ docker-build-node-servant:
 docker-push: docker-push-yurthub docker-push-yurt-controller-manager docker-push-yurt-tunnel-server docker-push-yurt-tunnel-agent docker-push-node-servant
 
 docker-push-yurthub:
+	docker buildx rm container-builder || true
+	docker buildx create --use --name=yurthub-container-builder
+	# enable qemu for arm64 build
+	# https://github.com/docker/buildx/issues/464#issuecomment-741507760
+	docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-aarch64
+	docker run --rm --privileged tonistiigi/binfmt --install all
 	docker buildx build --no-cache --push ${DOCKER_BUILD_ARGS}  --platform ${TARGET_PLATFORMS} -f hack/dockerfiles/Dockerfile.yurthub . -t ${IMAGE_REPO}/yurthub:${GIT_VERSION}
 
 docker-push-yurt-controller-manager:
+	docker buildx rm container-builder || true
+	docker buildx create --use --name=ycm-container-builder
+	# enable qemu for arm64 build
+	# https://github.com/docker/buildx/issues/464#issuecomment-741507760
+	docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-aarch64
+	docker run --rm --privileged tonistiigi/binfmt --install all
 	docker buildx build --no-cache --push ${DOCKER_BUILD_ARGS}  --platform ${TARGET_PLATFORMS} -f hack/dockerfiles/Dockerfile.yurt-controller-manager . -t ${IMAGE_REPO}/yurt-controller-manager:${GIT_VERSION}
 
 docker-push-yurt-tunnel-server:
+	docker buildx rm container-builder || true
+	docker buildx create --use --name=tunnel-server-container-builder
+	# enable qemu for arm64 build
+	# https://github.com/docker/buildx/issues/464#issuecomment-741507760
+	docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-aarch64
+	docker run --rm --privileged tonistiigi/binfmt --install all
 	docker buildx build --no-cache --push ${DOCKER_BUILD_ARGS}  --platform ${TARGET_PLATFORMS} -f hack/dockerfiles/Dockerfile.yurt-tunnel-server . -t ${IMAGE_REPO}/yurt-tunnel-server:${GIT_VERSION}
 
 docker-push-yurt-tunnel-agent:
+	docker buildx rm container-builder || true
+	docker buildx create --use --name=tunnel-agent-container-builder
+	# enable qemu for arm64 build
+	# https://github.com/docker/buildx/issues/464#issuecomment-741507760
+	docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-aarch64
+	docker run --rm --privileged tonistiigi/binfmt --install all
 	docker buildx build --no-cache --push ${DOCKER_BUILD_ARGS}  --platform ${TARGET_PLATFORMS} -f hack/dockerfiles/Dockerfile.yurt-tunnel-agent . -t ${IMAGE_REPO}/yurt-tunnel-agent:${GIT_VERSION}
 
 docker-push-node-servant:
+	docker buildx rm container-builder || true
+	docker buildx create --use --name=node-servant-container-builder
+	# enable qemu for arm64 build
+	# https://github.com/docker/buildx/issues/464#issuecomment-741507760
+	docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-aarch64
+	docker run --rm --privileged tonistiigi/binfmt --install all
 	docker buildx build --no-cache --push ${DOCKER_BUILD_ARGS}  --platform ${TARGET_PLATFORMS} -f hack/dockerfiles/Dockerfile.yurt-node-servant . -t ${IMAGE_REPO}/node-servant:${GIT_VERSION}
diff --git a/cmd/yurt-tunnel-agent/app/options/options.go b/cmd/yurt-tunnel-agent/app/options/options.go
index 9231a67e..6541c5f3 100644
--- a/cmd/yurt-tunnel-agent/app/options/options.go
+++ b/cmd/yurt-tunnel-agent/app/options/options.go
@@ -25,10 +25,12 @@ import (
 
 	"github.com/spf13/pflag"
 	"k8s.io/klog/v2"
+	utilnet "k8s.io/utils/net"
 	"sigs.k8s.io/apiserver-network-proxy/pkg/agent"
 
 	"github.com/openyurtio/openyurt/cmd/yurt-tunnel-agent/app/config"
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	utilip "github.com/openyurtio/openyurt/pkg/util/ip"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
 	kubeutil "github.com/openyurtio/openyurt/pkg/yurttunnel/kubernetes"
 )
@@ -52,7 +54,6 @@ type AgentOptions struct {
 // NewAgentOptions creates a new AgentOptions with a default config.
 func NewAgentOptions() *AgentOptions {
 	o := &AgentOptions{
-		MetaHost: "127.0.0.1",
 		MetaPort: constants.YurttunnelAgentMetaPort,
 	}
 
@@ -75,6 +76,10 @@ func (o *AgentOptions) Validate() error {
 		}
 	}
 
+	if o.MetaHost == "" {
+		o.MetaHost = utilip.MustGetLoopbackIP(utilnet.IsIPv6String(o.NodeIP))
+	}
+
 	if !agentIdentifiersAreValid(o.AgentIdentifiers) {
 		return errors.New("--agent-identifiers are invalid, format should be host={node-name}")
 	}
@@ -134,7 +139,11 @@ func (o *AgentOptions) Config() (*config.Config, error) {
 	}
 
 	if len(c.AgentIdentifiers) == 0 {
-		c.AgentIdentifiers = fmt.Sprintf("ipv4=%s&host=%s", o.NodeIP, o.NodeName)
+		ipFamily := "ipv4"
+		if utilnet.IsIPv6String(o.NodeIP) {
+			ipFamily = "ipv6"
+		}
+		c.AgentIdentifiers = fmt.Sprintf("%s=%s&host=%s", ipFamily, o.NodeIP, o.NodeName)
 	}
 	klog.Infof("%s is set for agent identifies", c.AgentIdentifiers)
 
diff --git a/cmd/yurt-tunnel-server/app/config/config.go b/cmd/yurt-tunnel-server/app/config/config.go
index 84e7e2f2..73d5b03c 100644
--- a/cmd/yurt-tunnel-server/app/config/config.go
+++ b/cmd/yurt-tunnel-server/app/config/config.go
@@ -25,6 +25,7 @@ import (
 	"k8s.io/client-go/kubernetes"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	"github.com/openyurtio/openyurt/pkg/util/iptables"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
 )
 
@@ -34,6 +35,7 @@ type Config struct {
 	EnableIptables              bool
 	EnableDNSController         bool
 	IptablesSyncPeriod          int
+	IPFamily                    iptables.Protocol
 	DNSSyncPeriod               int
 	CertDNSNames                []string
 	CertIPs                     []net.IP
@@ -72,3 +74,7 @@ func (c *Config) Complete() *CompletedConfig {
 	}
 	return &CompletedConfig{&cc}
 }
+
+func (c *Config) IsIPv6() bool {
+	return c.IPFamily == iptables.ProtocolIpv6
+}
diff --git a/cmd/yurt-tunnel-server/app/options/options.go b/cmd/yurt-tunnel-server/app/options/options.go
index 1dfb2360..1833b576 100644
--- a/cmd/yurt-tunnel-server/app/options/options.go
+++ b/cmd/yurt-tunnel-server/app/options/options.go
@@ -25,11 +25,14 @@ import (
 	"github.com/spf13/pflag"
 	"k8s.io/client-go/informers"
 	"k8s.io/klog/v2"
+	utilnet "k8s.io/utils/net"
 	"sigs.k8s.io/apiserver-network-proxy/pkg/server"
 
 	"github.com/openyurtio/openyurt/cmd/yurt-tunnel-server/app/config"
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
 	"github.com/openyurtio/openyurt/pkg/util/certmanager"
+	utilip "github.com/openyurtio/openyurt/pkg/util/ip"
+	"github.com/openyurtio/openyurt/pkg/util/iptables"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
 	kubeutil "github.com/openyurtio/openyurt/pkg/yurttunnel/kubernetes"
 )
@@ -60,7 +63,6 @@ type ServerOptions struct {
 func NewServerOptions() *ServerOptions {
 	o := &ServerOptions{
 		BindAddr:               "0.0.0.0",
-		InsecureBindAddr:       "127.0.0.1",
 		EnableIptables:         true,
 		EnableDNSController:    true,
 		IptablesSyncPeriod:     60,
@@ -81,6 +83,9 @@ func (o *ServerOptions) Validate() error {
 		return fmt.Errorf("%s's bind address can't be empty",
 			projectinfo.GetServerName())
 	}
+	if len(o.InsecureBindAddr) == 0 {
+		o.InsecureBindAddr = utilip.MustGetLoopbackIP(utilnet.IsIPv6String(o.BindAddr))
+	}
 	return nil
 }
 
@@ -136,6 +141,11 @@ func (o *ServerOptions) Config() (*config.Config, error) {
 		}
 	}
 
+	if utilnet.IsIPv6String(o.BindAddr) {
+		cfg.IPFamily = iptables.ProtocolIpv6
+	} else {
+		cfg.IPFamily = iptables.ProtocolIpv4
+	}
 	cfg.ListenAddrForAgent = net.JoinHostPort(o.BindAddr, o.TunnelAgentConnectPort)
 	cfg.ListenAddrForMaster = net.JoinHostPort(o.BindAddr, o.SecurePort)
 	cfg.ListenInsecureAddrForMaster = net.JoinHostPort(o.InsecureBindAddr, o.InsecurePort)
diff --git a/cmd/yurt-tunnel-server/app/start.go b/cmd/yurt-tunnel-server/app/start.go
index e5169f0b..01947a2b 100644
--- a/cmd/yurt-tunnel-server/app/start.go
+++ b/cmd/yurt-tunnel-server/app/start.go
@@ -93,11 +93,12 @@ func Run(cfg *config.CompletedConfig, stopCh <-chan struct{}) error {
 	}
 	// 1. start the IP table manager
 	if cfg.EnableIptables {
-		iptablesMgr := iptables.NewIptablesManager(cfg.Client,
+		iptablesMgr := iptables.NewIptablesManagerWithIPFamily(cfg.Client,
 			cfg.SharedInformerFactory.Core().V1().Nodes(),
 			cfg.ListenAddrForMaster,
 			cfg.ListenInsecureAddrForMaster,
-			cfg.IptablesSyncPeriod)
+			cfg.IptablesSyncPeriod,
+			cfg.IPFamily)
 		if iptablesMgr == nil {
 			return fmt.Errorf("fail to create a new IptableManager")
 		}
@@ -121,7 +122,7 @@ func Run(cfg *config.CompletedConfig, stopCh <-chan struct{}) error {
 
 	// 4. create handler wrappers
 	mInitializer := initializer.NewMiddlewareInitializer(cfg.SharedInformerFactory)
-	wrappers, err := wraphandler.InitHandlerWrappers(mInitializer)
+	wrappers, err := wraphandler.InitHandlerWrappers(mInitializer, cfg.IsIPv6())
 	if err != nil {
 		klog.Errorf("failed to init handler wrappers, %v", err)
 		return err
diff --git a/cmd/yurthub/app/config/config.go b/cmd/yurthub/app/config/config.go
index 82c3a13c..76f2cca3 100644
--- a/cmd/yurthub/app/config/config.go
+++ b/cmd/yurthub/app/config/config.go
@@ -91,6 +91,7 @@ type YurtHubConfiguration struct {
 	WorkingMode                       util.WorkingMode
 	KubeletHealthGracePeriod          time.Duration
 	FilterManager                     *filter.Manager
+	CertIPs                           []net.IP
 }
 
 // Complete converts *options.YurtHubOptions to *YurtHubConfiguration
@@ -135,6 +136,12 @@ func Complete(options *options.YurtHubOptions) (*YurtHubConfiguration, error) {
 		return nil, err
 	}
 
+	// use dummy ip and bind ip as cert IP SANs
+	certIPs := []net.IP{
+		net.ParseIP(options.HubAgentDummyIfIP),
+		net.ParseIP(options.YurtHubHost),
+	}
+
 	cfg := &YurtHubConfiguration{
 		LBMode:                            options.LBMode,
 		RemoteServers:                     us,
@@ -167,6 +174,7 @@ func Complete(options *options.YurtHubOptions) (*YurtHubConfiguration, error) {
 		YurtSharedFactory:                 yurtSharedFactory,
 		KubeletHealthGracePeriod:          options.KubeletHealthGracePeriod,
 		FilterManager:                     filterManager,
+		CertIPs:                           certIPs,
 	}
 
 	return cfg, nil
diff --git a/cmd/yurthub/app/options/options.go b/cmd/yurthub/app/options/options.go
index cee8d1d1..5ec07e4c 100644
--- a/cmd/yurthub/app/options/options.go
+++ b/cmd/yurthub/app/options/options.go
@@ -23,6 +23,8 @@ import (
 	"time"
 
 	"github.com/spf13/pflag"
+	"k8s.io/klog/v2"
+	utilnet "k8s.io/utils/net"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
 	"github.com/openyurtio/openyurt/pkg/yurthub/storage/disk"
@@ -30,8 +32,10 @@ import (
 )
 
 const (
-	DummyIfCIDR   = "169.254.0.0/16"
-	ExclusiveCIDR = "169.254.31.0/24"
+	DefaultDummyIfIP4 = "169.254.2.1"
+	DefaultDummyIfIP6 = "fd00::2:1"
+	DummyIfCIDR4      = "169.254.0.0/16"
+	ExclusiveCIDR     = "169.254.31.0/24"
 )
 
 // YurtHubOptions is the main settings for the yurthub
@@ -90,7 +94,6 @@ func NewYurtHubOptions() *YurtHubOptions {
 		EnableProfiling:           true,
 		EnableDummyIf:             true,
 		EnableIptables:            true,
-		HubAgentDummyIfIP:         "169.254.2.1",
 		HubAgentDummyIfName:       fmt.Sprintf("%s-dummy0", projectinfo.GetHubName()),
 		DiskCachePath:             disk.CacheBaseDir,
 		AccessServerThroughHub:    true,
@@ -103,8 +106,8 @@ func NewYurtHubOptions() *YurtHubOptions {
 	return o
 }
 
-// ValidateOptions validates YurtHubOptions
-func ValidateOptions(options *YurtHubOptions) error {
+// Validate validates YurtHubOptions
+func (options *YurtHubOptions) Validate() error {
 	if len(options.NodeName) == 0 {
 		return fmt.Errorf("node name is empty")
 	}
@@ -125,7 +128,7 @@ func ValidateOptions(options *YurtHubOptions) error {
 		return fmt.Errorf("working mode %s is not supported", options.WorkingMode)
 	}
 
-	if err := verifyDummyIP(options.HubAgentDummyIfIP); err != nil {
+	if err := options.verifyDummyIP(); err != nil {
 		return fmt.Errorf("dummy ip %s is not invalid, %w", options.HubAgentDummyIfIP, err)
 	}
 
@@ -168,21 +171,35 @@ func (o *YurtHubOptions) AddFlags(fs *pflag.FlagSet) {
 	fs.BoolVar(&o.EnableNodePool, "enable-node-pool", o.EnableNodePool, "enable list/watch nodepools resource or not for filters(only used for testing)")
 }
 
-// verifyDummyIP verify the specified ip is valid or not
-func verifyDummyIP(dummyIP string) error {
-	//169.254.2.1/32
+// verifyDummyIP verify the specified ip is valid or not and set the default ip if empty
+func (o *YurtHubOptions) verifyDummyIP() error {
+	if o.HubAgentDummyIfIP == "" {
+		if utilnet.IsIPv6String(o.YurtHubHost) {
+			o.HubAgentDummyIfIP = DefaultDummyIfIP6
+		} else {
+			o.HubAgentDummyIfIP = DefaultDummyIfIP4
+		}
+		klog.Infof("dummy ip not set, will use %s as default", o.HubAgentDummyIfIP)
+		return nil
+	}
+
+	dummyIP := o.HubAgentDummyIfIP
 	dip := net.ParseIP(dummyIP)
 	if dip == nil {
 		return fmt.Errorf("dummy ip %s is invalid", dummyIP)
 	}
 
-	_, dummyIfIPNet, err := net.ParseCIDR(DummyIfCIDR)
+	if utilnet.IsIPv6(dip) {
+		return nil
+	}
+
+	_, dummyIfIPNet, err := net.ParseCIDR(DummyIfCIDR4)
 	if err != nil {
-		return fmt.Errorf("cidr(%s) is invalid, %w", DummyIfCIDR, err)
+		return fmt.Errorf("cidr(%s) is invalid, %w", DummyIfCIDR4, err)
 	}
 
 	if !dummyIfIPNet.Contains(dip) {
-		return fmt.Errorf("dummy ip %s is not in cidr(%s)", dummyIP, DummyIfCIDR)
+		return fmt.Errorf("dummy ip %s is not in cidr(%s)", dummyIP, DummyIfCIDR4)
 	}
 
 	_, exclusiveIPNet, err := net.ParseCIDR(ExclusiveCIDR)
diff --git a/cmd/yurthub/app/start.go b/cmd/yurthub/app/start.go
index 23c4838a..9245a7b7 100644
--- a/cmd/yurthub/app/start.go
+++ b/cmd/yurthub/app/start.go
@@ -59,7 +59,7 @@ func NewCmdStartYurtHub(stopCh <-chan struct{}) *cobra.Command {
 			cmd.Flags().VisitAll(func(flag *pflag.Flag) {
 				klog.V(1).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
 			})
-			if err := options.ValidateOptions(yurtHubOptions); err != nil {
+			if err := yurtHubOptions.Validate(); err != nil {
 				klog.Fatalf("validate options: %v", err)
 			}
 
@@ -125,7 +125,8 @@ func Run(cfg *config.YurtHubConfiguration, stopCh <-chan struct{}) error {
 	trace++
 
 	klog.Infof("%d. create tls config for secure servers ", trace)
-	cfg.TLSConfig, err = server.GenUseCertMgrAndTLSConfig(restConfigMgr, certManager, filepath.Join(cfg.RootDir, "pki"), cfg.NodeName, cfg.YurtHubProxyServerSecureDummyAddr, stopCh)
+	cfg.TLSConfig, err = server.GenUseCertMgrAndTLSConfig(
+		restConfigMgr, certManager, filepath.Join(cfg.RootDir, "pki"), cfg.NodeName, cfg.CertIPs, stopCh)
 	if err != nil {
 		return fmt.Errorf("could not create tls config, %w", err)
 	}
diff --git a/config/setup/yurt-tunnel-server.yaml b/config/setup/yurt-tunnel-server.yaml
index 0874a486..8c33133b 100644
--- a/config/setup/yurt-tunnel-server.yaml
+++ b/config/setup/yurt-tunnel-server.yaml
@@ -28,7 +28,7 @@ metadata:
   name: tunnel-proxy-client
 subjects:
   - kind: User
-    name: tunnel-server-proxy-client
+    name: tunnel-proxy-client
     apiGroup: rbac.authorization.k8s.io
 roleRef:
   kind: ClusterRole
diff --git a/config/yaml-template/yurt-tunnel-server.yaml b/config/yaml-template/yurt-tunnel-server.yaml
index 0593bda2..feae619b 100644
--- a/config/yaml-template/yurt-tunnel-server.yaml
+++ b/config/yaml-template/yurt-tunnel-server.yaml
@@ -28,7 +28,7 @@ metadata:
   name: tunnel-proxy-client
 subjects:
   - kind: User
-    name: tunnel-server-proxy-client
+    name: tunnel-proxy-client
     apiGroup: rbac.authorization.k8s.io
 roleRef:
   kind: ClusterRole
diff --git a/go.mod b/go.mod
index f655f65a..2cfdca64 100644
--- a/go.mod
+++ b/go.mod
@@ -42,11 +42,10 @@ require (
 	k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b
 	sigs.k8s.io/apiserver-network-proxy v0.0.15
 	sigs.k8s.io/yaml v1.3.0 // indirect
-
 )
 
 replace (
 	k8s.io/klog/v2 => k8s.io/klog/v2 v2.9.0
 	sigs.k8s.io/apiserver-network-proxy => github.com/openyurtio/apiserver-network-proxy v1.18.8
 	sigs.k8s.io/apiserver-network-proxy/konnectivity-client => sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22
-)
+)
\ No newline at end of file
diff --git a/go.sum b/go.sum
index 8b24439b..cb4cc9cc 100644
--- a/go.sum
+++ b/go.sum
@@ -1229,4 +1229,4 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZa
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
-sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
\ No newline at end of file
diff --git a/hack/dockerfiles/Dockerfile.yurt-tunnel-server b/hack/dockerfiles/Dockerfile.yurt-tunnel-server
index 7c685e0d..2e7d0908 100644
--- a/hack/dockerfiles/Dockerfile.yurt-tunnel-server
+++ b/hack/dockerfiles/Dockerfile.yurt-tunnel-server
@@ -9,6 +9,6 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GIT_VERSION=${GIT_VERSIO
 FROM --platform=${TARGETPLATFORM} alpine:3.14
 ARG TARGETOS TARGETARCH MIRROR_REPO
 RUN if [ ! -z "${MIRROR_REPO+x}" ]; then sed -i "s/dl-cdn.alpinelinux.org/${MIRROR_REPO}/g" /etc/apk/repositories; fi && \
-    apk add ca-certificates bash libc6-compat iptables ip6tables && update-ca-certificates && rm /var/cache/apk/*
+    apk add ca-certificates bash libc6-compat iptables ip6tables conntrack-tools && update-ca-certificates && rm /var/cache/apk/*
 COPY --from=builder /build/_output/local/bin/${TARGETOS}/${TARGETARCH}/yurt-tunnel-server /usr/local/bin/yurt-tunnel-server
-ENTRYPOINT ["/usr/local/bin/yurt-tunnel-server"]
\ No newline at end of file
+ENTRYPOINT ["/usr/local/bin/yurt-tunnel-server"]
diff --git a/pkg/node-servant/components/yurthub.go b/pkg/node-servant/components/yurthub.go
index 98544c9a..a06c4711 100644
--- a/pkg/node-servant/components/yurthub.go
+++ b/pkg/node-servant/components/yurthub.go
@@ -30,8 +30,8 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/klog/v2"
 
+	"github.com/openyurtio/openyurt/pkg/util/templates"
 	enutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/util/templates"
 	"github.com/openyurtio/openyurt/pkg/yurthub/certificate/hubself"
 	"github.com/openyurtio/openyurt/pkg/yurthub/storage/disk"
 	"github.com/openyurtio/openyurt/pkg/yurthub/util"
diff --git a/pkg/node-servant/job.go b/pkg/node-servant/job.go
index 40c187a8..a2546e9b 100644
--- a/pkg/node-servant/job.go
+++ b/pkg/node-servant/job.go
@@ -24,7 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/serializer"
 	"k8s.io/client-go/kubernetes/scheme"
 
-	tmplutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/templates"
+	tmplutil "github.com/openyurtio/openyurt/pkg/util/templates"
 )
 
 // RenderNodeServantJob return k8s job
diff --git a/pkg/util/certmanager/certmanager.go b/pkg/util/certmanager/certmanager.go
index 054c2e24..acc377ef 100644
--- a/pkg/util/certmanager/certmanager.go
+++ b/pkg/util/certmanager/certmanager.go
@@ -38,6 +38,7 @@ import (
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
 	"github.com/openyurtio/openyurt/pkg/util/certmanager/store"
+	utilip "github.com/openyurtio/openyurt/pkg/util/ip"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/server/serveraddr"
 )
@@ -69,7 +70,7 @@ func NewYurttunnelServerCertManager(
 	)
 
 	// the ips and dnsNames should be acquired through api-server at the first time, because the informer factory has not started yet.
-	_ = wait.PollUntil(5*time.Second, func() (bool, error) {
+	werr := wait.PollUntil(5*time.Second, func() (bool, error) {
 		dnsNames, ips, err = serveraddr.GetYurttunelServerDNSandIP(clientset)
 		if err != nil {
 			klog.Errorf("failed to get yurt tunnel server dns and ip, %v", err)
@@ -94,10 +95,14 @@ func NewYurttunnelServerCertManager(
 
 		return true, nil
 	}, stopCh)
-	// add user specified DNS names and IP addresses
+	if werr != nil {
+		return nil, werr
+	}
+
+	// add user specified DNS anems and IP addresses
 	dnsNames = append(dnsNames, clCertNames...)
 	ips = append(ips, clIPs...)
-	klog.Infof("subject of tunnel server certificate, ips=%#+v, dnsNames=%#+v", ips, dnsNames)
+	klog.Infof("subject of tunnel server certificate, ips=%s, dnsNames=%#+v", utilip.JoinIPStrings(ips), dnsNames)
 
 	// the dynamic ip acquire func
 	getIPs := func() ([]net.IP, error) {
@@ -180,13 +185,8 @@ func NewYurttunnelAgentCertManager(
 func NewYurtHubServerCertManager(
 	clientset kubernetes.Interface,
 	certDir,
-	nodeName,
-	proxyServerSecureDummyAddr string) (certificate.Manager, error) {
-
-	host, _, err := net.SplitHostPort(proxyServerSecureDummyAddr)
-	if err != nil {
-		return nil, err
-	}
+	nodeName string,
+	certIPs []net.IP) (certificate.Manager, error) {
 
 	return newCertManager(
 		clientset,
@@ -201,7 +201,7 @@ func NewYurtHubServerCertManager(
 			certificatesv1.UsageDigitalSignature,
 			certificatesv1.UsageServerAuth,
 		},
-		[]net.IP{net.ParseIP("127.0.0.1"), net.ParseIP(host)},
+		certIPs,
 		nil)
 }
 
diff --git a/pkg/util/ip/ip.go b/pkg/util/ip/ip.go
new file mode 100644
index 00000000..2d1c9b23
--- /dev/null
+++ b/pkg/util/ip/ip.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2021 The OpenYurt Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ip
+
+import (
+	"net"
+	"strings"
+
+	"k8s.io/klog/v2"
+	utilnet "k8s.io/utils/net"
+)
+
+const (
+	DefaultLoopbackIP4 = "127.0.0.1"
+	DefaultLoopbackIP6 = "::1"
+)
+
+// MustGetLoopbackIP is a wrapper for GetLoopbackIP. If any error occurs or loopback interface is not found,
+// will fall back to 127.0.0.1 for ipv4 or ::1 for ipv6.
+func MustGetLoopbackIP(wantIPv6 bool) string {
+	ip, err := GetLoopbackIP(wantIPv6)
+	if err != nil {
+		klog.Errorf("failed to get loopback addr: %v", err)
+	}
+	if ip != "" {
+		return ip
+	}
+	if wantIPv6 {
+		return DefaultLoopbackIP6
+	}
+	return DefaultLoopbackIP4
+}
+
+// GetLoopbackIP returns the ip address of local loopback interface.
+func GetLoopbackIP(wantIPv6 bool) (string, error) {
+	addrs, err := net.InterfaceAddrs()
+	if err != nil {
+		return "", err
+	}
+	for _, address := range addrs {
+		if ipnet, ok := address.(*net.IPNet); ok && ipnet.IP.IsLoopback() && wantIPv6 == utilnet.IsIPv6(ipnet.IP) {
+			return ipnet.IP.String(), nil
+		}
+	}
+	return "", nil
+}
+
+func JoinIPStrings(ips []net.IP) string {
+	var strs []string
+	for _, ip := range ips {
+		strs = append(strs, ip.String())
+	}
+	return strings.Join(strs, ",")
+}
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go b/pkg/util/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go
rename to pkg/util/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go b/pkg/util/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go
rename to pkg/util/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenstring.go b/pkg/util/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenstring.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenstring.go
rename to pkg/util/kubernetes/kubeadm/app/apis/kubeadm/bootstraptokenstring.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/types.go b/pkg/util/kubernetes/kubeadm/app/apis/kubeadm/types.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm/types.go
rename to pkg/util/kubernetes/kubeadm/app/apis/kubeadm/types.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options/constant.go b/pkg/util/kubernetes/kubeadm/app/cmd/options/constant.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/cmd/options/constant.go
rename to pkg/util/kubernetes/kubeadm/app/cmd/options/constant.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow/phase.go b/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow/phase.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow/phase.go
rename to pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow/phase.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow/runner.go b/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow/runner.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow/runner.go
rename to pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow/runner.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow/runner_test.go b/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow/runner_test.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow/runner_test.go
rename to pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow/runner_test.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/constants/constants.go b/pkg/util/kubernetes/kubeadm/app/constants/constants.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/constants/constants.go
rename to pkg/util/kubernetes/kubeadm/app/constants/constants.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/constants/constants_unix.go b/pkg/util/kubernetes/kubeadm/app/constants/constants_unix.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/constants/constants_unix.go
rename to pkg/util/kubernetes/kubeadm/app/constants/constants_unix.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/constants/constants_windows.go b/pkg/util/kubernetes/kubeadm/app/constants/constants_windows.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/constants/constants_windows.go
rename to pkg/util/kubernetes/kubeadm/app/constants/constants_windows.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/discovery/token/token.go b/pkg/util/kubernetes/kubeadm/app/discovery/token/token.go
similarity index 96%
rename from pkg/yurtadm/kubernetes/kubeadm/app/discovery/token/token.go
rename to pkg/util/kubernetes/kubeadm/app/discovery/token/token.go
index 6742f2f4..f2246d89 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/discovery/token/token.go
+++ b/pkg/util/kubernetes/kubeadm/app/discovery/token/token.go
@@ -35,11 +35,11 @@ import (
 	bootstrap "k8s.io/cluster-bootstrap/token/jws"
 	"k8s.io/klog/v2"
 
+	kubeadmapi "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/apis/kubeadm"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	kubeconfigutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/kubeconfig"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/pubkeypin"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
-	kubeadmapi "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	kubeconfigutil "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/pubkeypin"
 )
 
 // BootstrapUser defines bootstrap user name
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go b/pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go
similarity index 97%
rename from pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go
rename to pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go
index 005b127b..d39ca015 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go
+++ b/pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go
@@ -31,7 +31,7 @@ import (
 	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
 	"k8s.io/klog/v2"
 
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/apiclient"
 )
 
 const (
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/node/token.go b/pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/node/token.go
similarity index 92%
rename from pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/node/token.go
rename to pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/node/token.go
index 55c6ecb1..39052da6 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/node/token.go
+++ b/pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/node/token.go
@@ -24,8 +24,8 @@ import (
 	clientset "k8s.io/client-go/kubernetes"
 	bootstraputil "k8s.io/cluster-bootstrap/token/util"
 
-	kubeadmapi "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient"
+	kubeadmapi "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/apis/kubeadm"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/apiclient"
 )
 
 // TODO(mattmoyer): Move CreateNewTokens, UpdateOrCreateTokens out of this package to client-go for a generic abstraction and client for a Bootstrap Token
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags.go b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags.go
similarity index 95%
rename from pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags.go
rename to pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags.go
index d99b28fc..8b8d482e 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags.go
+++ b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags.go
@@ -27,9 +27,9 @@ import (
 	"k8s.io/klog/v2"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	kubeadmutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	kubeadmutil "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util"
 )
 
 // WriteKubeletDynamicEnvFile writes an environment file with dynamic flags to the kubelet.
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags_test.go b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags_test.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags_test.go
rename to pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags_test.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags_unix.go b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags_unix.go
similarity index 87%
rename from pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags_unix.go
rename to pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags_unix.go
index 7bef3ebc..5f3e8337 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags_unix.go
+++ b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags_unix.go
@@ -24,10 +24,10 @@ import (
 	"k8s.io/klog/v2"
 	utilsexec "k8s.io/utils/exec"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	kubeadmutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/initsystem"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	kubeadmutil "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem"
 )
 
 // buildKubeletArgMap takes a kubeletFlagsOpts object and builds based on that a string-string map with flags
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags_windows.go b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags_windows.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/flags_windows.go
rename to pkg/util/kubernetes/kubeadm/app/phases/kubelet/flags_windows.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/kubelet.go b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/kubelet.go
similarity index 97%
rename from pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/kubelet.go
rename to pkg/util/kubernetes/kubeadm/app/phases/kubelet/kubelet.go
index a00ac980..34cec61d 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet/kubelet.go
+++ b/pkg/util/kubernetes/kubeadm/app/phases/kubelet/kubelet.go
@@ -19,7 +19,7 @@ package kubelet
 import (
 	"fmt"
 
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/initsystem"
 )
 
 // TryStartKubelet attempts to bring up kubelet service
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/preflight/checks.go b/pkg/util/kubernetes/kubeadm/app/preflight/checks.go
similarity index 98%
rename from pkg/yurtadm/kubernetes/kubeadm/app/preflight/checks.go
rename to pkg/util/kubernetes/kubeadm/app/preflight/checks.go
index f1f941a2..824a2ec7 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/preflight/checks.go
+++ b/pkg/util/kubernetes/kubeadm/app/preflight/checks.go
@@ -37,10 +37,10 @@ import (
 	system "k8s.io/system-validators/validators"
 	utilsexec "k8s.io/utils/exec"
 
+	kubeadmconstants "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/initsystem"
+	utilruntime "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/runtime"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
-	kubeadmconstants "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem"
-	utilruntime "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime"
 )
 
 const (
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/preflight/checks_unix.go b/pkg/util/kubernetes/kubeadm/app/preflight/checks_unix.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/preflight/checks_unix.go
rename to pkg/util/kubernetes/kubeadm/app/preflight/checks_unix.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/preflight/checks_windows.go b/pkg/util/kubernetes/kubeadm/app/preflight/checks_windows.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/preflight/checks_windows.go
rename to pkg/util/kubernetes/kubeadm/app/preflight/checks_windows.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/preflight/utils.go b/pkg/util/kubernetes/kubeadm/app/preflight/utils.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/preflight/utils.go
rename to pkg/util/kubernetes/kubeadm/app/preflight/utils.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/preflight/utils_test.go b/pkg/util/kubernetes/kubeadm/app/preflight/utils_test.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/preflight/utils_test.go
rename to pkg/util/kubernetes/kubeadm/app/preflight/utils_test.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/idempotency.go b/pkg/util/kubernetes/kubeadm/app/util/apiclient/idempotency.go
similarity index 99%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/idempotency.go
rename to pkg/util/kubernetes/kubeadm/app/util/apiclient/idempotency.go
index 8d577be1..a60cb9b9 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/idempotency.go
+++ b/pkg/util/kubernetes/kubeadm/app/util/apiclient/idempotency.go
@@ -34,7 +34,7 @@ import (
 	clientset "k8s.io/client-go/kubernetes"
 	clientsetretry "k8s.io/client-go/util/retry"
 
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
 )
 
 // ConfigMapMutator is a function that mutates the given ConfigMap and optionally returns an error
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/idempotency_test.go b/pkg/util/kubernetes/kubeadm/app/util/apiclient/idempotency_test.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/idempotency_test.go
rename to pkg/util/kubernetes/kubeadm/app/util/apiclient/idempotency_test.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/tryidempotency.go b/pkg/util/kubernetes/kubeadm/app/util/apiclient/tryidempotency.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/tryidempotency.go
rename to pkg/util/kubernetes/kubeadm/app/util/apiclient/tryidempotency.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/wait.go b/pkg/util/kubernetes/kubeadm/app/util/apiclient/wait.go
similarity index 99%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/wait.go
rename to pkg/util/kubernetes/kubeadm/app/util/apiclient/wait.go
index 747911f7..2eee7769 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient/wait.go
+++ b/pkg/util/kubernetes/kubeadm/app/util/apiclient/wait.go
@@ -32,7 +32,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	clientset "k8s.io/client-go/kubernetes"
 
-	kubeadmconstants "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
+	kubeadmconstants "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
 )
 
 // Waiter is an interface for waiting for criteria in Kubernetes to happen
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/arguments.go b/pkg/util/kubernetes/kubeadm/app/util/arguments.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/arguments.go
rename to pkg/util/kubernetes/kubeadm/app/util/arguments.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/cgroupdriver.go b/pkg/util/kubernetes/kubeadm/app/util/cgroupdriver.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/cgroupdriver.go
rename to pkg/util/kubernetes/kubeadm/app/util/cgroupdriver.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem/initsystem.go b/pkg/util/kubernetes/kubeadm/app/util/initsystem/initsystem.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem/initsystem.go
rename to pkg/util/kubernetes/kubeadm/app/util/initsystem/initsystem.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem/initsystem_unix.go b/pkg/util/kubernetes/kubeadm/app/util/initsystem/initsystem_unix.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem/initsystem_unix.go
rename to pkg/util/kubernetes/kubeadm/app/util/initsystem/initsystem_unix.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem/initsystem_windows.go b/pkg/util/kubernetes/kubeadm/app/util/initsystem/initsystem_windows.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem/initsystem_windows.go
rename to pkg/util/kubernetes/kubeadm/app/util/initsystem/initsystem_windows.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig.go b/pkg/util/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig.go
rename to pkg/util/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig_test.go b/pkg/util/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig_test.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig_test.go
rename to pkg/util/kubernetes/kubeadm/app/util/kubeconfig/kubeconfig_test.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin.go b/pkg/util/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin.go
rename to pkg/util/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin_test.go b/pkg/util/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin_test.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin_test.go
rename to pkg/util/kubernetes/kubeadm/app/util/pubkeypin/pubkeypin_test.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime.go b/pkg/util/kubernetes/kubeadm/app/util/runtime/runtime.go
similarity index 99%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime.go
rename to pkg/util/kubernetes/kubeadm/app/util/runtime/runtime.go
index cbff0d9a..9c85ac1c 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime.go
+++ b/pkg/util/kubernetes/kubeadm/app/util/runtime/runtime.go
@@ -26,7 +26,7 @@ import (
 	errorsutil "k8s.io/apimachinery/pkg/util/errors"
 	utilsexec "k8s.io/utils/exec"
 
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
 )
 
 // ContainerRuntime is an interface for working with container runtimes
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime_unix.go b/pkg/util/kubernetes/kubeadm/app/util/runtime/runtime_unix.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime_unix.go
rename to pkg/util/kubernetes/kubeadm/app/util/runtime/runtime_unix.go
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime_windows.go b/pkg/util/kubernetes/kubeadm/app/util/runtime/runtime_windows.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime_windows.go
rename to pkg/util/kubernetes/kubeadm/app/util/runtime/runtime_windows.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/register.go b/pkg/util/kubernetes/kubelet/apis/config/register.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/register.go
rename to pkg/util/kubernetes/kubelet/apis/config/register.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/scheme/scheme.go b/pkg/util/kubernetes/kubelet/apis/config/scheme/scheme.go
similarity index 88%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/scheme/scheme.go
rename to pkg/util/kubernetes/kubelet/apis/config/scheme/scheme.go
index 63fd7362..e47dc5fa 100644
--- a/pkg/yurtadm/kubernetes/kubelet/apis/config/scheme/scheme.go
+++ b/pkg/util/kubernetes/kubelet/apis/config/scheme/scheme.go
@@ -21,8 +21,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/serializer"
 
-	kubeletconfig "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/apis/config"
-	kubeletconfigv1beta1 "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1"
+	kubeletconfig "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/apis/config"
+	kubeletconfigv1beta1 "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/apis/config/v1beta1"
 )
 
 // Utility functions for the Kubelet's kubeletconfig API group
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/types.go b/pkg/util/kubernetes/kubelet/apis/config/types.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/types.go
rename to pkg/util/kubernetes/kubelet/apis/config/types.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/defaults.go b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/defaults.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/defaults.go
rename to pkg/util/kubernetes/kubelet/apis/config/v1beta1/defaults.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/defaults_linux.go b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/defaults_linux.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/defaults_linux.go
rename to pkg/util/kubernetes/kubelet/apis/config/v1beta1/defaults_linux.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/defaults_others.go b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/defaults_others.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/defaults_others.go
rename to pkg/util/kubernetes/kubelet/apis/config/v1beta1/defaults_others.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/register.go b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/register.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/register.go
rename to pkg/util/kubernetes/kubelet/apis/config/v1beta1/register.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/zz_generated.conversion.go b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/zz_generated.conversion.go
similarity index 99%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/zz_generated.conversion.go
rename to pkg/util/kubernetes/kubelet/apis/config/v1beta1/zz_generated.conversion.go
index d13b4e33..630425c9 100644
--- a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/zz_generated.conversion.go
+++ b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/zz_generated.conversion.go
@@ -29,7 +29,7 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	v1beta1 "k8s.io/kubelet/config/v1beta1"
 
-	config "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/apis/config"
+	config "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/apis/config"
 )
 
 func init() {
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/zz_generated.deepcopy.go b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/zz_generated.deepcopy.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/zz_generated.deepcopy.go
rename to pkg/util/kubernetes/kubelet/apis/config/v1beta1/zz_generated.deepcopy.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/zz_generated.defaults.go b/pkg/util/kubernetes/kubelet/apis/config/v1beta1/zz_generated.defaults.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/v1beta1/zz_generated.defaults.go
rename to pkg/util/kubernetes/kubelet/apis/config/v1beta1/zz_generated.defaults.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/apis/config/zz_generated.deepcopy.go b/pkg/util/kubernetes/kubelet/apis/config/zz_generated.deepcopy.go
similarity index 100%
rename from pkg/yurtadm/kubernetes/kubelet/apis/config/zz_generated.deepcopy.go
rename to pkg/util/kubernetes/kubelet/apis/config/zz_generated.deepcopy.go
diff --git a/pkg/yurtadm/kubernetes/kubelet/kubeletconfig/util/codec/codec.go b/pkg/util/kubernetes/kubelet/kubeletconfig/util/codec/codec.go
similarity index 95%
rename from pkg/yurtadm/kubernetes/kubelet/kubeletconfig/util/codec/codec.go
rename to pkg/util/kubernetes/kubelet/kubeletconfig/util/codec/codec.go
index e4772a93..ce4c74c0 100644
--- a/pkg/yurtadm/kubernetes/kubelet/kubeletconfig/util/codec/codec.go
+++ b/pkg/util/kubernetes/kubelet/kubeletconfig/util/codec/codec.go
@@ -28,8 +28,8 @@ import (
 	"k8s.io/klog/v2"
 	kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
 
-	kubeletconfig "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/apis/config"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/apis/config/scheme"
+	kubeletconfig "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/apis/config"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/apis/config/scheme"
 )
 
 // EncodeKubeletConfig encodes an internal KubeletConfiguration to an external YAML representation.
diff --git a/pkg/yurtadm/util/strings/util.go b/pkg/util/strings/util.go
similarity index 100%
rename from pkg/yurtadm/util/strings/util.go
rename to pkg/util/strings/util.go
diff --git a/pkg/yurtadm/util/templates/util.go b/pkg/util/templates/util.go
similarity index 100%
rename from pkg/yurtadm/util/templates/util.go
rename to pkg/util/templates/util.go
diff --git a/pkg/yurtadm/cmd/join/join.go b/pkg/yurtadm/cmd/join/join.go
index 20cba156..0d930b38 100644
--- a/pkg/yurtadm/cmd/join/join.go
+++ b/pkg/yurtadm/cmd/join/join.go
@@ -32,14 +32,14 @@ import (
 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 	"k8s.io/klog/v2"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/discovery/token"
+	kubeconfigutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/kubeconfig"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
 	yurtphase "github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/phases"
 	yurtconstants "github.com/openyurtio/openyurt/pkg/yurtadm/constants"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/discovery/token"
-	kubeconfigutil "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig"
 	yurtadmutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/kubernetes"
 )
 
diff --git a/pkg/yurtadm/cmd/join/phases/joinnode.go b/pkg/yurtadm/cmd/join/phases/joinnode.go
index c2d39537..e9702cfe 100644
--- a/pkg/yurtadm/cmd/join/phases/joinnode.go
+++ b/pkg/yurtadm/cmd/join/phases/joinnode.go
@@ -30,18 +30,18 @@ import (
 	"k8s.io/klog/v2"
 	kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	kubeutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/phases/kubelet"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/apiclient"
+	kubeletconfig "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/apis/config"
+	kubeletscheme "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/apis/config/scheme"
+	kubeletcodec "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubelet/kubeletconfig/util/codec"
+	"github.com/openyurtio/openyurt/pkg/util/templates"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
 	yurtconstants "github.com/openyurtio/openyurt/pkg/yurtadm/constants"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	kubeutil "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient"
-	kubeletconfig "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/apis/config"
-	kubeletscheme "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/apis/config/scheme"
-	kubeletcodec "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubelet/kubeletconfig/util/codec"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/util/templates"
 )
 
 // NewEdgeNodePhase creates a yurtadm workflow phase that start kubelet on a edge node.
diff --git a/pkg/yurtadm/cmd/join/phases/postcheck.go b/pkg/yurtadm/cmd/join/phases/postcheck.go
index 61a6cd9c..ac6c482f 100644
--- a/pkg/yurtadm/cmd/join/phases/postcheck.go
+++ b/pkg/yurtadm/cmd/join/phases/postcheck.go
@@ -27,13 +27,13 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/klog/v2"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/apiclient"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/initsystem"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/kubeconfig"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/initsystem"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
 )
 
diff --git a/pkg/yurtadm/cmd/join/phases/preflight.go b/pkg/yurtadm/cmd/join/phases/preflight.go
index c09d92b2..c236ef99 100644
--- a/pkg/yurtadm/cmd/join/phases/preflight.go
+++ b/pkg/yurtadm/cmd/join/phases/preflight.go
@@ -21,10 +21,10 @@ import (
 	"k8s.io/klog/v2"
 	utilsexec "k8s.io/utils/exec"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/preflight"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/preflight"
 )
 
 // NewPreflightPhase creates a kubeadm workflow phase that implements preflight checks for a new node join
diff --git a/pkg/yurtadm/cmd/join/phases/prepare.go b/pkg/yurtadm/cmd/join/phases/prepare.go
index d5bd0536..f17386b9 100644
--- a/pkg/yurtadm/cmd/join/phases/prepare.go
+++ b/pkg/yurtadm/cmd/join/phases/prepare.go
@@ -23,10 +23,10 @@ import (
 
 	"k8s.io/klog/v2"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util/kubernetes"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util/system"
 )
diff --git a/pkg/yurtadm/cmd/reset/phases/cleanupnode.go b/pkg/yurtadm/cmd/reset/phases/cleanupnode.go
index fb837d79..4bfd88f6 100644
--- a/pkg/yurtadm/cmd/reset/phases/cleanupnode.go
+++ b/pkg/yurtadm/cmd/reset/phases/cleanupnode.go
@@ -24,11 +24,11 @@ import (
 	"k8s.io/klog/v2"
 	utilsexec "k8s.io/utils/exec"
 
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	kubeadmconstants "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	kubeutil "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/phases/kubelet"
-	utilruntime "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	kubeadmconstants "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	kubeutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/phases/kubelet"
+	utilruntime "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/runtime"
 )
 
 // NewCleanupNodePhase creates a kubeadm workflow phase that cleanup the node
diff --git a/pkg/yurtadm/cmd/reset/phases/cleanyurtfile.go b/pkg/yurtadm/cmd/reset/phases/cleanyurtfile.go
index 5db82e4f..9406be52 100644
--- a/pkg/yurtadm/cmd/reset/phases/cleanyurtfile.go
+++ b/pkg/yurtadm/cmd/reset/phases/cleanyurtfile.go
@@ -22,8 +22,8 @@ import (
 
 	"k8s.io/klog/v2"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/constants"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
 )
 
diff --git a/pkg/yurtadm/cmd/reset/phases/preflight.go b/pkg/yurtadm/cmd/reset/phases/preflight.go
index 9885478f..e88d8d33 100644
--- a/pkg/yurtadm/cmd/reset/phases/preflight.go
+++ b/pkg/yurtadm/cmd/reset/phases/preflight.go
@@ -23,9 +23,9 @@ import (
 
 	"k8s.io/klog/v2"
 
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/preflight"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/preflight"
 )
 
 // NewPreflightPhase creates a kubeadm workflow phase implements preflight checks for reset
diff --git a/pkg/yurtadm/cmd/reset/reset.go b/pkg/yurtadm/cmd/reset/reset.go
index 8ed9bbd6..9df8a108 100644
--- a/pkg/yurtadm/cmd/reset/reset.go
+++ b/pkg/yurtadm/cmd/reset/reset.go
@@ -27,10 +27,10 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/klog/v2"
 
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/options"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/cmd/phases/workflow"
+	utilruntime "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/runtime"
 	yurtphases "github.com/openyurtio/openyurt/pkg/yurtadm/cmd/reset/phases"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/options"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/cmd/phases/workflow"
-	utilruntime "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime"
 )
 
 var (
diff --git a/pkg/yurtadm/cmd/yurtinit/init.go b/pkg/yurtadm/cmd/yurtinit/init.go
index 301397dd..b69a6d9f 100644
--- a/pkg/yurtadm/cmd/yurtinit/init.go
+++ b/pkg/yurtadm/cmd/yurtinit/init.go
@@ -29,11 +29,11 @@ import (
 	flag "github.com/spf13/pflag"
 	"k8s.io/klog/v2"
 
+	strutil "github.com/openyurtio/openyurt/pkg/util/strings"
+	tmplutil "github.com/openyurtio/openyurt/pkg/util/templates"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/constants"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
-	strutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/strings"
-	tmplutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/templates"
 )
 
 const (
diff --git a/pkg/yurtadm/constants/constants.go b/pkg/yurtadm/constants/constants.go
index de4b8e77..d7874cb4 100644
--- a/pkg/yurtadm/constants/constants.go
+++ b/pkg/yurtadm/constants/constants.go
@@ -97,164 +97,6 @@ current-context: default-context
 kind: Config
 preferences: {}
 `
-
-	YurtControllerManagerServiceAccount = `
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: yurt-controller-manager
-  namespace: kube-system
-`
-	// YurtControllerManagerClusterRole has the same privilege as the
-	// system:controller:node-controller and has the right to manipulate
-	// the leases resource
-	YurtControllerManagerClusterRole = `
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  annotations:
-    rbac.authorization.kubernetes.io/autoupdate: "true"
-  name: yurt-controller-manager 
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - nodes
-  verbs:
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - ""
-  resources:
-  - nodes/status
-  verbs:
-  - patch
-  - update
-- apiGroups:
-  - ""
-  resources:
-  - pods/status
-  verbs:
-  - update
-- apiGroups:
-  - ""
-  resources:
-  - pods
-  verbs:
-  - delete
-  - list
-  - watch
-- apiGroups:
-  - ""
-  - events.k8s.io
-  resources:
-  - events
-  verbs:
-  - create
-  - patch
-  - update
-- apiGroups:
-  - coordination.k8s.io
-  resources:
-  - leases
-  verbs:
-  - create
-  - delete
-  - get
-  - patch
-  - update
-  - list
-  - watch
-- apiGroups:
-  - ""
-  - apps
-  resources:
-  - daemonsets
-  verbs:
-  - list
-  - watch
-- apiGroups:
-    - certificates.k8s.io
-  resources:
-    - certificatesigningrequests
-  verbs:
-    - get
-    - list
-    - watch
-- apiGroups:
-    - certificates.k8s.io
-  resources:
-    - certificatesigningrequests/approval
-  verbs:
-    - update
-- apiGroups:
-    - certificates.k8s.io
-  resourceNames:
-    - kubernetes.io/kube-apiserver-client
-    - kubernetes.io/kubelet-serving
-  resources:
-    - signers
-  verbs:
-    - approve
-`
-	YurtControllerManagerClusterRoleBinding = `
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: yurt-controller-manager 
-subjects:
-  - kind: ServiceAccount
-    name: yurt-controller-manager
-    namespace: kube-system
-roleRef:
-  kind: ClusterRole
-  name: yurt-controller-manager 
-  apiGroup: rbac.authorization.k8s.io
-`
-	// YurtControllerManagerDeployment defines the yurt controller manager
-	// deployment in yaml format
-	YurtControllerManagerDeployment = `
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: yurt-controller-manager
-  namespace: kube-system
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: yurt-controller-manager
-  template:
-    metadata:
-      labels:
-        app: yurt-controller-manager
-    spec:
-      serviceAccountName: yurt-controller-manager
-      hostNetwork: true
-      tolerations:
-      - operator: "Exists"
-      affinity:
-        nodeAffinity:
-          # we prefer allocating ecm on cloud node
-          preferredDuringSchedulingIgnoredDuringExecution:
-          - weight: 1
-            preference:
-              matchExpressions:
-              - key: {{.edgeNodeLabel}}
-                operator: In
-                values:
-                - "false"
-      containers:
-      - name: yurt-controller-manager
-        image: {{.image}}
-        command:
-        - yurt-controller-manager	
-`
-
 	// DisableNodeControllerJobTemplate defines the node-controller disable job in yaml format
 	DisableNodeControllerJobTemplate = `
 apiVersion: batch/v1
diff --git a/pkg/yurtadm/util/edgenode/common.go b/pkg/yurtadm/util/edgenode/common.go
index 11e9bed8..4bd9a949 100644
--- a/pkg/yurtadm/util/edgenode/common.go
+++ b/pkg/yurtadm/util/edgenode/common.go
@@ -17,17 +17,14 @@ limitations under the License.
 package edgenode
 
 const (
-	KubeletSvcPath       = "/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf"
-	OpenyurtDir          = "/var/lib/openyurt"
-	StaticPodPath        = "/etc/kubernetes/manifests"
-	KubeCondfigPath      = "/etc/kubernetes/kubelet.conf"
-	KubeCaFile           = "/etc/kubernetes/pki/ca.crt"
-	YurthubYamlName      = "yurt-hub.yaml"
-	YurthubComponentName = "yurt-hub"
-	YurthubNamespace     = "kube-system"
-	YurthubCmName        = "yurt-hub-cfg"
-	KubeletConfName      = "kubelet.conf"
-	KubeletSvcBackup     = "%s.bk"
+	KubeletSvcPath   = "/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf"
+	OpenyurtDir      = "/var/lib/openyurt"
+	StaticPodPath    = "/etc/kubernetes/manifests"
+	KubeCondfigPath  = "/etc/kubernetes/kubelet.conf"
+	KubeCaFile       = "/etc/kubernetes/pki/ca.crt"
+	YurthubYamlName  = "yurt-hub.yaml"
+	KubeletConfName  = "kubelet.conf"
+	KubeletSvcBackup = "%s.bk"
 
 	Hostname               = "/etc/hostname"
 	KubeletHostname        = "--hostname-override=[^\"\\s]*"
@@ -124,61 +121,5 @@ spec:
   hostNetwork: true
   priorityClassName: system-node-critical
   priority: 2000001000
-`
-	YurthubClusterRole = `
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: yurt-hub
-rules:
-  - apiGroups:
-      - ""
-    resources:
-      - events
-    verbs:
-      - get
-  - apiGroups:
-      - apps.openyurt.io
-    resources:
-      - nodepools
-    verbs:
-      - list
-      - watch
-  - apiGroups:
-      - ""
-    resources:
-      - configmaps
-    resourceNames:
-      - yurt-hub-cfg
-    verbs:
-      - list
-      - watch
-`
-	YurthubClusterRoleBinding = `
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: yurt-hub
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: yurt-hub
-subjects:
-  - apiGroup: rbac.authorization.k8s.io
-    kind: Group
-    name: system:nodes
-`
-	YurthubConfigMap = `
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: yurt-hub-cfg
-  namespace: kube-system
-data:
-  cache_agents: ""
-  filter_endpoints: coredns/endpoints#list;watch
-  filter_servicetopology: coredns/endpointslices#list;watch
-  filter_discardcloudservice: ""
-  filter_masterservice: ""
 `
 )
diff --git a/pkg/yurtadm/util/kubernetes/util.go b/pkg/yurtadm/util/kubernetes/util.go
index a0838d46..3c6bcd64 100644
--- a/pkg/yurtadm/util/kubernetes/util.go
+++ b/pkg/yurtadm/util/kubernetes/util.go
@@ -18,426 +18,44 @@ package kubernetes
 
 import (
 	"bufio"
-	"bytes"
 	"context"
 	"errors"
 	"fmt"
-	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"runtime"
-	"sort"
 	"strings"
-	"sync"
 	"time"
 
 	pkgerrors "github.com/pkg/errors"
-	"github.com/spf13/pflag"
-	"k8s.io/api/admissionregistration/v1beta1"
-	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
-	corev1 "k8s.io/api/core/v1"
-	rbacv1 "k8s.io/api/rbac/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/fields"
-	k8sruntime "k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/runtime/serializer"
-	"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
-	utilerrors "k8s.io/apimachinery/pkg/util/errors"
-	yamlutil "k8s.io/apimachinery/pkg/util/yaml"
-	"k8s.io/client-go/discovery"
-	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/kubernetes/scheme"
-	"k8s.io/client-go/restmapper"
-	"k8s.io/client-go/tools/clientcmd"
 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
-	"k8s.io/client-go/util/homedir"
-	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
-	bootstraputil "k8s.io/cluster-bootstrap/token/util"
 	"k8s.io/klog/v2"
 
-	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	kubeadmconstants "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/apiclient"
+	kubeconfigutil "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/util/kubeconfig"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/constants"
-	kubeadmapi "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/apis/kubeadm"
-	kubeadmconstants "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
-	nodetoken "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/node"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/apiclient"
-	kubeconfigutil "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/util/kubeconfig"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util"
 	"github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
-	strutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/strings"
-	tmplutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/templates"
 )
 
 const (
 	// DisableNodeControllerJobNameBase is the prefix of the DisableNodeControllerJob name
 	DisableNodeControllerJobNameBase = "yurtctl-disable-node-controller"
-	// EnableNodeControllerJobNameBase is the prefix of the EnableNodeControllerJob name
-	EnableNodeControllerJobNameBase = "yurtctl-enable-node-controller"
-	SystemNamespace                 = "kube-system"
-	// DefaultWaitServantJobTimeout specifies the timeout value of waiting for the ServantJob to be succeeded
-	DefaultWaitServantJobTimeout = time.Minute * 5
 )
 
 var (
 	// PropagationPolicy defines the propagation policy used when deleting a resource
 	PropagationPolicy = metav1.DeletePropagationBackground
-	// CheckServantJobPeriod defines the time interval between two successive ServantJob statu's inspection
-	CheckServantJobPeriod = time.Second * 10
-	// ValidServerVersions contains all compatible server version
-	// yurtctl only support Kubernetes 1.12+ - 1.23+ for now
-	ValidServerVersions = []string{
-		"1.12", "1.12+",
-		"1.13", "1.13+",
-		"1.14", "1.14+",
-		"1.16", "1.16+",
-		"1.18", "1.18+",
-		"1.19", "1.19+",
-		"1.20", "1.20+",
-		"1.21", "1.21+",
-		"1.22", "1.22+",
-		"1.23", "1.23+"}
 
 	ErrClusterVersionEmpty = errors.New("cluster version should not be empty")
 )
 
-func processCreateErr(kind string, name string, err error) error {
-	if err != nil {
-		if apierrors.IsAlreadyExists(err) {
-			klog.V(4).Infof("[WARNING] %s/%s is already in cluster, skip to prepare it", kind, name)
-			return nil
-		}
-		return fmt.Errorf("fail to create the %s/%s: %w", kind, name, err)
-	}
-	klog.V(4).Infof("%s/%s is created", kind, name)
-	return nil
-}
-
-// CreateServiceAccountFromYaml creates the ServiceAccount from the yaml template.
-func CreateServiceAccountFromYaml(cliSet *kubernetes.Clientset, ns, saTmpl string) error {
-	obj, err := YamlToObject([]byte(saTmpl))
-	if err != nil {
-		return err
-	}
-	sa, ok := obj.(*corev1.ServiceAccount)
-	if !ok {
-		return fmt.Errorf("fail to assert serviceaccount: %w", err)
-	}
-	_, err = cliSet.CoreV1().ServiceAccounts(ns).Create(context.Background(), sa, metav1.CreateOptions{})
-	return processCreateErr("serviceaccount", sa.Name, err)
-}
-
-// CreateClusterRoleFromYaml creates the ClusterRole from the yaml template.
-func CreateClusterRoleFromYaml(cliSet *kubernetes.Clientset, crTmpl string) error {
-	obj, err := YamlToObject([]byte(crTmpl))
-	if err != nil {
-		return err
-	}
-	cr, ok := obj.(*rbacv1.ClusterRole)
-	if !ok {
-		return fmt.Errorf("fail to assert clusterrole: %w", err)
-	}
-	_, err = cliSet.RbacV1().ClusterRoles().Create(context.Background(), cr, metav1.CreateOptions{})
-	return processCreateErr("clusterrole", cr.Name, err)
-}
-
-// CreateClusterRoleBindingFromYaml creates the ClusterRoleBinding from the yaml template.
-func CreateClusterRoleBindingFromYaml(cliSet *kubernetes.Clientset, crbTmpl string) error {
-	obj, err := YamlToObject([]byte(crbTmpl))
-	if err != nil {
-		return err
-	}
-	crb, ok := obj.(*rbacv1.ClusterRoleBinding)
-	if !ok {
-		return fmt.Errorf("fail to assert clusterrolebinding: %w", err)
-	}
-	_, err = cliSet.RbacV1().ClusterRoleBindings().Create(context.Background(), crb, metav1.CreateOptions{})
-	return processCreateErr("clusterrolebinding", crb.Name, err)
-}
-
-// CreateConfigMapFromYaml creates the ConfigMap from the yaml template.
-func CreateConfigMapFromYaml(cliSet *kubernetes.Clientset, ns, cmTmpl string) error {
-	obj, err := YamlToObject([]byte(cmTmpl))
-	if err != nil {
-		return err
-	}
-	cm, ok := obj.(*corev1.ConfigMap)
-	if !ok {
-		return fmt.Errorf("fail to assert configmap: %w", err)
-	}
-	_, err = cliSet.CoreV1().ConfigMaps(ns).Create(context.Background(), cm, metav1.CreateOptions{})
-	return processCreateErr("configmap", cm.Name, err)
-}
-
-// CreateDeployFromYaml creates the Deployment from the yaml template.
-func CreateDeployFromYaml(cliSet *kubernetes.Clientset, ns, dplyTmpl string, ctx interface{}) error {
-	ycmdp, err := tmplutil.SubsituteTemplate(dplyTmpl, ctx)
-	if err != nil {
-		return err
-	}
-	dpObj, err := YamlToObject([]byte(ycmdp))
-	if err != nil {
-		return err
-	}
-	dply, ok := dpObj.(*appsv1.Deployment)
-	if !ok {
-		return errors.New("fail to assert Deployment")
-	}
-	_, err = cliSet.AppsV1().Deployments(ns).Create(context.Background(), dply, metav1.CreateOptions{})
-	return processCreateErr("deployment", dply.Name, err)
-}
-
-// CreateDaemonSetFromYaml creates the DaemonSet from the yaml template.
-func CreateDaemonSetFromYaml(cliSet *kubernetes.Clientset, ns, dsTmpl string, ctx interface{}) error {
-	var ytadstmp string
-	var err error
-	if ctx != nil {
-		ytadstmp, err = tmplutil.SubsituteTemplate(dsTmpl, ctx)
-		if err != nil {
-			return err
-		}
-	} else {
-		ytadstmp = dsTmpl
-	}
-
-	obj, err := YamlToObject([]byte(ytadstmp))
-	if err != nil {
-		return err
-	}
-	ds, ok := obj.(*appsv1.DaemonSet)
-	if !ok {
-		return fmt.Errorf("fail to assert daemonset: %w", err)
-	}
-	_, err = cliSet.AppsV1().DaemonSets(ns).Create(context.Background(), ds, metav1.CreateOptions{})
-	return processCreateErr("daemonset", ds.Name, err)
-}
-
-// CreateServiceFromYaml creates the Service from the yaml template.
-func CreateServiceFromYaml(cliSet *kubernetes.Clientset, ns, svcTmpl string) error {
-	obj, err := YamlToObject([]byte(svcTmpl))
-	if err != nil {
-		return err
-	}
-	svc, ok := obj.(*corev1.Service)
-	if !ok {
-		return fmt.Errorf("fail to assert service: %w", err)
-	}
-	_, err = cliSet.CoreV1().Services(ns).Create(context.Background(), svc, metav1.CreateOptions{})
-	return processCreateErr("service", svc.Name, err)
-}
-
-//add by yanyhui at 20210611
-// CreateRoleFromYaml creates the ClusterRole from the yaml template.
-func CreateRoleFromYaml(cliSet *kubernetes.Clientset, ns, crTmpl string) error {
-	obj, err := YamlToObject([]byte(crTmpl))
-	if err != nil {
-		return err
-	}
-	ro, ok := obj.(*rbacv1.Role)
-	if !ok {
-		return fmt.Errorf("fail to assert role: %w", err)
-	}
-	_, err = cliSet.RbacV1().Roles(ns).Create(context.Background(), ro, metav1.CreateOptions{})
-	return processCreateErr("role", ro.Name, err)
-}
-
-// CreateRoleBindingFromYaml creates the ClusterRoleBinding from the yaml template.
-func CreateRoleBindingFromYaml(cliSet *kubernetes.Clientset, ns, crbTmpl string) error {
-	obj, err := YamlToObject([]byte(crbTmpl))
-	if err != nil {
-		return err
-	}
-	rb, ok := obj.(*rbacv1.RoleBinding)
-	if !ok {
-		return fmt.Errorf("fail to assert rolebinding: %w", err)
-	}
-	_, err = cliSet.RbacV1().RoleBindings(ns).Create(context.Background(), rb, metav1.CreateOptions{})
-	return processCreateErr("rolebinding", rb.Name, err)
-}
-
-// CreateSecretFromYaml creates the Secret from the yaml template.
-func CreateSecretFromYaml(cliSet *kubernetes.Clientset, ns, saTmpl string) error {
-	obj, err := YamlToObject([]byte(saTmpl))
-	if err != nil {
-		return err
-	}
-	se, ok := obj.(*corev1.Secret)
-	if !ok {
-		return fmt.Errorf("fail to assert secret: %w", err)
-	}
-	_, err = cliSet.CoreV1().Secrets(ns).Create(context.Background(), se, metav1.CreateOptions{})
-
-	return processCreateErr("secret", se.Name, err)
-}
-
-// CreateMutatingWebhookConfigurationFromYaml creates the Service from the yaml template.
-func CreateMutatingWebhookConfigurationFromYaml(cliSet *kubernetes.Clientset, svcTmpl string) error {
-	obj, err := YamlToObject([]byte(svcTmpl))
-	if err != nil {
-		return err
-	}
-	mw, ok := obj.(*v1beta1.MutatingWebhookConfiguration)
-	if !ok {
-		return fmt.Errorf("fail to assert mutatingwebhookconfiguration: %w", err)
-	}
-	_, err = cliSet.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(context.Background(), mw, metav1.CreateOptions{})
-	return processCreateErr("mutatingwebhookconfiguration", mw.Name, err)
-}
-
-// CreateValidatingWebhookConfigurationFromYaml creates the Service from the yaml template.
-func CreateValidatingWebhookConfigurationFromYaml(cliSet *kubernetes.Clientset, svcTmpl string) error {
-	obj, err := YamlToObject([]byte(svcTmpl))
-	if err != nil {
-		return err
-	}
-	vw, ok := obj.(*v1beta1.ValidatingWebhookConfiguration)
-	if !ok {
-		return fmt.Errorf("fail to assert validatingwebhookconfiguration: %w", err)
-	}
-	_, err = cliSet.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(context.Background(), vw, metav1.CreateOptions{})
-	return processCreateErr("validatingwebhookconfiguration", vw.Name, err)
-}
-
-func CreateCRDFromYaml(clientset *kubernetes.Clientset, yurtAppManagerClient dynamic.Interface, nameSpace string, filebytes []byte) error {
-	var err error
-	decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(filebytes), 10000)
-	var rawObj k8sruntime.RawExtension
-	err = decoder.Decode(&rawObj)
-	if err != nil {
-		return err
-	}
-	obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(rawObj.Raw, nil, nil)
-	if err != nil {
-		return err
-	}
-	unstructuredMap, err := k8sruntime.DefaultUnstructuredConverter.ToUnstructured(obj)
-	if err != nil {
-		return err
-	}
-	unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
-	gr, err := restmapper.GetAPIGroupResources(clientset.Discovery())
-	if err != nil {
-		return err
-	}
-
-	mapper := restmapper.NewDiscoveryRESTMapper(gr)
-	mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
-	if err != nil {
-		return err
-	}
-
-	var dri dynamic.ResourceInterface
-	if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
-		if unstructuredObj.GetNamespace() == "" {
-			unstructuredObj.SetNamespace(nameSpace)
-		}
-		dri = yurtAppManagerClient.Resource(mapping.Resource).Namespace(unstructuredObj.GetNamespace())
-	} else {
-		dri = yurtAppManagerClient.Resource(mapping.Resource)
-	}
-
-	objSecond, err := dri.Create(context.Background(), unstructuredObj, metav1.CreateOptions{})
-	if err != nil {
-		return err
-	} else {
-		fmt.Printf("%s/%s created", objSecond.GetKind(), objSecond.GetName())
-	}
-	return nil
-}
-
-func DeleteCRDResource(clientset *kubernetes.Clientset, yurtAppManagerClientSet dynamic.Interface, res string, name string, filebytes []byte) error {
-	var err error
-	decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(filebytes), 10000)
-	var rawObj k8sruntime.RawExtension
-	err = decoder.Decode(&rawObj)
-	if err != nil {
-		return err
-	}
-	obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(rawObj.Raw, nil, nil)
-	if err != nil {
-		return err
-	}
-	unstructuredMap, err := k8sruntime.DefaultUnstructuredConverter.ToUnstructured(obj)
-	if err != nil {
-		return err
-	}
-	unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
-	gr, err := restmapper.GetAPIGroupResources(clientset.Discovery())
-	if err != nil {
-		return err
-	}
-
-	mapper := restmapper.NewDiscoveryRESTMapper(gr)
-	mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
-	if err != nil {
-		return err
-	}
-
-	var dri dynamic.ResourceInterface
-	if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
-		if unstructuredObj.GetNamespace() == "" {
-			unstructuredObj.SetNamespace("")
-		}
-		dri = yurtAppManagerClientSet.Resource(mapping.Resource).Namespace(unstructuredObj.GetNamespace())
-	} else {
-		dri = yurtAppManagerClientSet.Resource(mapping.Resource)
-	}
-	err = dri.Delete(context.Background(), name, metav1.DeleteOptions{})
-	if err != nil {
-		if !apierrors.IsNotFound(err) {
-			return err
-		}
-	} else {
-		fmt.Printf("%s/%s is deleted ", res, name)
-	}
-	return nil
-}
-
-// YamlToObject deserializes object in yaml format to a runtime.Object
-func YamlToObject(yamlContent []byte) (k8sruntime.Object, error) {
-	decode := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer().Decode
-	obj, _, err := decode(yamlContent, nil, nil)
-	if err != nil {
-		return nil, err
-	}
-	return obj, nil
-}
-
-// LabelNode add a new label (<key>=<val>) to the given node
-func LabelNode(cliSet *kubernetes.Clientset, node *corev1.Node, key, val string) (*corev1.Node, error) {
-	node.Labels[key] = val
-	newNode, err := cliSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{})
-	if err != nil {
-		return nil, err
-	}
-	return newNode, nil
-}
-
-// AnnotateNode add a new annotation (<key>=<val>) to the given node
-func AnnotateNode(cliSet *kubernetes.Clientset, node *corev1.Node, key, val string) (*corev1.Node, error) {
-	node.Annotations[key] = val
-	newNode, err := cliSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{})
-	if err != nil {
-		return nil, err
-	}
-	return newNode, nil
-}
-
-func AddEdgeWorkerLabelAndAutonomyAnnotation(cliSet *kubernetes.Clientset, node *corev1.Node, lVal, aVal string) (*corev1.Node, error) {
-	node.Labels[projectinfo.GetEdgeWorkerLabelKey()] = lVal
-	node.Annotations[projectinfo.GetAutonomyAnnotation()] = aVal
-	newNode, err := cliSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{})
-	if err != nil {
-		return nil, err
-	}
-	return newNode, nil
-}
-
 // RunJobAndCleanup runs the job, wait for it to be complete, and delete it
 func RunJobAndCleanup(cliSet *kubernetes.Clientset, job *batchv1.Job, timeout, period time.Duration, waitForTimeout bool) error {
 	job, err := cliSet.BatchV1().Jobs(job.GetNamespace()).Create(context.Background(), job, metav1.CreateOptions{})
@@ -478,247 +96,6 @@ func RunJobAndCleanup(cliSet *kubernetes.Clientset, job *batchv1.Job, timeout, p
 	}
 }
 
-// RenderServantJob renders servant job for a specified node.
-func RenderServantJob(action string, tmplCtx map[string]string, nodeName string) (*batchv1.Job, error) {
-	var jobTemplate string
-	switch action {
-	case "enable":
-		jobTemplate = constants.EnableNodeControllerJobTemplate
-		tmplCtx["jobName"] = EnableNodeControllerJobNameBase + "-" + nodeName
-	case "disable":
-		jobTemplate = constants.DisableNodeControllerJobTemplate
-		tmplCtx["jobName"] = DisableNodeControllerJobNameBase + "-" + nodeName
-	default:
-		return nil, fmt.Errorf("unknown action: %s", action)
-	}
-	tmplCtx["nodeName"] = nodeName
-	jobYaml, err := tmplutil.SubsituteTemplate(jobTemplate, tmplCtx)
-	if err != nil {
-		return nil, err
-	}
-	srvJobObj, err := YamlToObject([]byte(jobYaml))
-	if err != nil {
-		return nil, err
-	}
-	srvJob, ok := srvJobObj.(*batchv1.Job)
-	if !ok {
-		return nil, errors.New("fail to assert yurtctl-servant job")
-	}
-	return srvJob, nil
-}
-
-// RunServantJobs launch servant jobs on specified nodes and wait all jobs to finish.
-// Succeed jobs will be deleted when finished. Failed jobs are preserved for diagnosis.
-func RunServantJobs(
-	cliSet *kubernetes.Clientset,
-	waitServantJobTimeout time.Duration,
-	getJob func(nodeName string) (*batchv1.Job, error),
-	nodeNames []string, ww io.Writer,
-	waitForTimeout bool) error {
-	var wg sync.WaitGroup
-
-	jobByNodeName := make(map[string]*batchv1.Job)
-	for _, nodeName := range nodeNames {
-		job, err := getJob(nodeName)
-		if err != nil {
-			return fmt.Errorf("fail to get job for node %s: %w", nodeName, err)
-		}
-		jobByNodeName[nodeName] = job
-	}
-
-	res := make(chan string, len(nodeNames))
-	errCh := make(chan error, len(nodeNames))
-	for _, nodeName := range nodeNames {
-		wg.Add(1)
-		job := jobByNodeName[nodeName]
-		go func() {
-			defer wg.Done()
-			if err := RunJobAndCleanup(cliSet, job, waitServantJobTimeout, CheckServantJobPeriod, waitForTimeout); err != nil {
-				errCh <- fmt.Errorf("[ERROR] fail to run servant job(%s): %w", job.GetName(), err)
-			} else {
-				res <- fmt.Sprintf("\t[INFO] servant job(%s) has succeeded\n", job.GetName())
-			}
-		}()
-	}
-	wg.Wait()
-	close(res)
-	close(errCh)
-	for m := range res {
-		io.WriteString(ww, m)
-	}
-
-	errs := []error{}
-	for err := range errCh {
-		errs = append(errs, err)
-	}
-	if len(errs) != 0 {
-		return utilerrors.NewAggregate(errs)
-	}
-
-	return nil
-}
-
-// ValidateServerVersion checks if the target server's version is supported
-func ValidateServerVersion(cliSet *kubernetes.Clientset) error {
-	serverVersion, err := discovery.
-		NewDiscoveryClient(cliSet.RESTClient()).ServerVersion()
-	if err != nil {
-		return err
-	}
-	completeVersion := serverVersion.Major + "." + serverVersion.Minor
-	if !strutil.IsInStringLst(ValidServerVersions, completeVersion) {
-		return fmt.Errorf("server version(%s) is not supported, valid server versions are %v",
-			completeVersion, ValidServerVersions)
-	}
-	return nil
-}
-
-// GenClientSet generates the clientset based on command option, environment variable or
-// the default kubeconfig file
-func GenClientSet(flags *pflag.FlagSet) (*kubernetes.Clientset, error) {
-	kubeconfigPath, err := PrepareKubeConfigPath(flags)
-	if err != nil {
-		return nil, err
-	}
-
-	restCfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
-	if err != nil {
-		return nil, err
-	}
-
-	return kubernetes.NewForConfig(restCfg)
-}
-
-// GenDynamicClientSet generates the clientset based on command option, environment variable or
-// the default kubeconfig file
-func GenDynamicClientSet(flags *pflag.FlagSet) (dynamic.Interface, error) {
-	kubeconfigPath, err := PrepareKubeConfigPath(flags)
-	if err != nil {
-		return nil, err
-	}
-
-	restCfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
-	if err != nil {
-		return nil, err
-	}
-
-	return dynamic.NewForConfig(restCfg)
-}
-
-// PrepareKubeConfigPath returns the path of cluster kubeconfig file
-func PrepareKubeConfigPath(flags *pflag.FlagSet) (string, error) {
-	kbCfgPath, err := flags.GetString("kubeconfig")
-	if err != nil {
-		return "", err
-	}
-
-	if kbCfgPath == "" {
-		kbCfgPath = os.Getenv("KUBECONFIG")
-	}
-
-	if kbCfgPath == "" {
-		if home := homedir.HomeDir(); home != "" {
-			kbCfgPath = filepath.Join(home, ".kube", "config")
-		}
-	}
-
-	if kbCfgPath == "" {
-		return "", errors.New("either '--kubeconfig', '$HOME/.kube/config' or '$KUBECONFIG' need to be set")
-	}
-
-	return kbCfgPath, nil
-}
-
-func GetOrCreateJoinTokenString(cliSet *kubernetes.Clientset) (string, error) {
-	tokenSelector := fields.SelectorFromSet(
-		map[string]string{
-			// TODO: We hard-code "type" here until `field_constants.go` that is
-			// currently in `pkg/apis/core/` exists in the external API, i.e.
-			// k8s.io/api/v1. Should be v1.SecretTypeField
-			"type": string(bootstrapapi.SecretTypeBootstrapToken),
-		},
-	)
-	listOptions := metav1.ListOptions{
-		FieldSelector: tokenSelector.String(),
-	}
-	klog.V(1).Infoln("[token] retrieving list of bootstrap tokens")
-	secrets, err := cliSet.CoreV1().Secrets(metav1.NamespaceSystem).List(context.Background(), listOptions)
-	if err != nil {
-		return "", fmt.Errorf("%w%s", err, "failed to list bootstrap tokens")
-	}
-
-	for _, secret := range secrets.Items {
-
-		// Get the BootstrapToken struct representation from the Secret object
-		token, err := kubeadmapi.BootstrapTokenFromSecret(&secret)
-		if err != nil {
-			klog.Warningf("%v", err)
-			continue
-		}
-		if !usagesAndGroupsAreValid(token) {
-			continue
-		}
-
-		return token.Token.String(), nil
-		// Get the human-friendly string representation for the token
-	}
-
-	tokenStr, err := bootstraputil.GenerateBootstrapToken()
-	if err != nil {
-		return "", fmt.Errorf("couldn't generate random token, %w", err)
-	}
-	token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
-	if err != nil {
-		return "", err
-	}
-
-	klog.V(1).Infoln("[token] creating token")
-	if err := nodetoken.CreateNewTokens(cliSet,
-		[]kubeadmapi.BootstrapToken{{
-			Token:  token,
-			Usages: kubeadmconstants.DefaultTokenUsages,
-			Groups: kubeadmconstants.DefaultTokenGroups,
-		}}); err != nil {
-		return "", err
-	}
-	return tokenStr, nil
-}
-
-// usagesAndGroupsAreValid checks if the usages and groups in the given bootstrap token are valid
-func usagesAndGroupsAreValid(token *kubeadmapi.BootstrapToken) bool {
-	sliceEqual := func(a, b []string) bool {
-		if len(a) != len(b) {
-			return false
-		}
-		sort.Strings(a)
-		sort.Strings(b)
-		for k, v := range b {
-			if a[k] != v {
-				return false
-			}
-		}
-		return true
-	}
-
-	return sliceEqual(token.Usages, kubeadmconstants.DefaultTokenUsages) && sliceEqual(token.Groups, kubeadmconstants.DefaultTokenGroups)
-}
-
-// find kube-controller-manager deployed through static file
-func GetKubeControllerManagerHANodes(cliSet *kubernetes.Clientset) ([]string, error) {
-	var kcmNodeNames []string
-	podLst, err := cliSet.CoreV1().Pods(SystemNamespace).List(context.Background(), metav1.ListOptions{})
-	if err != nil {
-		return nil, err
-	}
-	for _, pod := range podLst.Items {
-		kcmPodName := fmt.Sprintf("kube-controller-manager-%s", pod.Spec.NodeName)
-		if kcmPodName == pod.Name {
-			kcmNodeNames = append(kcmNodeNames, pod.Spec.NodeName)
-		}
-	}
-	return kcmNodeNames, nil
-}
-
 //CheckAndInstallKubelet install kubelet and kubernetes-cni, skip install if they exist.
 func CheckAndInstallKubelet(kubernetesResourceServer, clusterVersion string) error {
 	if strings.Contains(clusterVersion, "-") {
diff --git a/pkg/yurtctl/cmd/clusterinfo/clusterinfo.go b/pkg/yurtctl/cmd/clusterinfo/clusterinfo.go
index 3d84c40a..cfb4b956 100644
--- a/pkg/yurtctl/cmd/clusterinfo/clusterinfo.go
+++ b/pkg/yurtctl/cmd/clusterinfo/clusterinfo.go
@@ -30,7 +30,7 @@ import (
 	"k8s.io/klog/v2"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
-	kubeutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/kubernetes"
+	kubeutil "github.com/openyurtio/openyurt/pkg/yurtctl/util/kubernetes"
 )
 
 // ClusterInfoOptions has the information that required by cluster-info operation
diff --git a/pkg/yurtctl/cmd/markautonomous/markautonomous.go b/pkg/yurtctl/cmd/markautonomous/markautonomous.go
index 30201d67..79c14fd3 100644
--- a/pkg/yurtctl/cmd/markautonomous/markautonomous.go
+++ b/pkg/yurtctl/cmd/markautonomous/markautonomous.go
@@ -29,9 +29,9 @@ import (
 	"k8s.io/klog/v2"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
-	kubeutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/kubernetes"
 	"github.com/openyurtio/openyurt/pkg/yurtctl/constants"
 	"github.com/openyurtio/openyurt/pkg/yurtctl/lock"
+	kubeutil "github.com/openyurtio/openyurt/pkg/yurtctl/util/kubernetes"
 )
 
 // MarkAutonomousOptions has the information that required by convert operation
diff --git a/pkg/yurtctl/constants/constants.go b/pkg/yurtctl/constants/constants.go
index ef0b9d53..9e69b274 100644
--- a/pkg/yurtctl/constants/constants.go
+++ b/pkg/yurtctl/constants/constants.go
@@ -32,4 +32,220 @@ const (
 
 	DirMode  = 0755
 	FileMode = 0666
+
+	YurthubComponentName                = "yurt-hub"
+	YurthubNamespace                    = "kube-system"
+	YurthubCmName                       = "yurt-hub-cfg"
+	YurtControllerManagerServiceAccount = `
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: yurt-controller-manager
+  namespace: kube-system
+`
+	// YurtControllerManagerClusterRole has the same privilege as the
+	// system:controller:node-controller and has the right to manipulate
+	// the leases resource
+	YurtControllerManagerClusterRole = `
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  annotations:
+    rbac.authorization.kubernetes.io/autoupdate: "true"
+  name: yurt-controller-manager 
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - nodes
+  verbs:
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - nodes/status
+  verbs:
+  - patch
+  - update
+- apiGroups:
+  - ""
+  resources:
+  - pods/status
+  verbs:
+  - update
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - delete
+  - list
+  - watch
+- apiGroups:
+  - ""
+  - events.k8s.io
+  resources:
+  - events
+  verbs:
+  - create
+  - patch
+  - update
+- apiGroups:
+  - coordination.k8s.io
+  resources:
+  - leases
+  verbs:
+  - create
+  - delete
+  - get
+  - patch
+  - update
+  - list
+  - watch
+- apiGroups:
+  - ""
+  - apps
+  resources:
+  - daemonsets
+  verbs:
+  - list
+  - watch
+- apiGroups:
+    - certificates.k8s.io
+  resources:
+    - certificatesigningrequests
+  verbs:
+    - get
+    - list
+    - watch
+- apiGroups:
+    - certificates.k8s.io
+  resources:
+    - certificatesigningrequests/approval
+  verbs:
+    - update
+- apiGroups:
+    - certificates.k8s.io
+  resourceNames:
+    - kubernetes.io/kube-apiserver-client
+    - kubernetes.io/kubelet-serving
+  resources:
+    - signers
+  verbs:
+    - approve
+`
+	YurtControllerManagerClusterRoleBinding = `
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: yurt-controller-manager 
+subjects:
+  - kind: ServiceAccount
+    name: yurt-controller-manager
+    namespace: kube-system
+roleRef:
+  kind: ClusterRole
+  name: yurt-controller-manager 
+  apiGroup: rbac.authorization.k8s.io
+`
+	// YurtControllerManagerDeployment defines the yurt controller manager
+	// deployment in yaml format
+	YurtControllerManagerDeployment = `
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: yurt-controller-manager
+  namespace: kube-system
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: yurt-controller-manager
+  template:
+    metadata:
+      labels:
+        app: yurt-controller-manager
+    spec:
+      serviceAccountName: yurt-controller-manager
+      hostNetwork: true
+      tolerations:
+      - operator: "Exists"
+      affinity:
+        nodeAffinity:
+          # we prefer allocating ecm on cloud node
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - weight: 1
+            preference:
+              matchExpressions:
+              - key: {{.edgeNodeLabel}}
+                operator: In
+                values:
+                - "false"
+      containers:
+      - name: yurt-controller-manager
+        image: {{.image}}
+        command:
+        - yurt-controller-manager	
+`
+	YurthubClusterRole = `
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: yurt-hub
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - events
+    verbs:
+      - get
+  - apiGroups:
+      - apps.openyurt.io
+    resources:
+      - nodepools
+    verbs:
+      - list
+      - watch
+  - apiGroups:
+      - ""
+    resources:
+      - configmaps
+    resourceNames:
+      - yurt-hub-cfg
+    verbs:
+      - list
+      - watch
+`
+	YurthubClusterRoleBinding = `
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: yurt-hub
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: yurt-hub
+subjects:
+  - apiGroup: rbac.authorization.k8s.io
+    kind: Group
+    name: system:nodes
+`
+	YurthubConfigMap = `
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: yurt-hub-cfg
+  namespace: kube-system
+data:
+  cache_agents: ""
+  filter_endpoints: coredns/endpoints#list;watch
+  filter_servicetopology: coredns/endpointslices#list;watch
+  filter_discardcloudservice: ""
+  filter_masterservice: ""
+`
 )
diff --git a/pkg/yurtadm/constants/yurt-app-manager-tmpl.go b/pkg/yurtctl/constants/yurt-app-manager-tmpl.go
similarity index 100%
rename from pkg/yurtadm/constants/yurt-app-manager-tmpl.go
rename to pkg/yurtctl/constants/yurt-app-manager-tmpl.go
diff --git a/pkg/yurtadm/constants/yurt-tunnel-agent-tmpl.go b/pkg/yurtctl/constants/yurt-tunnel-agent-tmpl.go
similarity index 100%
rename from pkg/yurtadm/constants/yurt-tunnel-agent-tmpl.go
rename to pkg/yurtctl/constants/yurt-tunnel-agent-tmpl.go
diff --git a/pkg/yurtadm/constants/yurt-tunnel-server-tmpl.go b/pkg/yurtctl/constants/yurt-tunnel-server-tmpl.go
similarity index 100%
rename from pkg/yurtadm/constants/yurt-tunnel-server-tmpl.go
rename to pkg/yurtctl/constants/yurt-tunnel-server-tmpl.go
diff --git a/pkg/yurtadm/util/kubernetes/apply_addons.go b/pkg/yurtctl/util/kubernetes/apply_addons.go
similarity index 91%
rename from pkg/yurtadm/util/kubernetes/apply_addons.go
rename to pkg/yurtctl/util/kubernetes/apply_addons.go
index 903f163e..8d97a194 100644
--- a/pkg/yurtadm/util/kubernetes/apply_addons.go
+++ b/pkg/yurtctl/util/kubernetes/apply_addons.go
@@ -26,8 +26,7 @@ import (
 	"k8s.io/client-go/kubernetes"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/constants"
-	"github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
+	"github.com/openyurtio/openyurt/pkg/yurtctl/constants"
 )
 
 func DeployYurtControllerManager(client *kubernetes.Clientset, yurtControllerManagerImage string) error {
@@ -225,19 +224,19 @@ func DeployYurttunnelAgent(
 // DeployYurthubSetting deploy clusterrole, clusterrolebinding for yurthub static pod.
 func DeployYurthubSetting(client *kubernetes.Clientset) error {
 	// 1. create the ClusterRole
-	if err := CreateClusterRoleFromYaml(client, edgenode.YurthubClusterRole); err != nil {
+	if err := CreateClusterRoleFromYaml(client, constants.YurthubClusterRole); err != nil {
 		return err
 	}
 
 	// 2. create the ClusterRoleBinding
-	if err := CreateClusterRoleBindingFromYaml(client, edgenode.YurthubClusterRoleBinding); err != nil {
+	if err := CreateClusterRoleBindingFromYaml(client, constants.YurthubClusterRoleBinding); err != nil {
 		return err
 	}
 
 	// 3. create the Configmap
 	if err := CreateConfigMapFromYaml(client,
 		SystemNamespace,
-		edgenode.YurthubConfigMap); err != nil {
+		constants.YurthubConfigMap); err != nil {
 		return err
 	}
 
@@ -249,26 +248,26 @@ func DeleteYurthubSetting(client *kubernetes.Clientset) error {
 
 	// 1. delete the ClusterRoleBinding
 	if err := client.RbacV1().ClusterRoleBindings().
-		Delete(context.Background(), edgenode.YurthubComponentName,
+		Delete(context.Background(), constants.YurthubComponentName,
 			metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
 		return fmt.Errorf("fail to delete the clusterrolebinding/%s: %w",
-			edgenode.YurthubComponentName, err)
+			constants.YurthubComponentName, err)
 	}
 
 	// 2. delete the ClusterRole
 	if err := client.RbacV1().ClusterRoles().
-		Delete(context.Background(), edgenode.YurthubComponentName,
+		Delete(context.Background(), constants.YurthubComponentName,
 			metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
 		return fmt.Errorf("fail to delete the clusterrole/%s: %w",
-			edgenode.YurthubComponentName, err)
+			constants.YurthubComponentName, err)
 	}
 
 	// 3. remove the ConfigMap
-	if err := client.CoreV1().ConfigMaps(edgenode.YurthubNamespace).
-		Delete(context.Background(), edgenode.YurthubCmName,
+	if err := client.CoreV1().ConfigMaps(constants.YurthubNamespace).
+		Delete(context.Background(), constants.YurthubCmName,
 			metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
 		return fmt.Errorf("fail to delete the configmap/%s: %w",
-			edgenode.YurthubCmName, err)
+			constants.YurthubCmName, err)
 	}
 
 	return nil
diff --git a/pkg/yurtctl/util/kubernetes/util.go b/pkg/yurtctl/util/kubernetes/util.go
new file mode 100644
index 00000000..86074952
--- /dev/null
+++ b/pkg/yurtctl/util/kubernetes/util.go
@@ -0,0 +1,554 @@
+/*
+Copyright 2020 The OpenYurt Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kubernetes
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"sort"
+	"sync"
+	"time"
+
+	"github.com/spf13/pflag"
+	"k8s.io/api/admissionregistration/v1beta1"
+	appsv1 "k8s.io/api/apps/v1"
+	batchv1 "k8s.io/api/batch/v1"
+	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/fields"
+	k8sruntime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
+	utilerrors "k8s.io/apimachinery/pkg/util/errors"
+	yamlutil "k8s.io/apimachinery/pkg/util/yaml"
+	"k8s.io/client-go/dynamic"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/restmapper"
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/client-go/util/homedir"
+	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
+	bootstraputil "k8s.io/cluster-bootstrap/token/util"
+	"k8s.io/klog/v2"
+
+	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	kubeadmapi "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/apis/kubeadm"
+	kubeadmconstants "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
+	nodetoken "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/node"
+	tmplutil "github.com/openyurtio/openyurt/pkg/util/templates"
+)
+
+const (
+	SystemNamespace = "kube-system"
+	// DefaultWaitServantJobTimeout specifies the timeout value of waiting for the ServantJob to be succeeded
+	DefaultWaitServantJobTimeout = time.Minute * 5
+)
+
+var (
+	// PropagationPolicy defines the propagation policy used when deleting a resource
+	PropagationPolicy = metav1.DeletePropagationBackground
+	// CheckServantJobPeriod defines the time interval between two successive ServantJob statu's inspection
+	CheckServantJobPeriod = time.Second * 10
+)
+
+func processCreateErr(kind string, name string, err error) error {
+	if err != nil {
+		if apierrors.IsAlreadyExists(err) {
+			klog.V(4).Infof("[WARNING] %s/%s is already in cluster, skip to prepare it", kind, name)
+			return nil
+		}
+		return fmt.Errorf("fail to create the %s/%s: %w", kind, name, err)
+	}
+	klog.V(4).Infof("%s/%s is created", kind, name)
+	return nil
+}
+
+// CreateServiceAccountFromYaml creates the ServiceAccount from the yaml template.
+func CreateServiceAccountFromYaml(cliSet *kubernetes.Clientset, ns, saTmpl string) error {
+	obj, err := YamlToObject([]byte(saTmpl))
+	if err != nil {
+		return err
+	}
+	sa, ok := obj.(*corev1.ServiceAccount)
+	if !ok {
+		return fmt.Errorf("fail to assert serviceaccount: %w", err)
+	}
+	_, err = cliSet.CoreV1().ServiceAccounts(ns).Create(context.Background(), sa, metav1.CreateOptions{})
+	return processCreateErr("serviceaccount", sa.Name, err)
+}
+
+// CreateClusterRoleFromYaml creates the ClusterRole from the yaml template.
+func CreateClusterRoleFromYaml(cliSet *kubernetes.Clientset, crTmpl string) error {
+	obj, err := YamlToObject([]byte(crTmpl))
+	if err != nil {
+		return err
+	}
+	cr, ok := obj.(*rbacv1.ClusterRole)
+	if !ok {
+		return fmt.Errorf("fail to assert clusterrole: %w", err)
+	}
+	_, err = cliSet.RbacV1().ClusterRoles().Create(context.Background(), cr, metav1.CreateOptions{})
+	return processCreateErr("clusterrole", cr.Name, err)
+}
+
+// CreateClusterRoleBindingFromYaml creates the ClusterRoleBinding from the yaml template.
+func CreateClusterRoleBindingFromYaml(cliSet *kubernetes.Clientset, crbTmpl string) error {
+	obj, err := YamlToObject([]byte(crbTmpl))
+	if err != nil {
+		return err
+	}
+	crb, ok := obj.(*rbacv1.ClusterRoleBinding)
+	if !ok {
+		return fmt.Errorf("fail to assert clusterrolebinding: %w", err)
+	}
+	_, err = cliSet.RbacV1().ClusterRoleBindings().Create(context.Background(), crb, metav1.CreateOptions{})
+	return processCreateErr("clusterrolebinding", crb.Name, err)
+}
+
+// CreateConfigMapFromYaml creates the ConfigMap from the yaml template.
+func CreateConfigMapFromYaml(cliSet *kubernetes.Clientset, ns, cmTmpl string) error {
+	obj, err := YamlToObject([]byte(cmTmpl))
+	if err != nil {
+		return err
+	}
+	cm, ok := obj.(*corev1.ConfigMap)
+	if !ok {
+		return fmt.Errorf("fail to assert configmap: %w", err)
+	}
+	_, err = cliSet.CoreV1().ConfigMaps(ns).Create(context.Background(), cm, metav1.CreateOptions{})
+	return processCreateErr("configmap", cm.Name, err)
+}
+
+// CreateDeployFromYaml creates the Deployment from the yaml template.
+func CreateDeployFromYaml(cliSet *kubernetes.Clientset, ns, dplyTmpl string, ctx interface{}) error {
+	ycmdp, err := tmplutil.SubsituteTemplate(dplyTmpl, ctx)
+	if err != nil {
+		return err
+	}
+	dpObj, err := YamlToObject([]byte(ycmdp))
+	if err != nil {
+		return err
+	}
+	dply, ok := dpObj.(*appsv1.Deployment)
+	if !ok {
+		return errors.New("fail to assert Deployment")
+	}
+	_, err = cliSet.AppsV1().Deployments(ns).Create(context.Background(), dply, metav1.CreateOptions{})
+	return processCreateErr("deployment", dply.Name, err)
+}
+
+// CreateDaemonSetFromYaml creates the DaemonSet from the yaml template.
+func CreateDaemonSetFromYaml(cliSet *kubernetes.Clientset, ns, dsTmpl string, ctx interface{}) error {
+	var ytadstmp string
+	var err error
+	if ctx != nil {
+		ytadstmp, err = tmplutil.SubsituteTemplate(dsTmpl, ctx)
+		if err != nil {
+			return err
+		}
+	} else {
+		ytadstmp = dsTmpl
+	}
+
+	obj, err := YamlToObject([]byte(ytadstmp))
+	if err != nil {
+		return err
+	}
+	ds, ok := obj.(*appsv1.DaemonSet)
+	if !ok {
+		return fmt.Errorf("fail to assert daemonset: %w", err)
+	}
+	_, err = cliSet.AppsV1().DaemonSets(ns).Create(context.Background(), ds, metav1.CreateOptions{})
+	return processCreateErr("daemonset", ds.Name, err)
+}
+
+// CreateServiceFromYaml creates the Service from the yaml template.
+func CreateServiceFromYaml(cliSet *kubernetes.Clientset, ns, svcTmpl string) error {
+	obj, err := YamlToObject([]byte(svcTmpl))
+	if err != nil {
+		return err
+	}
+	svc, ok := obj.(*corev1.Service)
+	if !ok {
+		return fmt.Errorf("fail to assert service: %w", err)
+	}
+	_, err = cliSet.CoreV1().Services(ns).Create(context.Background(), svc, metav1.CreateOptions{})
+	return processCreateErr("service", svc.Name, err)
+}
+
+//add by yanyhui at 20210611
+// CreateRoleFromYaml creates the ClusterRole from the yaml template.
+func CreateRoleFromYaml(cliSet *kubernetes.Clientset, ns, crTmpl string) error {
+	obj, err := YamlToObject([]byte(crTmpl))
+	if err != nil {
+		return err
+	}
+	ro, ok := obj.(*rbacv1.Role)
+	if !ok {
+		return fmt.Errorf("fail to assert role: %w", err)
+	}
+	_, err = cliSet.RbacV1().Roles(ns).Create(context.Background(), ro, metav1.CreateOptions{})
+	return processCreateErr("role", ro.Name, err)
+}
+
+// CreateRoleBindingFromYaml creates the ClusterRoleBinding from the yaml template.
+func CreateRoleBindingFromYaml(cliSet *kubernetes.Clientset, ns, crbTmpl string) error {
+	obj, err := YamlToObject([]byte(crbTmpl))
+	if err != nil {
+		return err
+	}
+	rb, ok := obj.(*rbacv1.RoleBinding)
+	if !ok {
+		return fmt.Errorf("fail to assert rolebinding: %w", err)
+	}
+	_, err = cliSet.RbacV1().RoleBindings(ns).Create(context.Background(), rb, metav1.CreateOptions{})
+	return processCreateErr("rolebinding", rb.Name, err)
+}
+
+// CreateSecretFromYaml creates the Secret from the yaml template.
+func CreateSecretFromYaml(cliSet *kubernetes.Clientset, ns, saTmpl string) error {
+	obj, err := YamlToObject([]byte(saTmpl))
+	if err != nil {
+		return err
+	}
+	se, ok := obj.(*corev1.Secret)
+	if !ok {
+		return fmt.Errorf("fail to assert secret: %w", err)
+	}
+	_, err = cliSet.CoreV1().Secrets(ns).Create(context.Background(), se, metav1.CreateOptions{})
+
+	return processCreateErr("secret", se.Name, err)
+}
+
+// CreateMutatingWebhookConfigurationFromYaml creates the Service from the yaml template.
+func CreateMutatingWebhookConfigurationFromYaml(cliSet *kubernetes.Clientset, svcTmpl string) error {
+	obj, err := YamlToObject([]byte(svcTmpl))
+	if err != nil {
+		return err
+	}
+	mw, ok := obj.(*v1beta1.MutatingWebhookConfiguration)
+	if !ok {
+		return fmt.Errorf("fail to assert mutatingwebhookconfiguration: %w", err)
+	}
+	_, err = cliSet.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(context.Background(), mw, metav1.CreateOptions{})
+	return processCreateErr("mutatingwebhookconfiguration", mw.Name, err)
+}
+
+// CreateValidatingWebhookConfigurationFromYaml creates the Service from the yaml template.
+func CreateValidatingWebhookConfigurationFromYaml(cliSet *kubernetes.Clientset, svcTmpl string) error {
+	obj, err := YamlToObject([]byte(svcTmpl))
+	if err != nil {
+		return err
+	}
+	vw, ok := obj.(*v1beta1.ValidatingWebhookConfiguration)
+	if !ok {
+		return fmt.Errorf("fail to assert validatingwebhookconfiguration: %w", err)
+	}
+	_, err = cliSet.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(context.Background(), vw, metav1.CreateOptions{})
+	return processCreateErr("validatingwebhookconfiguration", vw.Name, err)
+}
+
+func CreateCRDFromYaml(clientset *kubernetes.Clientset, yurtAppManagerClient dynamic.Interface, nameSpace string, filebytes []byte) error {
+	var err error
+	decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(filebytes), 10000)
+	var rawObj k8sruntime.RawExtension
+	err = decoder.Decode(&rawObj)
+	if err != nil {
+		return err
+	}
+	obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(rawObj.Raw, nil, nil)
+	if err != nil {
+		return err
+	}
+	unstructuredMap, err := k8sruntime.DefaultUnstructuredConverter.ToUnstructured(obj)
+	if err != nil {
+		return err
+	}
+	unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
+	gr, err := restmapper.GetAPIGroupResources(clientset.Discovery())
+	if err != nil {
+		return err
+	}
+
+	mapper := restmapper.NewDiscoveryRESTMapper(gr)
+	mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
+	if err != nil {
+		return err
+	}
+
+	var dri dynamic.ResourceInterface
+	if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
+		if unstructuredObj.GetNamespace() == "" {
+			unstructuredObj.SetNamespace(nameSpace)
+		}
+		dri = yurtAppManagerClient.Resource(mapping.Resource).Namespace(unstructuredObj.GetNamespace())
+	} else {
+		dri = yurtAppManagerClient.Resource(mapping.Resource)
+	}
+
+	objSecond, err := dri.Create(context.Background(), unstructuredObj, metav1.CreateOptions{})
+	if err != nil {
+		return err
+	} else {
+		fmt.Printf("%s/%s created", objSecond.GetKind(), objSecond.GetName())
+	}
+	return nil
+}
+
+// YamlToObject deserializes object in yaml format to a runtime.Object
+func YamlToObject(yamlContent []byte) (k8sruntime.Object, error) {
+	decode := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer().Decode
+	obj, _, err := decode(yamlContent, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	return obj, nil
+}
+
+// AnnotateNode add a new annotation (<key>=<val>) to the given node
+func AnnotateNode(cliSet *kubernetes.Clientset, node *corev1.Node, key, val string) (*corev1.Node, error) {
+	node.Annotations[key] = val
+	newNode, err := cliSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{})
+	if err != nil {
+		return nil, err
+	}
+	return newNode, nil
+}
+
+func AddEdgeWorkerLabelAndAutonomyAnnotation(cliSet *kubernetes.Clientset, node *corev1.Node, lVal, aVal string) (*corev1.Node, error) {
+	node.Labels[projectinfo.GetEdgeWorkerLabelKey()] = lVal
+	node.Annotations[projectinfo.GetAutonomyAnnotation()] = aVal
+	newNode, err := cliSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{})
+	if err != nil {
+		return nil, err
+	}
+	return newNode, nil
+}
+
+// RunJobAndCleanup runs the job, wait for it to be complete, and delete it
+func RunJobAndCleanup(cliSet *kubernetes.Clientset, job *batchv1.Job, timeout, period time.Duration, waitForTimeout bool) error {
+	job, err := cliSet.BatchV1().Jobs(job.GetNamespace()).Create(context.Background(), job, metav1.CreateOptions{})
+	if err != nil {
+		return err
+	}
+	waitJobTimeout := time.After(timeout)
+	for {
+		select {
+		case <-waitJobTimeout:
+			return errors.New("wait for job to be complete timeout")
+		case <-time.After(period):
+			newJob, err := cliSet.BatchV1().Jobs(job.GetNamespace()).
+				Get(context.Background(), job.GetName(), metav1.GetOptions{})
+			if err != nil {
+				if apierrors.IsNotFound(err) {
+					return err
+				}
+
+				if waitForTimeout {
+					klog.Infof("continue to wait for job(%s) to complete until timeout, even if failed to get job, %v", job.GetName(), err)
+					continue
+				}
+				return err
+			}
+
+			if newJob.Status.Succeeded == *newJob.Spec.Completions {
+				if err := cliSet.BatchV1().Jobs(job.GetNamespace()).
+					Delete(context.Background(), job.GetName(), metav1.DeleteOptions{
+						PropagationPolicy: &PropagationPolicy,
+					}); err != nil {
+					klog.Errorf("fail to delete succeeded servant job(%s): %s", job.GetName(), err)
+					return err
+				}
+				return nil
+			}
+		}
+	}
+}
+
+// RunServantJobs launch servant jobs on specified nodes and wait all jobs to finish.
+// Succeed jobs will be deleted when finished. Failed jobs are preserved for diagnosis.
+func RunServantJobs(
+	cliSet *kubernetes.Clientset,
+	waitServantJobTimeout time.Duration,
+	getJob func(nodeName string) (*batchv1.Job, error),
+	nodeNames []string, ww io.Writer,
+	waitForTimeout bool) error {
+	var wg sync.WaitGroup
+
+	jobByNodeName := make(map[string]*batchv1.Job)
+	for _, nodeName := range nodeNames {
+		job, err := getJob(nodeName)
+		if err != nil {
+			return fmt.Errorf("fail to get job for node %s: %w", nodeName, err)
+		}
+		jobByNodeName[nodeName] = job
+	}
+
+	res := make(chan string, len(nodeNames))
+	errCh := make(chan error, len(nodeNames))
+	for _, nodeName := range nodeNames {
+		wg.Add(1)
+		job := jobByNodeName[nodeName]
+		go func() {
+			defer wg.Done()
+			if err := RunJobAndCleanup(cliSet, job, waitServantJobTimeout, CheckServantJobPeriod, waitForTimeout); err != nil {
+				errCh <- fmt.Errorf("[ERROR] fail to run servant job(%s): %w", job.GetName(), err)
+			} else {
+				res <- fmt.Sprintf("\t[INFO] servant job(%s) has succeeded\n", job.GetName())
+			}
+		}()
+	}
+	wg.Wait()
+	close(res)
+	close(errCh)
+	for m := range res {
+		io.WriteString(ww, m)
+	}
+
+	errs := []error{}
+	for err := range errCh {
+		errs = append(errs, err)
+	}
+	if len(errs) != 0 {
+		return utilerrors.NewAggregate(errs)
+	}
+
+	return nil
+}
+
+// GenClientSet generates the clientset based on command option, environment variable or
+// the default kubeconfig file
+func GenClientSet(flags *pflag.FlagSet) (*kubernetes.Clientset, error) {
+	kubeconfigPath, err := PrepareKubeConfigPath(flags)
+	if err != nil {
+		return nil, err
+	}
+
+	restCfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
+	if err != nil {
+		return nil, err
+	}
+
+	return kubernetes.NewForConfig(restCfg)
+}
+
+// PrepareKubeConfigPath returns the path of cluster kubeconfig file
+func PrepareKubeConfigPath(flags *pflag.FlagSet) (string, error) {
+	kbCfgPath, err := flags.GetString("kubeconfig")
+	if err != nil {
+		return "", err
+	}
+
+	if kbCfgPath == "" {
+		kbCfgPath = os.Getenv("KUBECONFIG")
+	}
+
+	if kbCfgPath == "" {
+		if home := homedir.HomeDir(); home != "" {
+			kbCfgPath = filepath.Join(home, ".kube", "config")
+		}
+	}
+
+	if kbCfgPath == "" {
+		return "", errors.New("either '--kubeconfig', '$HOME/.kube/config' or '$KUBECONFIG' need to be set")
+	}
+
+	return kbCfgPath, nil
+}
+
+func GetOrCreateJoinTokenString(cliSet *kubernetes.Clientset) (string, error) {
+	tokenSelector := fields.SelectorFromSet(
+		map[string]string{
+			// TODO: We hard-code "type" here until `field_constants.go` that is
+			// currently in `pkg/apis/core/` exists in the external API, i.e.
+			// k8s.io/api/v1. Should be v1.SecretTypeField
+			"type": string(bootstrapapi.SecretTypeBootstrapToken),
+		},
+	)
+	listOptions := metav1.ListOptions{
+		FieldSelector: tokenSelector.String(),
+	}
+	klog.V(1).Infoln("[token] retrieving list of bootstrap tokens")
+	secrets, err := cliSet.CoreV1().Secrets(metav1.NamespaceSystem).List(context.Background(), listOptions)
+	if err != nil {
+		return "", fmt.Errorf("%w%s", err, "failed to list bootstrap tokens")
+	}
+
+	for _, secret := range secrets.Items {
+
+		// Get the BootstrapToken struct representation from the Secret object
+		token, err := kubeadmapi.BootstrapTokenFromSecret(&secret)
+		if err != nil {
+			klog.Warningf("%v", err)
+			continue
+		}
+		if !usagesAndGroupsAreValid(token) {
+			continue
+		}
+
+		return token.Token.String(), nil
+		// Get the human-friendly string representation for the token
+	}
+
+	tokenStr, err := bootstraputil.GenerateBootstrapToken()
+	if err != nil {
+		return "", fmt.Errorf("couldn't generate random token, %w", err)
+	}
+	token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
+	if err != nil {
+		return "", err
+	}
+
+	klog.V(1).Infoln("[token] creating token")
+	if err := nodetoken.CreateNewTokens(cliSet,
+		[]kubeadmapi.BootstrapToken{{
+			Token:  token,
+			Usages: kubeadmconstants.DefaultTokenUsages,
+			Groups: kubeadmconstants.DefaultTokenGroups,
+		}}); err != nil {
+		return "", err
+	}
+	return tokenStr, nil
+}
+
+// usagesAndGroupsAreValid checks if the usages and groups in the given bootstrap token are valid
+func usagesAndGroupsAreValid(token *kubeadmapi.BootstrapToken) bool {
+	sliceEqual := func(a, b []string) bool {
+		if len(a) != len(b) {
+			return false
+		}
+		sort.Strings(a)
+		sort.Strings(b)
+		for k, v := range b {
+			if a[k] != v {
+				return false
+			}
+		}
+		return true
+	}
+
+	return sliceEqual(token.Usages, kubeadmconstants.DefaultTokenUsages) && sliceEqual(token.Groups, kubeadmconstants.DefaultTokenGroups)
+}
diff --git a/pkg/yurtadm/util/kubernetes/util_test.go b/pkg/yurtctl/util/kubernetes/util_test.go
similarity index 100%
rename from pkg/yurtadm/util/kubernetes/util_test.go
rename to pkg/yurtctl/util/kubernetes/util_test.go
diff --git a/pkg/yurthub/filter/servicetopology/handler.go b/pkg/yurthub/filter/servicetopology/handler.go
index a2a605e8..b060a83a 100644
--- a/pkg/yurthub/filter/servicetopology/handler.go
+++ b/pkg/yurthub/filter/servicetopology/handler.go
@@ -20,7 +20,8 @@ import (
 	"io"
 
 	v1 "k8s.io/api/core/v1"
-	discovery "k8s.io/api/discovery/v1beta1"
+	discovery "k8s.io/api/discovery/v1"
+	discoveryV1beta1 "k8s.io/api/discovery/v1beta1"
 	"k8s.io/apimachinery/pkg/watch"
 	listers "k8s.io/client-go/listers/core/v1"
 	"k8s.io/klog/v2"
@@ -70,8 +71,20 @@ func (fh *serviceTopologyFilterHandler) ObjectResponseFilter(b []byte) ([]byte,
 		return b, nil
 	}
 
-	if endpointSliceList, ok := eps.(*discovery.EndpointSliceList); ok {
-		//filter endpointSlice
+	if endpointSliceList, ok := eps.(*discoveryV1beta1.EndpointSliceList); ok {
+		// filter endpointSlice before k8s 1.21
+		var items []discoveryV1beta1.EndpointSlice
+		for i := range endpointSliceList.Items {
+			item := fh.reassembleEndpointSliceV1beta1(&endpointSliceList.Items[i])
+			if item != nil {
+				items = append(items, *item)
+			}
+		}
+		endpointSliceList.Items = items
+
+		return fh.serializer.Encode(endpointSliceList)
+	} else if endpointSliceList, ok := eps.(*discovery.EndpointSliceList); ok {
+		// filter endpointSlice after k8s 1.21, include 1.21 version
 		var items []discovery.EndpointSlice
 		for i := range endpointSliceList.Items {
 			item := fh.reassembleEndpointSlice(&endpointSliceList.Items[i])
@@ -107,22 +120,81 @@ func (fh *serviceTopologyFilterHandler) StreamResponseFilter(rc io.ReadCloser, c
 
 		var wEvent watch.Event
 		wEvent.Type = watchType
-		endpointSlice, ok := obj.(*discovery.EndpointSlice)
-		if ok {
+
+		if endpointSlice, ok := obj.(*discoveryV1beta1.EndpointSlice); ok {
+			item := fh.reassembleEndpointSliceV1beta1(endpointSlice)
+			if item == nil {
+				continue
+			}
+			wEvent.Object = item
+			klog.V(5).Infof("filter watch decode endpointSlice: type: %s, obj=%#+v", watchType, endpointSlice)
+		} else if endpointSlice, ok := obj.(*discovery.EndpointSlice); ok {
 			item := fh.reassembleEndpointSlice(endpointSlice)
 			if item == nil {
 				continue
 			}
 			wEvent.Object = item
+			klog.V(5).Infof("filter watch decode endpointSlice: type: %s, obj=%#+v", watchType, endpointSlice)
 		} else {
 			wEvent.Object = obj
 		}
 
-		klog.V(5).Infof("filter watch decode endpointSlice: type: %s, obj=%#+v", watchType, endpointSlice)
 		ch <- wEvent
 	}
 }
 
+// reassembleEndpointSlice will discard endpointslice for LB service and filter the endpoints out of endpointslice in terms of service Topology.
+func (fh *serviceTopologyFilterHandler) reassembleEndpointSliceV1beta1(endpointSlice *discoveryV1beta1.EndpointSlice) *discoveryV1beta1.EndpointSlice {
+	var serviceTopologyType string
+	// get the service Topology type
+	if svcName, ok := endpointSlice.Labels[discoveryV1beta1.LabelServiceName]; ok {
+		svc, err := fh.serviceLister.Services(endpointSlice.Namespace).Get(svcName)
+		if err != nil {
+			klog.Infof("skip reassemble endpointSlice, failed to get service %s/%s, err: %v", endpointSlice.Namespace, svcName, err)
+			return endpointSlice
+		}
+
+		if serviceTopologyType, ok = svc.Annotations[AnnotationServiceTopologyKey]; !ok {
+			klog.Infof("skip reassemble endpointSlice, service %s/%s has no annotation %s", endpointSlice.Namespace, svcName, AnnotationServiceTopologyKey)
+			return endpointSlice
+		}
+	}
+
+	var newEps []discoveryV1beta1.Endpoint
+	// if type of service Topology is 'kubernetes.io/hostname'
+	// filter the endpoint just on the local host
+	if serviceTopologyType == AnnotationServiceTopologyValueNode {
+		for i := range endpointSlice.Endpoints {
+			if endpointSlice.Endpoints[i].Topology[v1.LabelHostname] == fh.nodeName {
+				newEps = append(newEps, endpointSlice.Endpoints[i])
+			}
+		}
+		endpointSlice.Endpoints = newEps
+	} else if serviceTopologyType == AnnotationServiceTopologyValueNodePool || serviceTopologyType == AnnotationServiceTopologyValueZone {
+		// if type of service Topology is openyurt.io/nodepool
+		// filter the endpoint just on the node which is in the same nodepool with current node
+		currentNode, err := fh.nodeGetter(fh.nodeName)
+		if err != nil {
+			klog.Infof("skip reassemble endpointSlice, failed to get current node %s, err: %v", fh.nodeName, err)
+			return endpointSlice
+		}
+		if nodePoolName, ok := currentNode.Labels[nodepoolv1alpha1.LabelCurrentNodePool]; ok {
+			nodePool, err := fh.nodePoolLister.Get(nodePoolName)
+			if err != nil {
+				klog.Infof("skip reassemble endpointSlice, failed to get nodepool %s, err: %v", nodePoolName, err)
+				return endpointSlice
+			}
+			for i := range endpointSlice.Endpoints {
+				if inSameNodePool(endpointSlice.Endpoints[i].Topology[v1.LabelHostname], nodePool.Status.Nodes) {
+					newEps = append(newEps, endpointSlice.Endpoints[i])
+				}
+			}
+			endpointSlice.Endpoints = newEps
+		}
+	}
+	return endpointSlice
+}
+
 // reassembleEndpointSlice will discard endpointslice for LB service and filter the endpoints out of endpointslice in terms of service Topology.
 func (fh *serviceTopologyFilterHandler) reassembleEndpointSlice(endpointSlice *discovery.EndpointSlice) *discovery.EndpointSlice {
 	var serviceTopologyType string
@@ -145,7 +217,7 @@ func (fh *serviceTopologyFilterHandler) reassembleEndpointSlice(endpointSlice *d
 	// filter the endpoint just on the local host
 	if serviceTopologyType == AnnotationServiceTopologyValueNode {
 		for i := range endpointSlice.Endpoints {
-			if endpointSlice.Endpoints[i].Topology[v1.LabelHostname] == fh.nodeName {
+			if *endpointSlice.Endpoints[i].NodeName == fh.nodeName {
 				newEps = append(newEps, endpointSlice.Endpoints[i])
 			}
 		}
@@ -165,7 +237,7 @@ func (fh *serviceTopologyFilterHandler) reassembleEndpointSlice(endpointSlice *d
 				return endpointSlice
 			}
 			for i := range endpointSlice.Endpoints {
-				if inSameNodePool(endpointSlice.Endpoints[i].Topology[v1.LabelHostname], nodePool.Status.Nodes) {
+				if inSameNodePool(*endpointSlice.Endpoints[i].NodeName, nodePool.Status.Nodes) {
 					newEps = append(newEps, endpointSlice.Endpoints[i])
 				}
 			}
diff --git a/pkg/yurthub/network/iptables.go b/pkg/yurthub/network/iptables.go
index 379c69ff..0c5a8519 100644
--- a/pkg/yurthub/network/iptables.go
+++ b/pkg/yurthub/network/iptables.go
@@ -22,6 +22,7 @@ import (
 	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/klog/v2"
 	"k8s.io/utils/exec"
+	utilnet "k8s.io/utils/net"
 
 	"github.com/openyurtio/openyurt/pkg/util/iptables"
 )
@@ -40,6 +41,9 @@ type IptablesManager struct {
 
 func NewIptablesManager(dummyIfIP, dummyIfPort string) *IptablesManager {
 	protocol := iptables.ProtocolIpv4
+	if utilnet.IsIPv6String(dummyIfIP) {
+		protocol = iptables.ProtocolIpv6
+	}
 	execer := exec.New()
 	iptInterface := iptables.New(execer, protocol)
 
@@ -63,16 +67,16 @@ func makeupIptablesRules(ifIP, ifPort string) []iptablesRule {
 		{iptables.Prepend, iptables.Table("raw"), iptables.ChainOutput, []string{"-p", "tcp", "--sport", ifPort, "-s", ifIP, "-j", "NOTRACK"}},
 		// accept traffic from 169.254.2.1:10261
 		{iptables.Prepend, iptables.TableFilter, iptables.ChainOutput, []string{"-p", "tcp", "--sport", ifPort, "-s", ifIP, "-j", "ACCEPT"}},
-		// skip connection track for traffic from container to 127.0.0.1:10261
-		{iptables.Prepend, iptables.Table("raw"), iptables.ChainPrerouting, []string{"-p", "tcp", "--dport", ifPort, "--destination", "127.0.0.1", "-j", "NOTRACK"}},
-		// skip connection track for traffic from host network to 127.0.0.1:10261
-		{iptables.Prepend, iptables.Table("raw"), iptables.ChainOutput, []string{"-p", "tcp", "--dport", ifPort, "--destination", "127.0.0.1", "-j", "NOTRACK"}},
-		// accept traffic to 127.0.0.1:10261
-		{iptables.Prepend, iptables.TableFilter, iptables.ChainInput, []string{"-p", "tcp", "--dport", ifPort, "--destination", "127.0.0.1", "-j", "ACCEPT"}},
-		// skip connection track for traffic from 127.0.0.1:10261
-		{iptables.Prepend, iptables.Table("raw"), iptables.ChainOutput, []string{"-p", "tcp", "--sport", ifPort, "-s", "127.0.0.1", "-j", "NOTRACK"}},
-		// accept traffic from 127.0.0.1:10261
-		{iptables.Prepend, iptables.TableFilter, iptables.ChainOutput, []string{"-p", "tcp", "--sport", ifPort, "-s", "127.0.0.1", "-j", "ACCEPT"}},
+		// skip connection track for traffic from container to localhost:10261
+		{iptables.Prepend, iptables.Table("raw"), iptables.ChainPrerouting, []string{"-p", "tcp", "--dport", ifPort, "--destination", "localhost", "-j", "NOTRACK"}},
+		// skip connection track for traffic from host network to localhost:10261
+		{iptables.Prepend, iptables.Table("raw"), iptables.ChainOutput, []string{"-p", "tcp", "--dport", ifPort, "--destination", "localhost", "-j", "NOTRACK"}},
+		// accept traffic to localhost:10261
+		{iptables.Prepend, iptables.TableFilter, iptables.ChainInput, []string{"-p", "tcp", "--dport", ifPort, "--destination", "localhost", "-j", "ACCEPT"}},
+		// skip connection track for traffic from localhost:10261
+		{iptables.Prepend, iptables.Table("raw"), iptables.ChainOutput, []string{"-p", "tcp", "--sport", ifPort, "-s", "localhost", "-j", "NOTRACK"}},
+		// accept traffic from localhost:10261
+		{iptables.Prepend, iptables.TableFilter, iptables.ChainOutput, []string{"-p", "tcp", "--sport", ifPort, "-s", "localhost", "-j", "ACCEPT"}},
 	}
 }
 
diff --git a/pkg/yurthub/server/server.go b/pkg/yurthub/server/server.go
index 0c7038e2..9c2bb62c 100644
--- a/pkg/yurthub/server/server.go
+++ b/pkg/yurthub/server/server.go
@@ -169,7 +169,12 @@ func healthz(w http.ResponseWriter, _ *http.Request) {
 }
 
 // GenUseCertMgrAndTLSConfig create a certificate manager for the yurthub server and generate a TLS configuration
-func GenUseCertMgrAndTLSConfig(restConfigMgr *rest.RestConfigManager, certificateMgr interfaces.YurtCertificateManager, certDir, nodeName, proxyServerSecureDummyAddr string, stopCh <-chan struct{}) (*tls.Config, error) {
+func GenUseCertMgrAndTLSConfig(
+	restConfigMgr *rest.RestConfigManager,
+	certificateMgr interfaces.YurtCertificateManager,
+	certDir, nodeName string,
+	certIPs []net.IP,
+	stopCh <-chan struct{}) (*tls.Config, error) {
 	cfg := restConfigMgr.GetRestConfig(false)
 	if cfg == nil {
 		return nil, fmt.Errorf("failed to prepare rest config based ong hub agent client certificate")
@@ -180,7 +185,7 @@ func GenUseCertMgrAndTLSConfig(restConfigMgr *rest.RestConfigManager, certificat
 		return nil, err
 	}
 	// create a certificate manager for the yurthub server and run the csr approver for both yurthub
-	serverCertMgr, err := certmanager.NewYurtHubServerCertManager(clientSet, certDir, nodeName, proxyServerSecureDummyAddr)
+	serverCertMgr, err := certmanager.NewYurtHubServerCertManager(clientSet, certDir, nodeName, certIPs)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/yurttunnel/handlerwrapper/localhostproxy/handler.go b/pkg/yurttunnel/handlerwrapper/localhostproxy/handler.go
index 7b72b5ed..7897c0b8 100644
--- a/pkg/yurttunnel/handlerwrapper/localhostproxy/handler.go
+++ b/pkg/yurttunnel/handlerwrapper/localhostproxy/handler.go
@@ -35,6 +35,7 @@ import (
 	"k8s.io/klog/v2"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	utilip "github.com/openyurtio/openyurt/pkg/util/ip"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
 	hw "github.com/openyurtio/openyurt/pkg/yurttunnel/handlerwrapper"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/util"
@@ -47,11 +48,13 @@ type localHostProxyMiddleware struct {
 	localhostPorts     map[string]struct{}
 	nodeInformerSynced cache.InformerSynced
 	cmInformerSynced   cache.InformerSynced
+	loopbackAddr       string
 }
 
-func NewLocalHostProxyMiddleware() hw.Middleware {
+func NewLocalHostProxyMiddleware(isIPv6 bool) hw.Middleware {
 	return &localHostProxyMiddleware{
 		localhostPorts: make(map[string]struct{}),
+		loopbackAddr:   utilip.MustGetLoopbackIP(isIPv6),
 	}
 }
 
@@ -99,7 +102,7 @@ func (plm *localHostProxyMiddleware) WrapHandler(handler http.Handler) http.Hand
 				req.Header.Set(constants.ProxyHostHeaderKey, nodeName)
 			}
 
-			proxyDest = fmt.Sprintf("127.0.0.1:%s", port)
+			proxyDest = net.JoinHostPort(plm.loopbackAddr, port)
 			oldHost := req.URL.Host
 			req.Host = proxyDest
 			req.Header.Set("Host", proxyDest)
diff --git a/pkg/yurttunnel/handlerwrapper/wraphandler/wraphandler.go b/pkg/yurttunnel/handlerwrapper/wraphandler/wraphandler.go
index d90ab7ea..c5919642 100644
--- a/pkg/yurttunnel/handlerwrapper/wraphandler/wraphandler.go
+++ b/pkg/yurttunnel/handlerwrapper/wraphandler/wraphandler.go
@@ -27,7 +27,7 @@ import (
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/handlerwrapper/tracerequest"
 )
 
-func InitHandlerWrappers(mi initializer.MiddlewareInitializer) (hw.HandlerWrappers, error) {
+func InitHandlerWrappers(mi initializer.MiddlewareInitializer, isIPv6 bool) (hw.HandlerWrappers, error) {
 	wrappers := make(hw.HandlerWrappers, 0)
 	// register all of middleware here
 	//
@@ -42,7 +42,7 @@ func InitHandlerWrappers(mi initializer.MiddlewareInitializer) (hw.HandlerWrappe
 	//
 	// then the middleware m2 will be called before the mw1
 	wrappers = append(wrappers, tracerequest.NewTraceReqMiddleware())
-	wrappers = append(wrappers, localhostproxy.NewLocalHostProxyMiddleware())
+	wrappers = append(wrappers, localhostproxy.NewLocalHostProxyMiddleware(isIPv6))
 
 	// init all of wrappers
 	for i := range wrappers {
diff --git a/pkg/yurttunnel/server/interceptor.go b/pkg/yurttunnel/server/interceptor.go
index b5087f58..3e4baf48 100644
--- a/pkg/yurttunnel/server/interceptor.go
+++ b/pkg/yurttunnel/server/interceptor.go
@@ -88,7 +88,7 @@ func NewRequestInterceptor(udsSockFile string, cfg *tls.Config) *RequestIntercep
 			}
 		}
 
-		fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s%s\r\n\r\n", addr, "127.0.0.1", connectHeaders)
+		fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: localhost%s\r\n\r\n", addr, connectHeaders)
 		br := newBufioReader(proxyConn)
 		defer putBufioReader(br)
 		res, err := http.ReadResponse(br, nil)
diff --git a/pkg/yurttunnel/server/serveraddr/addr.go b/pkg/yurttunnel/server/serveraddr/addr.go
index bfc2eca9..9024b7b3 100644
--- a/pkg/yurttunnel/server/serveraddr/addr.go
+++ b/pkg/yurttunnel/server/serveraddr/addr.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"net"
+	"strconv"
 
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -30,6 +31,7 @@ import (
 	"k8s.io/klog/v2"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	utilip "github.com/openyurtio/openyurt/pkg/util/ip"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/constants"
 )
 
@@ -57,7 +59,7 @@ func GetTunnelServerAddr(clientset kubernetes.Interface) (string, error) {
 
 	for _, tmpIP := range ips {
 		// we use the first non-loopback IP address.
-		if tmpIP.String() != "127.0.0.1" {
+		if s := tmpIP.String(); s != utilip.DefaultLoopbackIP4 && s != utilip.DefaultLoopbackIP6 {
 			ip = tmpIP
 			break
 		}
@@ -86,7 +88,7 @@ func GetTunnelServerAddr(clientset kubernetes.Interface) (string, error) {
 		return "", errors.New("fail to get the port number")
 	}
 
-	return fmt.Sprintf("%s:%d", host, tcpPort), nil
+	return net.JoinHostPort(host, strconv.Itoa(int(tcpPort))), nil
 }
 
 // GetYurttunelServerDNSandIP gets DNS names and IPS for generating tunnel server certificate.
@@ -231,7 +233,7 @@ func extractTunnelServerDNSandIPs(svc *corev1.Service, eps []*corev1.Endpoints,
 	if svc.Spec.ClusterIP != "None" {
 		ips = append(ips, net.ParseIP(svc.Spec.ClusterIP))
 	}
-	ips = append(ips, net.ParseIP("127.0.0.1"))
+	ips = append(ips, net.ParseIP(utilip.DefaultLoopbackIP4), net.ParseIP(utilip.DefaultLoopbackIP6))
 
 	// 3. extract dns and ip from the endpoint
 	for _, eps := range eps {
diff --git a/pkg/yurttunnel/trafficforward/iptables/iptables.go b/pkg/yurttunnel/trafficforward/iptables/iptables.go
index 23230fc1..0fccfd27 100644
--- a/pkg/yurttunnel/trafficforward/iptables/iptables.go
+++ b/pkg/yurttunnel/trafficforward/iptables/iptables.go
@@ -34,13 +34,13 @@ import (
 	utilnet "k8s.io/utils/net"
 
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	"github.com/openyurtio/openyurt/pkg/util/ip"
 	"github.com/openyurtio/openyurt/pkg/util/iptables"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/server/metrics"
 	"github.com/openyurtio/openyurt/pkg/yurttunnel/util"
 )
 
 const (
-	loopbackAddr              = "127.0.0.1"
 	reqReturnComment          = "return request to access node directly"
 	dnatToTunnelComment       = "dnat to tunnel for access node"
 	yurttunnelServerPortChain = "TUNNEL-PORT"
@@ -84,6 +84,7 @@ type iptablesManager struct {
 	nodeInformer     coreinformer.NodeInformer
 	iptables         iptables.Interface
 	execer           exec.Interface
+	loopbackAddr     string
 	conntrackPath    string
 	secureDnatDest   string
 	insecureDnatDest string
@@ -92,18 +93,18 @@ type iptablesManager struct {
 	syncPeriod       int
 }
 
-// NewIptablesManager creates an IptablesManager; deletes old chains, if any;
+// NewIptablesManagerWithIPFamily creates an IptablesManager; deletes old chains, if any;
 // generates new dnat rules based on IPs of current active nodes; and
 // appends the rules to the iptable.
-func NewIptablesManager(client clientset.Interface,
+func NewIptablesManagerWithIPFamily(client clientset.Interface,
 	nodeInformer coreinformer.NodeInformer,
 	listenAddr string,
 	listenInsecureAddr string,
-	syncPeriod int) IptablesManager {
+	syncPeriod int,
+	ipFamily iptables.Protocol) IptablesManager {
 
-	protocol := iptables.ProtocolIpv4
 	execer := exec.New()
-	iptInterface := iptables.New(execer, protocol)
+	iptInterface := iptables.New(execer, ipFamily)
 
 	if syncPeriod < defaultSyncPeriod {
 		syncPeriod = defaultSyncPeriod
@@ -121,6 +122,8 @@ func NewIptablesManager(client clientset.Interface,
 		syncPeriod:       syncPeriod,
 	}
 
+	im.loopbackAddr = ip.MustGetLoopbackIP(ipFamily == iptables.ProtocolIpv6)
+
 	// conntrack setting
 	conntrackPath, err := im.execer.LookPath("conntrack")
 	if err != nil {
@@ -135,6 +138,15 @@ func NewIptablesManager(client clientset.Interface,
 	return im
 }
 
+// NewIptablesManager creates an IptablesManager with ipv4 protocol
+func NewIptablesManager(client clientset.Interface,
+	nodeInformer coreinformer.NodeInformer,
+	listenAddr string,
+	listenInsecureAddr string,
+	syncPeriod int) IptablesManager {
+	return NewIptablesManagerWithIPFamily(client, nodeInformer, listenAddr, listenInsecureAddr, syncPeriod, iptables.ProtocolIpv4)
+}
+
 // Run starts the iptablesManager that will updates dnat rules periodically
 func (im *iptablesManager) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
 	defer wg.Done()
@@ -361,7 +373,7 @@ func (im *iptablesManager) ensurePortIptables(port string, currentIPs, deletedIP
 			iptables.Prepend,
 			iptables.TableNAT, portChain, reqReturnPortIptablesArgs...)
 		if err != nil {
-			klog.Errorf("could not ensure -j RETURN iptables rule for %s:%s: %v", ip, port, err)
+			klog.Errorf("could not ensure -j RETURN iptables rule for %s: %v", net.JoinHostPort(ip, port), err)
 			return err
 		}
 	}
@@ -382,7 +394,7 @@ func (im *iptablesManager) ensurePortIptables(port string, currentIPs, deletedIP
 		err = im.iptables.DeleteRule(iptables.TableNAT,
 			portChain, deletedIPIptablesArgs...)
 		if err != nil {
-			klog.Errorf("could not delete old iptables rules for %s:%s: %v", ip, port, err)
+			klog.Errorf("could not delete old iptables rules for %s: %v", net.JoinHostPort(ip, port), err)
 			return err
 		}
 	}
@@ -509,7 +521,7 @@ func (im *iptablesManager) syncIptableSetting() {
 	// check if there are new nodes
 	nodesIP := im.getIPOfNodesWithoutAgent()
 	nodesChanged, addedNodesIP, deletedNodesIP := im.getAddedAndDeletedNodes(nodesIP)
-	currentNodesIP := append(nodesIP, loopbackAddr)
+	currentNodesIP := append(nodesIP, im.loopbackAddr)
 
 	// update the iptables setting if necessary
 	err = im.ensurePortsIptables(currentDnatPorts, deletedDnatPorts, currentNodesIP, deletedNodesIP, portMappings)


Test patch:
diff --git a/pkg/util/ip/ip_test.go b/pkg/util/ip/ip_test.go
new file mode 100644
index 00000000..efcef419
--- /dev/null
+++ b/pkg/util/ip/ip_test.go
@@ -0,0 +1,44 @@
+/*
+Copyright 2021 The OpenYurt Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ip
+
+import (
+	"testing"
+)
+
+func TestGetLoopbackIP(t *testing.T) {
+	lo4, err := GetLoopbackIP(false)
+	if err != nil {
+		t.Errorf("failed to get ipv4 loopback address: %v", err)
+	}
+	t.Logf("got ipv4 loopback address: %s", lo4)
+	if lo4 != "127.0.0.1" {
+		t.Errorf("got ipv4 loopback addr: '%s', expect: '127.0.0.1'", lo4)
+	}
+
+	lo6, err := GetLoopbackIP(true)
+	if err != nil {
+		t.Errorf("failed to get ipv6 loopback address: %v", err)
+	}
+	if lo6 != "" {
+		// dual stack env
+		t.Logf("got ipv6 loopback address: %s", lo6)
+		if lo6 != "::1" {
+			t.Errorf("got ipv6 loopback addr: '%s', expect: '::1'", lo6)
+		}
+	}
+}
diff --git a/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime_test.go b/pkg/util/kubernetes/kubeadm/app/util/runtime/runtime_test.go
similarity index 99%
rename from pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime_test.go
rename to pkg/util/kubernetes/kubeadm/app/util/runtime/runtime_test.go
index 0a101eef..5382779e 100644
--- a/pkg/yurtadm/kubernetes/kubeadm/app/util/runtime/runtime_test.go
+++ b/pkg/util/kubernetes/kubeadm/app/util/runtime/runtime_test.go
@@ -28,7 +28,7 @@ import (
 	"k8s.io/utils/exec"
 	fakeexec "k8s.io/utils/exec/testing"
 
-	"github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/constants"
+	"github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/constants"
 )
 
 func TestNewContainerRuntime(t *testing.T) {
diff --git a/pkg/yurtctl/cmd/yurttest/kindinit/converter.go b/pkg/yurtctl/cmd/yurttest/kindinit/converter.go
index 256297bb..e91f8f0b 100644
--- a/pkg/yurtctl/cmd/yurttest/kindinit/converter.go
+++ b/pkg/yurtctl/cmd/yurttest/kindinit/converter.go
@@ -32,10 +32,10 @@ import (
 	"k8s.io/klog/v2"
 
 	nodeservant "github.com/openyurtio/openyurt/pkg/node-servant"
-	kubeadmapi "github.com/openyurtio/openyurt/pkg/yurtadm/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo"
-	kubeutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/kubernetes"
-	strutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/strings"
+	kubeadmapi "github.com/openyurtio/openyurt/pkg/util/kubernetes/kubeadm/app/phases/bootstraptoken/clusterinfo"
+	strutil "github.com/openyurtio/openyurt/pkg/util/strings"
 	"github.com/openyurtio/openyurt/pkg/yurtctl/lock"
+	kubeutil "github.com/openyurtio/openyurt/pkg/yurtctl/util/kubernetes"
 	"github.com/openyurtio/openyurt/pkg/yurthub/util"
 )
 
diff --git a/pkg/yurtctl/cmd/yurttest/kindinit/init.go b/pkg/yurtctl/cmd/yurttest/kindinit/init.go
index a37e8cc2..586862ab 100644
--- a/pkg/yurtctl/cmd/yurttest/kindinit/init.go
+++ b/pkg/yurtctl/cmd/yurttest/kindinit/init.go
@@ -35,10 +35,10 @@ import (
 	"k8s.io/klog/v2"
 
 	nodeservant "github.com/openyurtio/openyurt/pkg/node-servant"
-	kubeutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/kubernetes"
-	strutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/strings"
-	tmplutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/templates"
+	strutil "github.com/openyurtio/openyurt/pkg/util/strings"
+	tmplutil "github.com/openyurtio/openyurt/pkg/util/templates"
 	"github.com/openyurtio/openyurt/pkg/yurtctl/constants"
+	kubeutil "github.com/openyurtio/openyurt/pkg/yurtctl/util/kubernetes"
 	"github.com/openyurtio/openyurt/pkg/yurthub/filter/servicetopology"
 )
 
diff --git a/pkg/yurtctl/cmd/yurttest/kindinit/kindoperator.go b/pkg/yurtctl/cmd/yurttest/kindinit/kindoperator.go
index 412a5735..76ff7168 100644
--- a/pkg/yurtctl/cmd/yurttest/kindinit/kindoperator.go
+++ b/pkg/yurtctl/cmd/yurttest/kindinit/kindoperator.go
@@ -25,7 +25,7 @@ import (
 
 	"k8s.io/klog/v2"
 
-	strutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/strings"
+	strutil "github.com/openyurtio/openyurt/pkg/util/strings"
 )
 
 var (
diff --git a/pkg/yurthub/filter/servicetopology/handler_test.go b/pkg/yurthub/filter/servicetopology/handler_test.go
index 979c5ac6..d1666bef 100644
--- a/pkg/yurthub/filter/servicetopology/handler_test.go
+++ b/pkg/yurthub/filter/servicetopology/handler_test.go
@@ -22,7 +22,8 @@ import (
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
-	discovery "k8s.io/api/discovery/v1beta1"
+	discovery "k8s.io/api/discovery/v1"
+	discoveryV1beta1 "k8s.io/api/discovery/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/informers"
 	k8sfake "k8s.io/client-go/kubernetes/fake"
@@ -32,25 +33,25 @@ import (
 	yurtinformers "github.com/openyurtio/yurt-app-manager-api/pkg/yurtappmanager/client/informers/externalversions"
 )
 
-func TestReassembleEndpointSlice(t *testing.T) {
+func TestReassembleEndpointSliceV1beta1(t *testing.T) {
 	currentNodeName := "node1"
 
 	testcases := map[string]struct {
-		endpointSlice *discovery.EndpointSlice
+		endpointSlice *discoveryV1beta1.EndpointSlice
 		kubeClient    *k8sfake.Clientset
 		yurtClient    *yurtfake.Clientset
-		expectResult  *discovery.EndpointSlice
+		expectResult  *discoveryV1beta1.EndpointSlice
 	}{
 		"service with annotation openyurt.io/topologyKeys: kubernetes.io/hostname": {
-			endpointSlice: &discovery.EndpointSlice{
+			endpointSlice: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -149,15 +150,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 					},
 				},
 			),
-			expectResult: &discovery.EndpointSlice{
+			expectResult: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -178,15 +179,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 			},
 		},
 		"service with annotation openyurt.io/topologyKeys: openyurt.io/nodepool": {
-			endpointSlice: &discovery.EndpointSlice{
+			endpointSlice: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -285,15 +286,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 					},
 				},
 			),
-			expectResult: &discovery.EndpointSlice{
+			expectResult: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -322,15 +323,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 			},
 		},
 		"service with annotation openyurt.io/topologyKeys: kubernetes.io/zone": {
-			endpointSlice: &discovery.EndpointSlice{
+			endpointSlice: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -429,15 +430,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 					},
 				},
 			),
-			expectResult: &discovery.EndpointSlice{
+			expectResult: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -466,15 +467,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 			},
 		},
 		"service without annotation openyurt.io/topologyKeys": {
-			endpointSlice: &discovery.EndpointSlice{
+			endpointSlice: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -571,15 +572,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 					},
 				},
 			),
-			expectResult: &discovery.EndpointSlice{
+			expectResult: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -616,15 +617,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 			},
 		},
 		"currentNode is not in any nodepool": {
-			endpointSlice: &discovery.EndpointSlice{
+			endpointSlice: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -718,15 +719,15 @@ func TestReassembleEndpointSlice(t *testing.T) {
 					},
 				},
 			),
-			expectResult: &discovery.EndpointSlice{
+			expectResult: &discoveryV1beta1.EndpointSlice{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "svc1-np7sf",
 					Namespace: "default",
 					Labels: map[string]string{
-						discovery.LabelServiceName: "svc1",
+						discoveryV1beta1.LabelServiceName: "svc1",
 					},
 				},
-				Endpoints: []discovery.Endpoint{
+				Endpoints: []discoveryV1beta1.Endpoint{
 					{
 						Addresses: []string{
 							"10.244.1.2",
@@ -798,22 +799,21 @@ func TestReassembleEndpointSlice(t *testing.T) {
 				nodeGetter:     nodeGetter,
 			}
 
-			reassembledEndpointSlice := fh.reassembleEndpointSlice(tt.endpointSlice)
+			reassembledEndpointSlice := fh.reassembleEndpointSliceV1beta1(tt.endpointSlice)
 
-			if !isEqualEndpointSlice(reassembledEndpointSlice, tt.expectResult) {
+			if !isEqualEndpointSliceV1beta1(reassembledEndpointSlice, tt.expectResult) {
 				t.Errorf("reassembleEndpointSlice got error, expected: \n%v\nbut got: \n%v\n", tt.expectResult, reassembledEndpointSlice)
 			}
-
 		})
 	}
 }
 
 // isEqualEndpointSlice is used to determine whether two endpointSlice are equal.
 // Note that this function can only be used in this test.
-func isEqualEndpointSlice(endpointSlice1, endpointSlice2 *discovery.EndpointSlice) bool {
+func isEqualEndpointSliceV1beta1(endpointSlice1, endpointSlice2 *discoveryV1beta1.EndpointSlice) bool {
 	if endpointSlice1.Name != endpointSlice2.Name ||
 		endpointSlice1.Namespace != endpointSlice2.Namespace ||
-		endpointSlice1.Labels[discovery.LabelServiceName] != endpointSlice2.Labels[discovery.LabelServiceName] {
+		endpointSlice1.Labels[discoveryV1beta1.LabelServiceName] != endpointSlice2.Labels[discoveryV1beta1.LabelServiceName] {
 		return false
 	}
 
@@ -849,3 +849,734 @@ func isEqualStrings(s1, s2 []string) bool {
 
 	return true
 }
+
+func TestReassembleEndpointSlice(t *testing.T) {
+	currentNodeName := "node1"
+	nodeName2 := "node2"
+	nodeName3 := "node3"
+
+	testcases := map[string]struct {
+		endpointSlice *discovery.EndpointSlice
+		kubeClient    *k8sfake.Clientset
+		yurtClient    *yurtfake.Clientset
+		expectResult  *discovery.EndpointSlice
+	}{
+		"service with annotation openyurt.io/topologyKeys: kubernetes.io/hostname": {
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.3",
+						},
+						NodeName: &nodeName2,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+			kubeClient: k8sfake.NewSimpleClientset(
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: currentNodeName,
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node2",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "shanghai",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node3",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Service{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "svc1",
+						Namespace: "default",
+						Annotations: map[string]string{
+							AnnotationServiceTopologyKey: AnnotationServiceTopologyValueNode,
+						},
+					},
+				},
+			),
+			yurtClient: yurtfake.NewSimpleClientset(
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "hangzhou",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							currentNodeName,
+							"node3",
+						},
+					},
+				},
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "shanghai",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							"node2",
+						},
+					},
+				},
+			),
+			expectResult: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+				},
+			},
+		},
+		"service with annotation openyurt.io/topologyKeys: openyurt.io/nodepool": {
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.3",
+						},
+						NodeName: &nodeName2,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+			kubeClient: k8sfake.NewSimpleClientset(
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: currentNodeName,
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node2",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "shanghai",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node3",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Service{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "svc1",
+						Namespace: "default",
+						Annotations: map[string]string{
+							AnnotationServiceTopologyKey: AnnotationServiceTopologyValueNodePool,
+						},
+					},
+				},
+			),
+			yurtClient: yurtfake.NewSimpleClientset(
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "hangzhou",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							currentNodeName,
+							"node3",
+						},
+					},
+				},
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "shanghai",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							"node2",
+						},
+					},
+				},
+			),
+			expectResult: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+		},
+		"service with annotation openyurt.io/topologyKeys: kubernetes.io/zone": {
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.3",
+						},
+						NodeName: &nodeName2,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+			kubeClient: k8sfake.NewSimpleClientset(
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: currentNodeName,
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node2",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "shanghai",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node3",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Service{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "svc1",
+						Namespace: "default",
+						Annotations: map[string]string{
+							AnnotationServiceTopologyKey: AnnotationServiceTopologyValueNodePool,
+						},
+					},
+				},
+			),
+			yurtClient: yurtfake.NewSimpleClientset(
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "hangzhou",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							currentNodeName,
+							"node3",
+						},
+					},
+				},
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "shanghai",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							"node2",
+						},
+					},
+				},
+			),
+			expectResult: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+		},
+		"service without annotation openyurt.io/topologyKeys": {
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.3",
+						},
+						NodeName: &nodeName2,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+			kubeClient: k8sfake.NewSimpleClientset(
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: currentNodeName,
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node2",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "shanghai",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node3",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Service{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:        "svc1",
+						Namespace:   "default",
+						Annotations: map[string]string{},
+					},
+				},
+			),
+			yurtClient: yurtfake.NewSimpleClientset(
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "hangzhou",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							currentNodeName,
+							"node3",
+						},
+					},
+				},
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "shanghai",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							"node2",
+						},
+					},
+				},
+			),
+			expectResult: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.3",
+						},
+						NodeName: &nodeName2,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+		},
+		"currentNode is not in any nodepool": {
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.3",
+						},
+						NodeName: &nodeName2,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+			kubeClient: k8sfake.NewSimpleClientset(
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:   currentNodeName,
+						Labels: map[string]string{},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node2",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "shanghai",
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node3",
+						Labels: map[string]string{
+							nodepoolv1alpha1.LabelCurrentNodePool: "hangzhou",
+						},
+					},
+				},
+				&corev1.Service{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:        "svc1",
+						Namespace:   "default",
+						Annotations: map[string]string{},
+					},
+				},
+			),
+			yurtClient: yurtfake.NewSimpleClientset(
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "hangzhou",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							"node3",
+						},
+					},
+				},
+				&nodepoolv1alpha1.NodePool{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "shanghai",
+					},
+					Spec: nodepoolv1alpha1.NodePoolSpec{
+						Type: nodepoolv1alpha1.Edge,
+					},
+					Status: nodepoolv1alpha1.NodePoolStatus{
+						Nodes: []string{
+							"node2",
+						},
+					},
+				},
+			),
+			expectResult: &discovery.EndpointSlice{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "svc1-np7sf",
+					Namespace: "default",
+					Labels: map[string]string{
+						discoveryV1beta1.LabelServiceName: "svc1",
+					},
+				},
+				Endpoints: []discovery.Endpoint{
+					{
+						Addresses: []string{
+							"10.244.1.2",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.3",
+						},
+						NodeName: &nodeName2,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.4",
+						},
+						NodeName: &currentNodeName,
+					},
+					{
+						Addresses: []string{
+							"10.244.1.5",
+						},
+						NodeName: &nodeName3,
+					},
+				},
+			},
+		},
+	}
+
+	for k, tt := range testcases {
+		t.Run(k, func(t *testing.T) {
+			tt.kubeClient.DiscoveryV1().EndpointSlices("default").Create(context.TODO(), tt.endpointSlice, metav1.CreateOptions{})
+
+			factory := informers.NewSharedInformerFactory(tt.kubeClient, 24*time.Hour)
+			serviceInformer := factory.Core().V1().Services()
+			serviceInformer.Informer()
+			serviceLister := serviceInformer.Lister()
+
+			stopper := make(chan struct{})
+			defer close(stopper)
+			factory.Start(stopper)
+			factory.WaitForCacheSync(stopper)
+
+			yurtFactory := yurtinformers.NewSharedInformerFactory(tt.yurtClient, 24*time.Hour)
+			nodePoolInformer := yurtFactory.Apps().V1alpha1().NodePools()
+			nodePoolLister := nodePoolInformer.Lister()
+
+			stopper2 := make(chan struct{})
+			defer close(stopper2)
+			yurtFactory.Start(stopper2)
+			yurtFactory.WaitForCacheSync(stopper2)
+
+			nodeGetter := func(name string) (*corev1.Node, error) {
+				return tt.kubeClient.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{})
+			}
+
+			fh := &serviceTopologyFilterHandler{
+				nodeName:       currentNodeName,
+				serviceLister:  serviceLister,
+				nodePoolLister: nodePoolLister,
+				nodeGetter:     nodeGetter,
+			}
+
+			reassembledEndpointSlice := fh.reassembleEndpointSlice(tt.endpointSlice)
+
+			if !isEqualEndpointSlice(reassembledEndpointSlice, tt.expectResult) {
+				t.Errorf("reassembleEndpointSlice got error, expected: \n%v\nbut got: \n%v\n", tt.expectResult, reassembledEndpointSlice)
+			}
+		})
+	}
+}
+
+func isEqualEndpointSlice(endpointSlice1, endpointSlice2 *discovery.EndpointSlice) bool {
+	if endpointSlice1.Name != endpointSlice2.Name ||
+		endpointSlice1.Namespace != endpointSlice2.Namespace ||
+		endpointSlice1.Labels[discovery.LabelServiceName] != endpointSlice2.Labels[discovery.LabelServiceName] {
+		return false
+	}
+
+	endpoints1 := endpointSlice1.Endpoints
+	endpoints2 := endpointSlice2.Endpoints
+	if len(endpoints1) != len(endpoints2) {
+		return false
+	}
+
+	for i := 0; i < len(endpoints1); i++ {
+		if !isEqualStrings(endpoints1[i].Addresses, endpoints2[i].Addresses) {
+			return false
+		}
+
+		if endpoints1[i].NodeName != endpoints2[i].NodeName {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/pkg/yurttunnel/server/serveraddr/addr_test.go b/pkg/yurttunnel/server/serveraddr/addr_test.go
index b668ca70..53cea5aa 100644
--- a/pkg/yurttunnel/server/serveraddr/addr_test.go
+++ b/pkg/yurttunnel/server/serveraddr/addr_test.go
@@ -447,6 +447,7 @@ func TestExtractTunnelServerDNSandIPs(t *testing.T) {
 					net.ParseIP("192.168.1.1"),
 					net.ParseIP("10.10.102.1"),
 					net.ParseIP("127.0.0.1"),
+					net.ParseIP("::1"),
 					net.ParseIP("192.168.1.2"),
 					net.ParseIP("192.168.1.3"),
 				},
@@ -520,6 +521,7 @@ func TestExtractTunnelServerDNSandIPs(t *testing.T) {
 				ips: []net.IP{
 					net.ParseIP("10.10.102.1"),
 					net.ParseIP("127.0.0.1"),
+					net.ParseIP("::1"),
 					net.ParseIP("192.168.1.2"),
 					net.ParseIP("192.168.1.3"),
 				},
@@ -612,6 +614,7 @@ func TestExtractTunnelServerDNSandIPs(t *testing.T) {
 					net.ParseIP("192.168.1.5"),
 					net.ParseIP("10.10.102.1"),
 					net.ParseIP("127.0.0.1"),
+					net.ParseIP("::1"),
 					net.ParseIP("192.168.1.2"),
 					net.ParseIP("192.168.1.3"),
 				},